xref: /openbmc/u-boot/tools/buildman/builder.py (revision b50113f373a95d28cf001eb20adc6ef56f8ba14c)
1fc3fe1c2SSimon Glass# Copyright (c) 2013 The Chromium OS Authors.
2fc3fe1c2SSimon Glass#
3fc3fe1c2SSimon Glass# Bloat-o-meter code used here Copyright 2004 Matt Mackall <mpm@selenic.com>
4fc3fe1c2SSimon Glass#
51a459660SWolfgang Denk# SPDX-License-Identifier:	GPL-2.0+
6fc3fe1c2SSimon Glass#
7fc3fe1c2SSimon Glass
8fc3fe1c2SSimon Glassimport collections
9fc3fe1c2SSimon Glassfrom datetime import datetime, timedelta
10fc3fe1c2SSimon Glassimport glob
11fc3fe1c2SSimon Glassimport os
12fc3fe1c2SSimon Glassimport re
13fc3fe1c2SSimon Glassimport Queue
14fc3fe1c2SSimon Glassimport shutil
152f256648SSimon Glassimport signal
16fc3fe1c2SSimon Glassimport string
17fc3fe1c2SSimon Glassimport sys
18d436e381SSimon Glassimport threading
19fc3fe1c2SSimon Glassimport time
20fc3fe1c2SSimon Glass
21190064b4SSimon Glassimport builderthread
22fc3fe1c2SSimon Glassimport command
23fc3fe1c2SSimon Glassimport gitutil
24fc3fe1c2SSimon Glassimport terminal
254653a882SSimon Glassfrom terminal import Print
26fc3fe1c2SSimon Glassimport toolchain
27fc3fe1c2SSimon Glass
28fc3fe1c2SSimon Glass
29fc3fe1c2SSimon Glass"""
30fc3fe1c2SSimon GlassTheory of Operation
31fc3fe1c2SSimon Glass
32fc3fe1c2SSimon GlassPlease see README for user documentation, and you should be familiar with
33fc3fe1c2SSimon Glassthat before trying to make sense of this.
34fc3fe1c2SSimon Glass
35fc3fe1c2SSimon GlassBuildman works by keeping the machine as busy as possible, building different
36fc3fe1c2SSimon Glasscommits for different boards on multiple CPUs at once.
37fc3fe1c2SSimon Glass
38fc3fe1c2SSimon GlassThe source repo (self.git_dir) contains all the commits to be built. Each
39fc3fe1c2SSimon Glassthread works on a single board at a time. It checks out the first commit,
40fc3fe1c2SSimon Glassconfigures it for that board, then builds it. Then it checks out the next
41fc3fe1c2SSimon Glasscommit and builds it (typically without re-configuring). When it runs out
42fc3fe1c2SSimon Glassof commits, it gets another job from the builder and starts again with that
43fc3fe1c2SSimon Glassboard.
44fc3fe1c2SSimon Glass
45fc3fe1c2SSimon GlassClearly the builder threads could work either way - they could check out a
46fc3fe1c2SSimon Glasscommit and then built it for all boards. Using separate directories for each
47fc3fe1c2SSimon Glasscommit/board pair they could leave their build product around afterwards
48fc3fe1c2SSimon Glassalso.
49fc3fe1c2SSimon Glass
50fc3fe1c2SSimon GlassThe intent behind building a single board for multiple commits, is to make
51fc3fe1c2SSimon Glassuse of incremental builds. Since each commit is built incrementally from
52fc3fe1c2SSimon Glassthe previous one, builds are faster. Reconfiguring for a different board
53fc3fe1c2SSimon Glassremoves all intermediate object files.
54fc3fe1c2SSimon Glass
55fc3fe1c2SSimon GlassMany threads can be working at once, but each has its own working directory.
56fc3fe1c2SSimon GlassWhen a thread finishes a build, it puts the output files into a result
57fc3fe1c2SSimon Glassdirectory.
58fc3fe1c2SSimon Glass
59fc3fe1c2SSimon GlassThe base directory used by buildman is normally '../<branch>', i.e.
60fc3fe1c2SSimon Glassa directory higher than the source repository and named after the branch
61fc3fe1c2SSimon Glassbeing built.
62fc3fe1c2SSimon Glass
63fc3fe1c2SSimon GlassWithin the base directory, we have one subdirectory for each commit. Within
64fc3fe1c2SSimon Glassthat is one subdirectory for each board. Within that is the build output for
65fc3fe1c2SSimon Glassthat commit/board combination.
66fc3fe1c2SSimon Glass
67fc3fe1c2SSimon GlassBuildman also create working directories for each thread, in a .bm-work/
68fc3fe1c2SSimon Glasssubdirectory in the base dir.
69fc3fe1c2SSimon Glass
70fc3fe1c2SSimon GlassAs an example, say we are building branch 'us-net' for boards 'sandbox' and
71fc3fe1c2SSimon Glass'seaboard', and say that us-net has two commits. We will have directories
72fc3fe1c2SSimon Glasslike this:
73fc3fe1c2SSimon Glass
74fc3fe1c2SSimon Glassus-net/             base directory
75fc3fe1c2SSimon Glass    01_of_02_g4ed4ebc_net--Add-tftp-speed-/
76fc3fe1c2SSimon Glass        sandbox/
77fc3fe1c2SSimon Glass            u-boot.bin
78fc3fe1c2SSimon Glass        seaboard/
79fc3fe1c2SSimon Glass            u-boot.bin
80fc3fe1c2SSimon Glass    02_of_02_g4ed4ebc_net--Check-tftp-comp/
81fc3fe1c2SSimon Glass        sandbox/
82fc3fe1c2SSimon Glass            u-boot.bin
83fc3fe1c2SSimon Glass        seaboard/
84fc3fe1c2SSimon Glass            u-boot.bin
85fc3fe1c2SSimon Glass    .bm-work/
86fc3fe1c2SSimon Glass        00/         working directory for thread 0 (contains source checkout)
87fc3fe1c2SSimon Glass            build/  build output
88fc3fe1c2SSimon Glass        01/         working directory for thread 1
89fc3fe1c2SSimon Glass            build/  build output
90fc3fe1c2SSimon Glass        ...
91fc3fe1c2SSimon Glassu-boot/             source directory
92fc3fe1c2SSimon Glass    .git/           repository
93fc3fe1c2SSimon Glass"""
94fc3fe1c2SSimon Glass
95fc3fe1c2SSimon Glass# Possible build outcomes
96fc3fe1c2SSimon GlassOUTCOME_OK, OUTCOME_WARNING, OUTCOME_ERROR, OUTCOME_UNKNOWN = range(4)
97fc3fe1c2SSimon Glass
98fc3fe1c2SSimon Glass# Translate a commit subject into a valid filename
99fc3fe1c2SSimon Glasstrans_valid_chars = string.maketrans("/: ", "---")
100fc3fe1c2SSimon Glass
101843312dcSSimon GlassCONFIG_FILENAMES = [
102843312dcSSimon Glass    '.config', '.config-spl', '.config-tpl',
103843312dcSSimon Glass    'autoconf.mk', 'autoconf-spl.mk', 'autoconf-tpl.mk',
104843312dcSSimon Glass    'autoconf.h', 'autoconf-spl.h','autoconf-tpl.h',
105843312dcSSimon Glass    'u-boot.cfg', 'u-boot-spl.cfg', 'u-boot-tpl.cfg'
106843312dcSSimon Glass]
107843312dcSSimon Glass
1088270e3c1SSimon Glassclass Config:
1098270e3c1SSimon Glass    """Holds information about configuration settings for a board."""
1108270e3c1SSimon Glass    def __init__(self, target):
1118270e3c1SSimon Glass        self.target = target
1128270e3c1SSimon Glass        self.config = {}
1138270e3c1SSimon Glass        for fname in CONFIG_FILENAMES:
1148270e3c1SSimon Glass            self.config[fname] = {}
1158270e3c1SSimon Glass
1168270e3c1SSimon Glass    def Add(self, fname, key, value):
1178270e3c1SSimon Glass        self.config[fname][key] = value
1188270e3c1SSimon Glass
1198270e3c1SSimon Glass    def __hash__(self):
1208270e3c1SSimon Glass        val = 0
1218270e3c1SSimon Glass        for fname in self.config:
1228270e3c1SSimon Glass            for key, value in self.config[fname].iteritems():
1238270e3c1SSimon Glass                print key, value
1248270e3c1SSimon Glass                val = val ^ hash(key) & hash(value)
1258270e3c1SSimon Glass        return val
126fc3fe1c2SSimon Glass
127fc3fe1c2SSimon Glassclass Builder:
128fc3fe1c2SSimon Glass    """Class for building U-Boot for a particular commit.
129fc3fe1c2SSimon Glass
130fc3fe1c2SSimon Glass    Public members: (many should ->private)
131fc3fe1c2SSimon Glass        already_done: Number of builds already completed
132fc3fe1c2SSimon Glass        base_dir: Base directory to use for builder
133fc3fe1c2SSimon Glass        checkout: True to check out source, False to skip that step.
134fc3fe1c2SSimon Glass            This is used for testing.
135fc3fe1c2SSimon Glass        col: terminal.Color() object
136fc3fe1c2SSimon Glass        count: Number of commits to build
137fc3fe1c2SSimon Glass        do_make: Method to call to invoke Make
138fc3fe1c2SSimon Glass        fail: Number of builds that failed due to error
139fc3fe1c2SSimon Glass        force_build: Force building even if a build already exists
140fc3fe1c2SSimon Glass        force_config_on_failure: If a commit fails for a board, disable
141fc3fe1c2SSimon Glass            incremental building for the next commit we build for that
142fc3fe1c2SSimon Glass            board, so that we will see all warnings/errors again.
1434266dc28SSimon Glass        force_build_failures: If a previously-built build (i.e. built on
1444266dc28SSimon Glass            a previous run of buildman) is marked as failed, rebuild it.
145fc3fe1c2SSimon Glass        git_dir: Git directory containing source repository
146fc3fe1c2SSimon Glass        last_line_len: Length of the last line we printed (used for erasing
147fc3fe1c2SSimon Glass            it with new progress information)
148fc3fe1c2SSimon Glass        num_jobs: Number of jobs to run at once (passed to make as -j)
149fc3fe1c2SSimon Glass        num_threads: Number of builder threads to run
150fc3fe1c2SSimon Glass        out_queue: Queue of results to process
151fc3fe1c2SSimon Glass        re_make_err: Compiled regular expression for ignore_lines
152fc3fe1c2SSimon Glass        queue: Queue of jobs to run
153fc3fe1c2SSimon Glass        threads: List of active threads
154fc3fe1c2SSimon Glass        toolchains: Toolchains object to use for building
155fc3fe1c2SSimon Glass        upto: Current commit number we are building (0.count-1)
156fc3fe1c2SSimon Glass        warned: Number of builds that produced at least one warning
15797e91526SSimon Glass        force_reconfig: Reconfigure U-Boot on each comiit. This disables
15897e91526SSimon Glass            incremental building, where buildman reconfigures on the first
15997e91526SSimon Glass            commit for a baord, and then just does an incremental build for
16097e91526SSimon Glass            the following commits. In fact buildman will reconfigure and
16197e91526SSimon Glass            retry for any failing commits, so generally the only effect of
16297e91526SSimon Glass            this option is to slow things down.
163189a4968SSimon Glass        in_tree: Build U-Boot in-tree instead of specifying an output
164189a4968SSimon Glass            directory separate from the source code. This option is really
165189a4968SSimon Glass            only useful for testing in-tree builds.
166fc3fe1c2SSimon Glass
167fc3fe1c2SSimon Glass    Private members:
168fc3fe1c2SSimon Glass        _base_board_dict: Last-summarised Dict of boards
169fc3fe1c2SSimon Glass        _base_err_lines: Last-summarised list of errors
170e30965dbSSimon Glass        _base_warn_lines: Last-summarised list of warnings
171fc3fe1c2SSimon Glass        _build_period_us: Time taken for a single build (float object).
172fc3fe1c2SSimon Glass        _complete_delay: Expected delay until completion (timedelta)
173fc3fe1c2SSimon Glass        _next_delay_update: Next time we plan to display a progress update
174fc3fe1c2SSimon Glass                (datatime)
175fc3fe1c2SSimon Glass        _show_unknown: Show unknown boards (those not built) in summary
176fc3fe1c2SSimon Glass        _timestamps: List of timestamps for the completion of the last
177fc3fe1c2SSimon Glass            last _timestamp_count builds. Each is a datetime object.
178fc3fe1c2SSimon Glass        _timestamp_count: Number of timestamps to keep in our list.
179fc3fe1c2SSimon Glass        _working_dir: Base working directory containing all threads
180fc3fe1c2SSimon Glass    """
181fc3fe1c2SSimon Glass    class Outcome:
182fc3fe1c2SSimon Glass        """Records a build outcome for a single make invocation
183fc3fe1c2SSimon Glass
184fc3fe1c2SSimon Glass        Public Members:
185fc3fe1c2SSimon Glass            rc: Outcome value (OUTCOME_...)
186fc3fe1c2SSimon Glass            err_lines: List of error lines or [] if none
187fc3fe1c2SSimon Glass            sizes: Dictionary of image size information, keyed by filename
188fc3fe1c2SSimon Glass                - Each value is itself a dictionary containing
189fc3fe1c2SSimon Glass                    values for 'text', 'data' and 'bss', being the integer
190fc3fe1c2SSimon Glass                    size in bytes of each section.
191fc3fe1c2SSimon Glass            func_sizes: Dictionary keyed by filename - e.g. 'u-boot'. Each
192fc3fe1c2SSimon Glass                    value is itself a dictionary:
193fc3fe1c2SSimon Glass                        key: function name
194fc3fe1c2SSimon Glass                        value: Size of function in bytes
195843312dcSSimon Glass            config: Dictionary keyed by filename - e.g. '.config'. Each
196843312dcSSimon Glass                    value is itself a dictionary:
197843312dcSSimon Glass                        key: config name
198843312dcSSimon Glass                        value: config value
199fc3fe1c2SSimon Glass        """
200843312dcSSimon Glass        def __init__(self, rc, err_lines, sizes, func_sizes, config):
201fc3fe1c2SSimon Glass            self.rc = rc
202fc3fe1c2SSimon Glass            self.err_lines = err_lines
203fc3fe1c2SSimon Glass            self.sizes = sizes
204fc3fe1c2SSimon Glass            self.func_sizes = func_sizes
205843312dcSSimon Glass            self.config = config
206fc3fe1c2SSimon Glass
207fc3fe1c2SSimon Glass    def __init__(self, toolchains, base_dir, git_dir, num_threads, num_jobs,
2085971ab5cSSimon Glass                 gnu_make='make', checkout=True, show_unknown=True, step=1,
209f79f1e0cSStephen Warren                 no_subdirs=False, full_path=False, verbose_build=False,
210*b50113f3SSimon Glass                 incremental=False, per_board_out_dir=False,
211*b50113f3SSimon Glass                 config_only=False):
212fc3fe1c2SSimon Glass        """Create a new Builder object
213fc3fe1c2SSimon Glass
214fc3fe1c2SSimon Glass        Args:
215fc3fe1c2SSimon Glass            toolchains: Toolchains object to use for building
216fc3fe1c2SSimon Glass            base_dir: Base directory to use for builder
217fc3fe1c2SSimon Glass            git_dir: Git directory containing source repository
218fc3fe1c2SSimon Glass            num_threads: Number of builder threads to run
219fc3fe1c2SSimon Glass            num_jobs: Number of jobs to run at once (passed to make as -j)
22099796923SMasahiro Yamada            gnu_make: the command name of GNU Make.
221fc3fe1c2SSimon Glass            checkout: True to check out source, False to skip that step.
222fc3fe1c2SSimon Glass                This is used for testing.
223fc3fe1c2SSimon Glass            show_unknown: Show unknown boards (those not built) in summary
224fc3fe1c2SSimon Glass            step: 1 to process every commit, n to process every nth commit
225bb1501f2SSimon Glass            no_subdirs: Don't create subdirectories when building current
226bb1501f2SSimon Glass                source for a single board
227bb1501f2SSimon Glass            full_path: Return the full path in CROSS_COMPILE and don't set
228bb1501f2SSimon Glass                PATH
229d2ce658dSSimon Glass            verbose_build: Run build with V=1 and don't use 'make -s'
230f79f1e0cSStephen Warren            incremental: Always perform incremental builds; don't run make
231f79f1e0cSStephen Warren                mrproper when configuring
232f79f1e0cSStephen Warren            per_board_out_dir: Build in a separate persistent directory per
233f79f1e0cSStephen Warren                board rather than a thread-specific directory
234*b50113f3SSimon Glass            config_only: Only configure each build, don't build it
235fc3fe1c2SSimon Glass        """
236fc3fe1c2SSimon Glass        self.toolchains = toolchains
237fc3fe1c2SSimon Glass        self.base_dir = base_dir
238fc3fe1c2SSimon Glass        self._working_dir = os.path.join(base_dir, '.bm-work')
239fc3fe1c2SSimon Glass        self.threads = []
240fc3fe1c2SSimon Glass        self.do_make = self.Make
24199796923SMasahiro Yamada        self.gnu_make = gnu_make
242fc3fe1c2SSimon Glass        self.checkout = checkout
243fc3fe1c2SSimon Glass        self.num_threads = num_threads
244fc3fe1c2SSimon Glass        self.num_jobs = num_jobs
245fc3fe1c2SSimon Glass        self.already_done = 0
246fc3fe1c2SSimon Glass        self.force_build = False
247fc3fe1c2SSimon Glass        self.git_dir = git_dir
248fc3fe1c2SSimon Glass        self._show_unknown = show_unknown
249fc3fe1c2SSimon Glass        self._timestamp_count = 10
250fc3fe1c2SSimon Glass        self._build_period_us = None
251fc3fe1c2SSimon Glass        self._complete_delay = None
252fc3fe1c2SSimon Glass        self._next_delay_update = datetime.now()
253fc3fe1c2SSimon Glass        self.force_config_on_failure = True
2544266dc28SSimon Glass        self.force_build_failures = False
25597e91526SSimon Glass        self.force_reconfig = False
256fc3fe1c2SSimon Glass        self._step = step
257189a4968SSimon Glass        self.in_tree = False
25828370c1bSSimon Glass        self._error_lines = 0
2595971ab5cSSimon Glass        self.no_subdirs = no_subdirs
260bb1501f2SSimon Glass        self.full_path = full_path
261d2ce658dSSimon Glass        self.verbose_build = verbose_build
262*b50113f3SSimon Glass        self.config_only = config_only
263fc3fe1c2SSimon Glass
264fc3fe1c2SSimon Glass        self.col = terminal.Color()
265fc3fe1c2SSimon Glass
266e30965dbSSimon Glass        self._re_function = re.compile('(.*): In function.*')
267e30965dbSSimon Glass        self._re_files = re.compile('In file included from.*')
268e30965dbSSimon Glass        self._re_warning = re.compile('(.*):(\d*):(\d*): warning: .*')
269e30965dbSSimon Glass        self._re_note = re.compile('(.*):(\d*):(\d*): note: this is the location of the previous.*')
270e30965dbSSimon Glass
271fc3fe1c2SSimon Glass        self.queue = Queue.Queue()
272fc3fe1c2SSimon Glass        self.out_queue = Queue.Queue()
273fc3fe1c2SSimon Glass        for i in range(self.num_threads):
274f79f1e0cSStephen Warren            t = builderthread.BuilderThread(self, i, incremental,
275f79f1e0cSStephen Warren                    per_board_out_dir)
276fc3fe1c2SSimon Glass            t.setDaemon(True)
277fc3fe1c2SSimon Glass            t.start()
278fc3fe1c2SSimon Glass            self.threads.append(t)
279fc3fe1c2SSimon Glass
280fc3fe1c2SSimon Glass        self.last_line_len = 0
281190064b4SSimon Glass        t = builderthread.ResultThread(self)
282fc3fe1c2SSimon Glass        t.setDaemon(True)
283fc3fe1c2SSimon Glass        t.start()
284fc3fe1c2SSimon Glass        self.threads.append(t)
285fc3fe1c2SSimon Glass
286fc3fe1c2SSimon Glass        ignore_lines = ['(make.*Waiting for unfinished)', '(Segmentation fault)']
287fc3fe1c2SSimon Glass        self.re_make_err = re.compile('|'.join(ignore_lines))
288fc3fe1c2SSimon Glass
2892f256648SSimon Glass        # Handle existing graceful with SIGINT / Ctrl-C
2902f256648SSimon Glass        signal.signal(signal.SIGINT, self.signal_handler)
2912f256648SSimon Glass
292fc3fe1c2SSimon Glass    def __del__(self):
293fc3fe1c2SSimon Glass        """Get rid of all threads created by the builder"""
294fc3fe1c2SSimon Glass        for t in self.threads:
295fc3fe1c2SSimon Glass            del t
296fc3fe1c2SSimon Glass
2972f256648SSimon Glass    def signal_handler(self, signal, frame):
2982f256648SSimon Glass        sys.exit(1)
2992f256648SSimon Glass
300b2ea7ab2SSimon Glass    def SetDisplayOptions(self, show_errors=False, show_sizes=False,
301ed966657SSimon Glass                          show_detail=False, show_bloat=False,
302843312dcSSimon Glass                          list_error_boards=False, show_config=False):
303b2ea7ab2SSimon Glass        """Setup display options for the builder.
304b2ea7ab2SSimon Glass
305b2ea7ab2SSimon Glass        show_errors: True to show summarised error/warning info
306b2ea7ab2SSimon Glass        show_sizes: Show size deltas
307b2ea7ab2SSimon Glass        show_detail: Show detail for each board
308b2ea7ab2SSimon Glass        show_bloat: Show detail for each function
309ed966657SSimon Glass        list_error_boards: Show the boards which caused each error/warning
310843312dcSSimon Glass        show_config: Show config deltas
311b2ea7ab2SSimon Glass        """
312b2ea7ab2SSimon Glass        self._show_errors = show_errors
313b2ea7ab2SSimon Glass        self._show_sizes = show_sizes
314b2ea7ab2SSimon Glass        self._show_detail = show_detail
315b2ea7ab2SSimon Glass        self._show_bloat = show_bloat
316ed966657SSimon Glass        self._list_error_boards = list_error_boards
317843312dcSSimon Glass        self._show_config = show_config
318b2ea7ab2SSimon Glass
319fc3fe1c2SSimon Glass    def _AddTimestamp(self):
320fc3fe1c2SSimon Glass        """Add a new timestamp to the list and record the build period.
321fc3fe1c2SSimon Glass
322fc3fe1c2SSimon Glass        The build period is the length of time taken to perform a single
323fc3fe1c2SSimon Glass        build (one board, one commit).
324fc3fe1c2SSimon Glass        """
325fc3fe1c2SSimon Glass        now = datetime.now()
326fc3fe1c2SSimon Glass        self._timestamps.append(now)
327fc3fe1c2SSimon Glass        count = len(self._timestamps)
328fc3fe1c2SSimon Glass        delta = self._timestamps[-1] - self._timestamps[0]
329fc3fe1c2SSimon Glass        seconds = delta.total_seconds()
330fc3fe1c2SSimon Glass
331fc3fe1c2SSimon Glass        # If we have enough data, estimate build period (time taken for a
332fc3fe1c2SSimon Glass        # single build) and therefore completion time.
333fc3fe1c2SSimon Glass        if count > 1 and self._next_delay_update < now:
334fc3fe1c2SSimon Glass            self._next_delay_update = now + timedelta(seconds=2)
335fc3fe1c2SSimon Glass            if seconds > 0:
336fc3fe1c2SSimon Glass                self._build_period = float(seconds) / count
337fc3fe1c2SSimon Glass                todo = self.count - self.upto
338fc3fe1c2SSimon Glass                self._complete_delay = timedelta(microseconds=
339fc3fe1c2SSimon Glass                        self._build_period * todo * 1000000)
340fc3fe1c2SSimon Glass                # Round it
341fc3fe1c2SSimon Glass                self._complete_delay -= timedelta(
342fc3fe1c2SSimon Glass                        microseconds=self._complete_delay.microseconds)
343fc3fe1c2SSimon Glass
344fc3fe1c2SSimon Glass        if seconds > 60:
345fc3fe1c2SSimon Glass            self._timestamps.popleft()
346fc3fe1c2SSimon Glass            count -= 1
347fc3fe1c2SSimon Glass
348fc3fe1c2SSimon Glass    def ClearLine(self, length):
349fc3fe1c2SSimon Glass        """Clear any characters on the current line
350fc3fe1c2SSimon Glass
351fc3fe1c2SSimon Glass        Make way for a new line of length 'length', by outputting enough
352fc3fe1c2SSimon Glass        spaces to clear out the old line. Then remember the new length for
353fc3fe1c2SSimon Glass        next time.
354fc3fe1c2SSimon Glass
355fc3fe1c2SSimon Glass        Args:
356fc3fe1c2SSimon Glass            length: Length of new line, in characters
357fc3fe1c2SSimon Glass        """
358fc3fe1c2SSimon Glass        if length < self.last_line_len:
3594653a882SSimon Glass            Print(' ' * (self.last_line_len - length), newline=False)
3604653a882SSimon Glass            Print('\r', newline=False)
361fc3fe1c2SSimon Glass        self.last_line_len = length
362fc3fe1c2SSimon Glass        sys.stdout.flush()
363fc3fe1c2SSimon Glass
364fc3fe1c2SSimon Glass    def SelectCommit(self, commit, checkout=True):
365fc3fe1c2SSimon Glass        """Checkout the selected commit for this build
366fc3fe1c2SSimon Glass        """
367fc3fe1c2SSimon Glass        self.commit = commit
368fc3fe1c2SSimon Glass        if checkout and self.checkout:
369fc3fe1c2SSimon Glass            gitutil.Checkout(commit.hash)
370fc3fe1c2SSimon Glass
371fc3fe1c2SSimon Glass    def Make(self, commit, brd, stage, cwd, *args, **kwargs):
372fc3fe1c2SSimon Glass        """Run make
373fc3fe1c2SSimon Glass
374fc3fe1c2SSimon Glass        Args:
375fc3fe1c2SSimon Glass            commit: Commit object that is being built
376fc3fe1c2SSimon Glass            brd: Board object that is being built
377fd18a89eSRoger Meier            stage: Stage that we are at (mrproper, config, build)
378fc3fe1c2SSimon Glass            cwd: Directory where make should be run
379fc3fe1c2SSimon Glass            args: Arguments to pass to make
380fc3fe1c2SSimon Glass            kwargs: Arguments to pass to command.RunPipe()
381fc3fe1c2SSimon Glass        """
38299796923SMasahiro Yamada        cmd = [self.gnu_make] + list(args)
383fc3fe1c2SSimon Glass        result = command.RunPipe([cmd], capture=True, capture_stderr=True,
384fc3fe1c2SSimon Glass                cwd=cwd, raise_on_error=False, **kwargs)
38540f11fceSSimon Glass        if self.verbose_build:
38640f11fceSSimon Glass            result.stdout = '%s\n' % (' '.join(cmd)) + result.stdout
38740f11fceSSimon Glass            result.combined = '%s\n' % (' '.join(cmd)) + result.combined
388fc3fe1c2SSimon Glass        return result
389fc3fe1c2SSimon Glass
390fc3fe1c2SSimon Glass    def ProcessResult(self, result):
391fc3fe1c2SSimon Glass        """Process the result of a build, showing progress information
392fc3fe1c2SSimon Glass
393fc3fe1c2SSimon Glass        Args:
394e5a0e5d8SSimon Glass            result: A CommandResult object, which indicates the result for
395e5a0e5d8SSimon Glass                    a single build
396fc3fe1c2SSimon Glass        """
397fc3fe1c2SSimon Glass        col = terminal.Color()
398fc3fe1c2SSimon Glass        if result:
399fc3fe1c2SSimon Glass            target = result.brd.target
400fc3fe1c2SSimon Glass
401fc3fe1c2SSimon Glass            self.upto += 1
402fc3fe1c2SSimon Glass            if result.return_code != 0:
403fc3fe1c2SSimon Glass                self.fail += 1
404fc3fe1c2SSimon Glass            elif result.stderr:
405fc3fe1c2SSimon Glass                self.warned += 1
406fc3fe1c2SSimon Glass            if result.already_done:
407fc3fe1c2SSimon Glass                self.already_done += 1
408e5a0e5d8SSimon Glass            if self._verbose:
4094653a882SSimon Glass                Print('\r', newline=False)
410e5a0e5d8SSimon Glass                self.ClearLine(0)
411e5a0e5d8SSimon Glass                boards_selected = {target : result.brd}
412e5a0e5d8SSimon Glass                self.ResetResultSummary(boards_selected)
413e5a0e5d8SSimon Glass                self.ProduceResultSummary(result.commit_upto, self.commits,
414e5a0e5d8SSimon Glass                                          boards_selected)
415fc3fe1c2SSimon Glass        else:
416fc3fe1c2SSimon Glass            target = '(starting)'
417fc3fe1c2SSimon Glass
418fc3fe1c2SSimon Glass        # Display separate counts for ok, warned and fail
419fc3fe1c2SSimon Glass        ok = self.upto - self.warned - self.fail
420fc3fe1c2SSimon Glass        line = '\r' + self.col.Color(self.col.GREEN, '%5d' % ok)
421fc3fe1c2SSimon Glass        line += self.col.Color(self.col.YELLOW, '%5d' % self.warned)
422fc3fe1c2SSimon Glass        line += self.col.Color(self.col.RED, '%5d' % self.fail)
423fc3fe1c2SSimon Glass
424fc3fe1c2SSimon Glass        name = ' /%-5d  ' % self.count
425fc3fe1c2SSimon Glass
426fc3fe1c2SSimon Glass        # Add our current completion time estimate
427fc3fe1c2SSimon Glass        self._AddTimestamp()
428fc3fe1c2SSimon Glass        if self._complete_delay:
429fc3fe1c2SSimon Glass            name += '%s  : ' % self._complete_delay
430fc3fe1c2SSimon Glass        # When building all boards for a commit, we can print a commit
431fc3fe1c2SSimon Glass        # progress message.
432fc3fe1c2SSimon Glass        if result and result.commit_upto is None:
433fc3fe1c2SSimon Glass            name += 'commit %2d/%-3d' % (self.commit_upto + 1,
434fc3fe1c2SSimon Glass                    self.commit_count)
435fc3fe1c2SSimon Glass
436fc3fe1c2SSimon Glass        name += target
4374653a882SSimon Glass        Print(line + name, newline=False)
438e5a0e5d8SSimon Glass        length = 14 + len(name)
439fc3fe1c2SSimon Glass        self.ClearLine(length)
440fc3fe1c2SSimon Glass
441fc3fe1c2SSimon Glass    def _GetOutputDir(self, commit_upto):
442fc3fe1c2SSimon Glass        """Get the name of the output directory for a commit number
443fc3fe1c2SSimon Glass
444fc3fe1c2SSimon Glass        The output directory is typically .../<branch>/<commit>.
445fc3fe1c2SSimon Glass
446fc3fe1c2SSimon Glass        Args:
447fc3fe1c2SSimon Glass            commit_upto: Commit number to use (0..self.count-1)
448fc3fe1c2SSimon Glass        """
4495971ab5cSSimon Glass        commit_dir = None
450fea5858eSSimon Glass        if self.commits:
451fc3fe1c2SSimon Glass            commit = self.commits[commit_upto]
452fc3fe1c2SSimon Glass            subject = commit.subject.translate(trans_valid_chars)
453fc3fe1c2SSimon Glass            commit_dir = ('%02d_of_%02d_g%s_%s' % (commit_upto + 1,
454fc3fe1c2SSimon Glass                    self.commit_count, commit.hash, subject[:20]))
4555971ab5cSSimon Glass        elif not self.no_subdirs:
456fea5858eSSimon Glass            commit_dir = 'current'
4575971ab5cSSimon Glass        if not commit_dir:
4585971ab5cSSimon Glass            return self.base_dir
4595971ab5cSSimon Glass        return os.path.join(self.base_dir, commit_dir)
460fc3fe1c2SSimon Glass
461fc3fe1c2SSimon Glass    def GetBuildDir(self, commit_upto, target):
462fc3fe1c2SSimon Glass        """Get the name of the build directory for a commit number
463fc3fe1c2SSimon Glass
464fc3fe1c2SSimon Glass        The build directory is typically .../<branch>/<commit>/<target>.
465fc3fe1c2SSimon Glass
466fc3fe1c2SSimon Glass        Args:
467fc3fe1c2SSimon Glass            commit_upto: Commit number to use (0..self.count-1)
468fc3fe1c2SSimon Glass            target: Target name
469fc3fe1c2SSimon Glass        """
470fc3fe1c2SSimon Glass        output_dir = self._GetOutputDir(commit_upto)
471fc3fe1c2SSimon Glass        return os.path.join(output_dir, target)
472fc3fe1c2SSimon Glass
473fc3fe1c2SSimon Glass    def GetDoneFile(self, commit_upto, target):
474fc3fe1c2SSimon Glass        """Get the name of the done file for a commit number
475fc3fe1c2SSimon Glass
476fc3fe1c2SSimon Glass        Args:
477fc3fe1c2SSimon Glass            commit_upto: Commit number to use (0..self.count-1)
478fc3fe1c2SSimon Glass            target: Target name
479fc3fe1c2SSimon Glass        """
480fc3fe1c2SSimon Glass        return os.path.join(self.GetBuildDir(commit_upto, target), 'done')
481fc3fe1c2SSimon Glass
482fc3fe1c2SSimon Glass    def GetSizesFile(self, commit_upto, target):
483fc3fe1c2SSimon Glass        """Get the name of the sizes file for a commit number
484fc3fe1c2SSimon Glass
485fc3fe1c2SSimon Glass        Args:
486fc3fe1c2SSimon Glass            commit_upto: Commit number to use (0..self.count-1)
487fc3fe1c2SSimon Glass            target: Target name
488fc3fe1c2SSimon Glass        """
489fc3fe1c2SSimon Glass        return os.path.join(self.GetBuildDir(commit_upto, target), 'sizes')
490fc3fe1c2SSimon Glass
491fc3fe1c2SSimon Glass    def GetFuncSizesFile(self, commit_upto, target, elf_fname):
492fc3fe1c2SSimon Glass        """Get the name of the funcsizes file for a commit number and ELF file
493fc3fe1c2SSimon Glass
494fc3fe1c2SSimon Glass        Args:
495fc3fe1c2SSimon Glass            commit_upto: Commit number to use (0..self.count-1)
496fc3fe1c2SSimon Glass            target: Target name
497fc3fe1c2SSimon Glass            elf_fname: Filename of elf image
498fc3fe1c2SSimon Glass        """
499fc3fe1c2SSimon Glass        return os.path.join(self.GetBuildDir(commit_upto, target),
500fc3fe1c2SSimon Glass                            '%s.sizes' % elf_fname.replace('/', '-'))
501fc3fe1c2SSimon Glass
502fc3fe1c2SSimon Glass    def GetObjdumpFile(self, commit_upto, target, elf_fname):
503fc3fe1c2SSimon Glass        """Get the name of the objdump file for a commit number and ELF file
504fc3fe1c2SSimon Glass
505fc3fe1c2SSimon Glass        Args:
506fc3fe1c2SSimon Glass            commit_upto: Commit number to use (0..self.count-1)
507fc3fe1c2SSimon Glass            target: Target name
508fc3fe1c2SSimon Glass            elf_fname: Filename of elf image
509fc3fe1c2SSimon Glass        """
510fc3fe1c2SSimon Glass        return os.path.join(self.GetBuildDir(commit_upto, target),
511fc3fe1c2SSimon Glass                            '%s.objdump' % elf_fname.replace('/', '-'))
512fc3fe1c2SSimon Glass
513fc3fe1c2SSimon Glass    def GetErrFile(self, commit_upto, target):
514fc3fe1c2SSimon Glass        """Get the name of the err file for a commit number
515fc3fe1c2SSimon Glass
516fc3fe1c2SSimon Glass        Args:
517fc3fe1c2SSimon Glass            commit_upto: Commit number to use (0..self.count-1)
518fc3fe1c2SSimon Glass            target: Target name
519fc3fe1c2SSimon Glass        """
520fc3fe1c2SSimon Glass        output_dir = self.GetBuildDir(commit_upto, target)
521fc3fe1c2SSimon Glass        return os.path.join(output_dir, 'err')
522fc3fe1c2SSimon Glass
523fc3fe1c2SSimon Glass    def FilterErrors(self, lines):
524fc3fe1c2SSimon Glass        """Filter out errors in which we have no interest
525fc3fe1c2SSimon Glass
526fc3fe1c2SSimon Glass        We should probably use map().
527fc3fe1c2SSimon Glass
528fc3fe1c2SSimon Glass        Args:
529fc3fe1c2SSimon Glass            lines: List of error lines, each a string
530fc3fe1c2SSimon Glass        Returns:
531fc3fe1c2SSimon Glass            New list with only interesting lines included
532fc3fe1c2SSimon Glass        """
533fc3fe1c2SSimon Glass        out_lines = []
534fc3fe1c2SSimon Glass        for line in lines:
535fc3fe1c2SSimon Glass            if not self.re_make_err.search(line):
536fc3fe1c2SSimon Glass                out_lines.append(line)
537fc3fe1c2SSimon Glass        return out_lines
538fc3fe1c2SSimon Glass
539fc3fe1c2SSimon Glass    def ReadFuncSizes(self, fname, fd):
540fc3fe1c2SSimon Glass        """Read function sizes from the output of 'nm'
541fc3fe1c2SSimon Glass
542fc3fe1c2SSimon Glass        Args:
543fc3fe1c2SSimon Glass            fd: File containing data to read
544fc3fe1c2SSimon Glass            fname: Filename we are reading from (just for errors)
545fc3fe1c2SSimon Glass
546fc3fe1c2SSimon Glass        Returns:
547fc3fe1c2SSimon Glass            Dictionary containing size of each function in bytes, indexed by
548fc3fe1c2SSimon Glass            function name.
549fc3fe1c2SSimon Glass        """
550fc3fe1c2SSimon Glass        sym = {}
551fc3fe1c2SSimon Glass        for line in fd.readlines():
552fc3fe1c2SSimon Glass            try:
553fc3fe1c2SSimon Glass                size, type, name = line[:-1].split()
554fc3fe1c2SSimon Glass            except:
5554653a882SSimon Glass                Print("Invalid line in file '%s': '%s'" % (fname, line[:-1]))
556fc3fe1c2SSimon Glass                continue
557fc3fe1c2SSimon Glass            if type in 'tTdDbB':
558fc3fe1c2SSimon Glass                # function names begin with '.' on 64-bit powerpc
559fc3fe1c2SSimon Glass                if '.' in name[1:]:
560fc3fe1c2SSimon Glass                    name = 'static.' + name.split('.')[0]
561fc3fe1c2SSimon Glass                sym[name] = sym.get(name, 0) + int(size, 16)
562fc3fe1c2SSimon Glass        return sym
563fc3fe1c2SSimon Glass
564843312dcSSimon Glass    def _ProcessConfig(self, fname):
565843312dcSSimon Glass        """Read in a .config, autoconf.mk or autoconf.h file
566843312dcSSimon Glass
567843312dcSSimon Glass        This function handles all config file types. It ignores comments and
568843312dcSSimon Glass        any #defines which don't start with CONFIG_.
569843312dcSSimon Glass
570843312dcSSimon Glass        Args:
571843312dcSSimon Glass            fname: Filename to read
572843312dcSSimon Glass
573843312dcSSimon Glass        Returns:
574843312dcSSimon Glass            Dictionary:
575843312dcSSimon Glass                key: Config name (e.g. CONFIG_DM)
576843312dcSSimon Glass                value: Config value (e.g. 1)
577843312dcSSimon Glass        """
578843312dcSSimon Glass        config = {}
579843312dcSSimon Glass        if os.path.exists(fname):
580843312dcSSimon Glass            with open(fname) as fd:
581843312dcSSimon Glass                for line in fd:
582843312dcSSimon Glass                    line = line.strip()
583843312dcSSimon Glass                    if line.startswith('#define'):
584843312dcSSimon Glass                        values = line[8:].split(' ', 1)
585843312dcSSimon Glass                        if len(values) > 1:
586843312dcSSimon Glass                            key, value = values
587843312dcSSimon Glass                        else:
588843312dcSSimon Glass                            key = values[0]
589843312dcSSimon Glass                            value = ''
590843312dcSSimon Glass                        if not key.startswith('CONFIG_'):
591843312dcSSimon Glass                            continue
592843312dcSSimon Glass                    elif not line or line[0] in ['#', '*', '/']:
593843312dcSSimon Glass                        continue
594843312dcSSimon Glass                    else:
595843312dcSSimon Glass                        key, value = line.split('=', 1)
596843312dcSSimon Glass                    config[key] = value
597843312dcSSimon Glass        return config
598843312dcSSimon Glass
599843312dcSSimon Glass    def GetBuildOutcome(self, commit_upto, target, read_func_sizes,
600843312dcSSimon Glass                        read_config):
601fc3fe1c2SSimon Glass        """Work out the outcome of a build.
602fc3fe1c2SSimon Glass
603fc3fe1c2SSimon Glass        Args:
604fc3fe1c2SSimon Glass            commit_upto: Commit number to check (0..n-1)
605fc3fe1c2SSimon Glass            target: Target board to check
606fc3fe1c2SSimon Glass            read_func_sizes: True to read function size information
607843312dcSSimon Glass            read_config: True to read .config and autoconf.h files
608fc3fe1c2SSimon Glass
609fc3fe1c2SSimon Glass        Returns:
610fc3fe1c2SSimon Glass            Outcome object
611fc3fe1c2SSimon Glass        """
612fc3fe1c2SSimon Glass        done_file = self.GetDoneFile(commit_upto, target)
613fc3fe1c2SSimon Glass        sizes_file = self.GetSizesFile(commit_upto, target)
614fc3fe1c2SSimon Glass        sizes = {}
615fc3fe1c2SSimon Glass        func_sizes = {}
616843312dcSSimon Glass        config = {}
617fc3fe1c2SSimon Glass        if os.path.exists(done_file):
618fc3fe1c2SSimon Glass            with open(done_file, 'r') as fd:
619fc3fe1c2SSimon Glass                return_code = int(fd.readline())
620fc3fe1c2SSimon Glass                err_lines = []
621fc3fe1c2SSimon Glass                err_file = self.GetErrFile(commit_upto, target)
622fc3fe1c2SSimon Glass                if os.path.exists(err_file):
623fc3fe1c2SSimon Glass                    with open(err_file, 'r') as fd:
624fc3fe1c2SSimon Glass                        err_lines = self.FilterErrors(fd.readlines())
625fc3fe1c2SSimon Glass
626fc3fe1c2SSimon Glass                # Decide whether the build was ok, failed or created warnings
627fc3fe1c2SSimon Glass                if return_code:
628fc3fe1c2SSimon Glass                    rc = OUTCOME_ERROR
629fc3fe1c2SSimon Glass                elif len(err_lines):
630fc3fe1c2SSimon Glass                    rc = OUTCOME_WARNING
631fc3fe1c2SSimon Glass                else:
632fc3fe1c2SSimon Glass                    rc = OUTCOME_OK
633fc3fe1c2SSimon Glass
634fc3fe1c2SSimon Glass                # Convert size information to our simple format
635fc3fe1c2SSimon Glass                if os.path.exists(sizes_file):
636fc3fe1c2SSimon Glass                    with open(sizes_file, 'r') as fd:
637fc3fe1c2SSimon Glass                        for line in fd.readlines():
638fc3fe1c2SSimon Glass                            values = line.split()
639fc3fe1c2SSimon Glass                            rodata = 0
640fc3fe1c2SSimon Glass                            if len(values) > 6:
641fc3fe1c2SSimon Glass                                rodata = int(values[6], 16)
642fc3fe1c2SSimon Glass                            size_dict = {
643fc3fe1c2SSimon Glass                                'all' : int(values[0]) + int(values[1]) +
644fc3fe1c2SSimon Glass                                        int(values[2]),
645fc3fe1c2SSimon Glass                                'text' : int(values[0]) - rodata,
646fc3fe1c2SSimon Glass                                'data' : int(values[1]),
647fc3fe1c2SSimon Glass                                'bss' : int(values[2]),
648fc3fe1c2SSimon Glass                                'rodata' : rodata,
649fc3fe1c2SSimon Glass                            }
650fc3fe1c2SSimon Glass                            sizes[values[5]] = size_dict
651fc3fe1c2SSimon Glass
652fc3fe1c2SSimon Glass            if read_func_sizes:
653fc3fe1c2SSimon Glass                pattern = self.GetFuncSizesFile(commit_upto, target, '*')
654fc3fe1c2SSimon Glass                for fname in glob.glob(pattern):
655fc3fe1c2SSimon Glass                    with open(fname, 'r') as fd:
656fc3fe1c2SSimon Glass                        dict_name = os.path.basename(fname).replace('.sizes',
657fc3fe1c2SSimon Glass                                                                    '')
658fc3fe1c2SSimon Glass                        func_sizes[dict_name] = self.ReadFuncSizes(fname, fd)
659fc3fe1c2SSimon Glass
660843312dcSSimon Glass            if read_config:
661843312dcSSimon Glass                output_dir = self.GetBuildDir(commit_upto, target)
662843312dcSSimon Glass                for name in CONFIG_FILENAMES:
663843312dcSSimon Glass                    fname = os.path.join(output_dir, name)
664843312dcSSimon Glass                    config[name] = self._ProcessConfig(fname)
665fc3fe1c2SSimon Glass
666843312dcSSimon Glass            return Builder.Outcome(rc, err_lines, sizes, func_sizes, config)
667fc3fe1c2SSimon Glass
668843312dcSSimon Glass        return Builder.Outcome(OUTCOME_UNKNOWN, [], {}, {}, {})
669843312dcSSimon Glass
670843312dcSSimon Glass    def GetResultSummary(self, boards_selected, commit_upto, read_func_sizes,
671843312dcSSimon Glass                         read_config):
672fc3fe1c2SSimon Glass        """Calculate a summary of the results of building a commit.
673fc3fe1c2SSimon Glass
674fc3fe1c2SSimon Glass        Args:
675fc3fe1c2SSimon Glass            board_selected: Dict containing boards to summarise
676fc3fe1c2SSimon Glass            commit_upto: Commit number to summarize (0..self.count-1)
677fc3fe1c2SSimon Glass            read_func_sizes: True to read function size information
678843312dcSSimon Glass            read_config: True to read .config and autoconf.h files
679fc3fe1c2SSimon Glass
680fc3fe1c2SSimon Glass        Returns:
681fc3fe1c2SSimon Glass            Tuple:
682fc3fe1c2SSimon Glass                Dict containing boards which passed building this commit.
683fc3fe1c2SSimon Glass                    keyed by board.target
684e30965dbSSimon Glass                List containing a summary of error lines
685ed966657SSimon Glass                Dict keyed by error line, containing a list of the Board
686ed966657SSimon Glass                    objects with that error
687e30965dbSSimon Glass                List containing a summary of warning lines
688e30965dbSSimon Glass                Dict keyed by error line, containing a list of the Board
689e30965dbSSimon Glass                    objects with that warning
6908270e3c1SSimon Glass                Dictionary keyed by board.target. Each value is a dictionary:
6918270e3c1SSimon Glass                    key: filename - e.g. '.config'
692843312dcSSimon Glass                    value is itself a dictionary:
693843312dcSSimon Glass                        key: config name
694843312dcSSimon Glass                        value: config value
695fc3fe1c2SSimon Glass        """
696e30965dbSSimon Glass        def AddLine(lines_summary, lines_boards, line, board):
697e30965dbSSimon Glass            line = line.rstrip()
698e30965dbSSimon Glass            if line in lines_boards:
699e30965dbSSimon Glass                lines_boards[line].append(board)
700e30965dbSSimon Glass            else:
701e30965dbSSimon Glass                lines_boards[line] = [board]
702e30965dbSSimon Glass                lines_summary.append(line)
703e30965dbSSimon Glass
704fc3fe1c2SSimon Glass        board_dict = {}
705fc3fe1c2SSimon Glass        err_lines_summary = []
706ed966657SSimon Glass        err_lines_boards = {}
707e30965dbSSimon Glass        warn_lines_summary = []
708e30965dbSSimon Glass        warn_lines_boards = {}
709843312dcSSimon Glass        config = {}
710fc3fe1c2SSimon Glass
711fc3fe1c2SSimon Glass        for board in boards_selected.itervalues():
712fc3fe1c2SSimon Glass            outcome = self.GetBuildOutcome(commit_upto, board.target,
713843312dcSSimon Glass                                           read_func_sizes, read_config)
714fc3fe1c2SSimon Glass            board_dict[board.target] = outcome
715e30965dbSSimon Glass            last_func = None
716e30965dbSSimon Glass            last_was_warning = False
717e30965dbSSimon Glass            for line in outcome.err_lines:
718e30965dbSSimon Glass                if line:
719e30965dbSSimon Glass                    if (self._re_function.match(line) or
720e30965dbSSimon Glass                            self._re_files.match(line)):
721e30965dbSSimon Glass                        last_func = line
722ed966657SSimon Glass                    else:
723e30965dbSSimon Glass                        is_warning = self._re_warning.match(line)
724e30965dbSSimon Glass                        is_note = self._re_note.match(line)
725e30965dbSSimon Glass                        if is_warning or (last_was_warning and is_note):
726e30965dbSSimon Glass                            if last_func:
727e30965dbSSimon Glass                                AddLine(warn_lines_summary, warn_lines_boards,
728e30965dbSSimon Glass                                        last_func, board)
729e30965dbSSimon Glass                            AddLine(warn_lines_summary, warn_lines_boards,
730e30965dbSSimon Glass                                    line, board)
731e30965dbSSimon Glass                        else:
732e30965dbSSimon Glass                            if last_func:
733e30965dbSSimon Glass                                AddLine(err_lines_summary, err_lines_boards,
734e30965dbSSimon Glass                                        last_func, board)
735e30965dbSSimon Glass                            AddLine(err_lines_summary, err_lines_boards,
736e30965dbSSimon Glass                                    line, board)
737e30965dbSSimon Glass                        last_was_warning = is_warning
738e30965dbSSimon Glass                        last_func = None
7398270e3c1SSimon Glass            tconfig = Config(board.target)
740843312dcSSimon Glass            for fname in CONFIG_FILENAMES:
741843312dcSSimon Glass                if outcome.config:
742843312dcSSimon Glass                    for key, value in outcome.config[fname].iteritems():
7438270e3c1SSimon Glass                        tconfig.Add(fname, key, value)
7448270e3c1SSimon Glass            config[board.target] = tconfig
745843312dcSSimon Glass
746e30965dbSSimon Glass        return (board_dict, err_lines_summary, err_lines_boards,
747843312dcSSimon Glass                warn_lines_summary, warn_lines_boards, config)
748fc3fe1c2SSimon Glass
749fc3fe1c2SSimon Glass    def AddOutcome(self, board_dict, arch_list, changes, char, color):
750fc3fe1c2SSimon Glass        """Add an output to our list of outcomes for each architecture
751fc3fe1c2SSimon Glass
752fc3fe1c2SSimon Glass        This simple function adds failing boards (changes) to the
753fc3fe1c2SSimon Glass        relevant architecture string, so we can print the results out
754fc3fe1c2SSimon Glass        sorted by architecture.
755fc3fe1c2SSimon Glass
756fc3fe1c2SSimon Glass        Args:
757fc3fe1c2SSimon Glass             board_dict: Dict containing all boards
758fc3fe1c2SSimon Glass             arch_list: Dict keyed by arch name. Value is a string containing
759fc3fe1c2SSimon Glass                    a list of board names which failed for that arch.
760fc3fe1c2SSimon Glass             changes: List of boards to add to arch_list
761fc3fe1c2SSimon Glass             color: terminal.Colour object
762fc3fe1c2SSimon Glass        """
763fc3fe1c2SSimon Glass        done_arch = {}
764fc3fe1c2SSimon Glass        for target in changes:
765fc3fe1c2SSimon Glass            if target in board_dict:
766fc3fe1c2SSimon Glass                arch = board_dict[target].arch
767fc3fe1c2SSimon Glass            else:
768fc3fe1c2SSimon Glass                arch = 'unknown'
769fc3fe1c2SSimon Glass            str = self.col.Color(color, ' ' + target)
770fc3fe1c2SSimon Glass            if not arch in done_arch:
77163c619eeSSimon Glass                str = ' %s  %s' % (self.col.Color(color, char), str)
772fc3fe1c2SSimon Glass                done_arch[arch] = True
773fc3fe1c2SSimon Glass            if not arch in arch_list:
774fc3fe1c2SSimon Glass                arch_list[arch] = str
775fc3fe1c2SSimon Glass            else:
776fc3fe1c2SSimon Glass                arch_list[arch] += str
777fc3fe1c2SSimon Glass
778fc3fe1c2SSimon Glass
779fc3fe1c2SSimon Glass    def ColourNum(self, num):
780fc3fe1c2SSimon Glass        color = self.col.RED if num > 0 else self.col.GREEN
781fc3fe1c2SSimon Glass        if num == 0:
782fc3fe1c2SSimon Glass            return '0'
783fc3fe1c2SSimon Glass        return self.col.Color(color, str(num))
784fc3fe1c2SSimon Glass
785fc3fe1c2SSimon Glass    def ResetResultSummary(self, board_selected):
786fc3fe1c2SSimon Glass        """Reset the results summary ready for use.
787fc3fe1c2SSimon Glass
788fc3fe1c2SSimon Glass        Set up the base board list to be all those selected, and set the
789fc3fe1c2SSimon Glass        error lines to empty.
790fc3fe1c2SSimon Glass
791fc3fe1c2SSimon Glass        Following this, calls to PrintResultSummary() will use this
792fc3fe1c2SSimon Glass        information to work out what has changed.
793fc3fe1c2SSimon Glass
794fc3fe1c2SSimon Glass        Args:
795fc3fe1c2SSimon Glass            board_selected: Dict containing boards to summarise, keyed by
796fc3fe1c2SSimon Glass                board.target
797fc3fe1c2SSimon Glass        """
798fc3fe1c2SSimon Glass        self._base_board_dict = {}
799fc3fe1c2SSimon Glass        for board in board_selected:
800843312dcSSimon Glass            self._base_board_dict[board] = Builder.Outcome(0, [], [], {}, {})
801fc3fe1c2SSimon Glass        self._base_err_lines = []
802e30965dbSSimon Glass        self._base_warn_lines = []
803e30965dbSSimon Glass        self._base_err_line_boards = {}
804e30965dbSSimon Glass        self._base_warn_line_boards = {}
8058270e3c1SSimon Glass        self._base_config = None
806fc3fe1c2SSimon Glass
807fc3fe1c2SSimon Glass    def PrintFuncSizeDetail(self, fname, old, new):
808fc3fe1c2SSimon Glass        grow, shrink, add, remove, up, down = 0, 0, 0, 0, 0, 0
809fc3fe1c2SSimon Glass        delta, common = [], {}
810fc3fe1c2SSimon Glass
811fc3fe1c2SSimon Glass        for a in old:
812fc3fe1c2SSimon Glass            if a in new:
813fc3fe1c2SSimon Glass                common[a] = 1
814fc3fe1c2SSimon Glass
815fc3fe1c2SSimon Glass        for name in old:
816fc3fe1c2SSimon Glass            if name not in common:
817fc3fe1c2SSimon Glass                remove += 1
818fc3fe1c2SSimon Glass                down += old[name]
819fc3fe1c2SSimon Glass                delta.append([-old[name], name])
820fc3fe1c2SSimon Glass
821fc3fe1c2SSimon Glass        for name in new:
822fc3fe1c2SSimon Glass            if name not in common:
823fc3fe1c2SSimon Glass                add += 1
824fc3fe1c2SSimon Glass                up += new[name]
825fc3fe1c2SSimon Glass                delta.append([new[name], name])
826fc3fe1c2SSimon Glass
827fc3fe1c2SSimon Glass        for name in common:
828fc3fe1c2SSimon Glass                diff = new.get(name, 0) - old.get(name, 0)
829fc3fe1c2SSimon Glass                if diff > 0:
830fc3fe1c2SSimon Glass                    grow, up = grow + 1, up + diff
831fc3fe1c2SSimon Glass                elif diff < 0:
832fc3fe1c2SSimon Glass                    shrink, down = shrink + 1, down - diff
833fc3fe1c2SSimon Glass                delta.append([diff, name])
834fc3fe1c2SSimon Glass
835fc3fe1c2SSimon Glass        delta.sort()
836fc3fe1c2SSimon Glass        delta.reverse()
837fc3fe1c2SSimon Glass
838fc3fe1c2SSimon Glass        args = [add, -remove, grow, -shrink, up, -down, up - down]
839fc3fe1c2SSimon Glass        if max(args) == 0:
840fc3fe1c2SSimon Glass            return
841fc3fe1c2SSimon Glass        args = [self.ColourNum(x) for x in args]
842fc3fe1c2SSimon Glass        indent = ' ' * 15
8434653a882SSimon Glass        Print('%s%s: add: %s/%s, grow: %s/%s bytes: %s/%s (%s)' %
844fc3fe1c2SSimon Glass              tuple([indent, self.col.Color(self.col.YELLOW, fname)] + args))
8454653a882SSimon Glass        Print('%s  %-38s %7s %7s %+7s' % (indent, 'function', 'old', 'new',
8464653a882SSimon Glass                                         'delta'))
847fc3fe1c2SSimon Glass        for diff, name in delta:
848fc3fe1c2SSimon Glass            if diff:
849fc3fe1c2SSimon Glass                color = self.col.RED if diff > 0 else self.col.GREEN
850fc3fe1c2SSimon Glass                msg = '%s  %-38s %7s %7s %+7d' % (indent, name,
851fc3fe1c2SSimon Glass                        old.get(name, '-'), new.get(name,'-'), diff)
8524653a882SSimon Glass                Print(msg, colour=color)
853fc3fe1c2SSimon Glass
854fc3fe1c2SSimon Glass
855fc3fe1c2SSimon Glass    def PrintSizeDetail(self, target_list, show_bloat):
856fc3fe1c2SSimon Glass        """Show details size information for each board
857fc3fe1c2SSimon Glass
858fc3fe1c2SSimon Glass        Args:
859fc3fe1c2SSimon Glass            target_list: List of targets, each a dict containing:
860fc3fe1c2SSimon Glass                    'target': Target name
861fc3fe1c2SSimon Glass                    'total_diff': Total difference in bytes across all areas
862fc3fe1c2SSimon Glass                    <part_name>: Difference for that part
863fc3fe1c2SSimon Glass            show_bloat: Show detail for each function
864fc3fe1c2SSimon Glass        """
865fc3fe1c2SSimon Glass        targets_by_diff = sorted(target_list, reverse=True,
866fc3fe1c2SSimon Glass        key=lambda x: x['_total_diff'])
867fc3fe1c2SSimon Glass        for result in targets_by_diff:
868fc3fe1c2SSimon Glass            printed_target = False
869fc3fe1c2SSimon Glass            for name in sorted(result):
870fc3fe1c2SSimon Glass                diff = result[name]
871fc3fe1c2SSimon Glass                if name.startswith('_'):
872fc3fe1c2SSimon Glass                    continue
873fc3fe1c2SSimon Glass                if diff != 0:
874fc3fe1c2SSimon Glass                    color = self.col.RED if diff > 0 else self.col.GREEN
875fc3fe1c2SSimon Glass                msg = ' %s %+d' % (name, diff)
876fc3fe1c2SSimon Glass                if not printed_target:
8774653a882SSimon Glass                    Print('%10s  %-15s:' % ('', result['_target']),
8784653a882SSimon Glass                          newline=False)
879fc3fe1c2SSimon Glass                    printed_target = True
8804653a882SSimon Glass                Print(msg, colour=color, newline=False)
881fc3fe1c2SSimon Glass            if printed_target:
8824653a882SSimon Glass                Print()
883fc3fe1c2SSimon Glass                if show_bloat:
884fc3fe1c2SSimon Glass                    target = result['_target']
885fc3fe1c2SSimon Glass                    outcome = result['_outcome']
886fc3fe1c2SSimon Glass                    base_outcome = self._base_board_dict[target]
887fc3fe1c2SSimon Glass                    for fname in outcome.func_sizes:
888fc3fe1c2SSimon Glass                        self.PrintFuncSizeDetail(fname,
889fc3fe1c2SSimon Glass                                                 base_outcome.func_sizes[fname],
890fc3fe1c2SSimon Glass                                                 outcome.func_sizes[fname])
891fc3fe1c2SSimon Glass
892fc3fe1c2SSimon Glass
893fc3fe1c2SSimon Glass    def PrintSizeSummary(self, board_selected, board_dict, show_detail,
894fc3fe1c2SSimon Glass                         show_bloat):
895fc3fe1c2SSimon Glass        """Print a summary of image sizes broken down by section.
896fc3fe1c2SSimon Glass
897fc3fe1c2SSimon Glass        The summary takes the form of one line per architecture. The
898fc3fe1c2SSimon Glass        line contains deltas for each of the sections (+ means the section
899fc3fe1c2SSimon Glass        got bigger, - means smaller). The nunmbers are the average number
900fc3fe1c2SSimon Glass        of bytes that a board in this section increased by.
901fc3fe1c2SSimon Glass
902fc3fe1c2SSimon Glass        For example:
903fc3fe1c2SSimon Glass           powerpc: (622 boards)   text -0.0
904fc3fe1c2SSimon Glass          arm: (285 boards)   text -0.0
905fc3fe1c2SSimon Glass          nds32: (3 boards)   text -8.0
906fc3fe1c2SSimon Glass
907fc3fe1c2SSimon Glass        Args:
908fc3fe1c2SSimon Glass            board_selected: Dict containing boards to summarise, keyed by
909fc3fe1c2SSimon Glass                board.target
910fc3fe1c2SSimon Glass            board_dict: Dict containing boards for which we built this
911fc3fe1c2SSimon Glass                commit, keyed by board.target. The value is an Outcome object.
912fc3fe1c2SSimon Glass            show_detail: Show detail for each board
913fc3fe1c2SSimon Glass            show_bloat: Show detail for each function
914fc3fe1c2SSimon Glass        """
915fc3fe1c2SSimon Glass        arch_list = {}
916fc3fe1c2SSimon Glass        arch_count = {}
917fc3fe1c2SSimon Glass
918fc3fe1c2SSimon Glass        # Calculate changes in size for different image parts
919fc3fe1c2SSimon Glass        # The previous sizes are in Board.sizes, for each board
920fc3fe1c2SSimon Glass        for target in board_dict:
921fc3fe1c2SSimon Glass            if target not in board_selected:
922fc3fe1c2SSimon Glass                continue
923fc3fe1c2SSimon Glass            base_sizes = self._base_board_dict[target].sizes
924fc3fe1c2SSimon Glass            outcome = board_dict[target]
925fc3fe1c2SSimon Glass            sizes = outcome.sizes
926fc3fe1c2SSimon Glass
927fc3fe1c2SSimon Glass            # Loop through the list of images, creating a dict of size
928fc3fe1c2SSimon Glass            # changes for each image/part. We end up with something like
929fc3fe1c2SSimon Glass            # {'target' : 'snapper9g45, 'data' : 5, 'u-boot-spl:text' : -4}
930fc3fe1c2SSimon Glass            # which means that U-Boot data increased by 5 bytes and SPL
931fc3fe1c2SSimon Glass            # text decreased by 4.
932fc3fe1c2SSimon Glass            err = {'_target' : target}
933fc3fe1c2SSimon Glass            for image in sizes:
934fc3fe1c2SSimon Glass                if image in base_sizes:
935fc3fe1c2SSimon Glass                    base_image = base_sizes[image]
936fc3fe1c2SSimon Glass                    # Loop through the text, data, bss parts
937fc3fe1c2SSimon Glass                    for part in sorted(sizes[image]):
938fc3fe1c2SSimon Glass                        diff = sizes[image][part] - base_image[part]
939fc3fe1c2SSimon Glass                        col = None
940fc3fe1c2SSimon Glass                        if diff:
941fc3fe1c2SSimon Glass                            if image == 'u-boot':
942fc3fe1c2SSimon Glass                                name = part
943fc3fe1c2SSimon Glass                            else:
944fc3fe1c2SSimon Glass                                name = image + ':' + part
945fc3fe1c2SSimon Glass                            err[name] = diff
946fc3fe1c2SSimon Glass            arch = board_selected[target].arch
947fc3fe1c2SSimon Glass            if not arch in arch_count:
948fc3fe1c2SSimon Glass                arch_count[arch] = 1
949fc3fe1c2SSimon Glass            else:
950fc3fe1c2SSimon Glass                arch_count[arch] += 1
951fc3fe1c2SSimon Glass            if not sizes:
952fc3fe1c2SSimon Glass                pass    # Only add to our list when we have some stats
953fc3fe1c2SSimon Glass            elif not arch in arch_list:
954fc3fe1c2SSimon Glass                arch_list[arch] = [err]
955fc3fe1c2SSimon Glass            else:
956fc3fe1c2SSimon Glass                arch_list[arch].append(err)
957fc3fe1c2SSimon Glass
958fc3fe1c2SSimon Glass        # We now have a list of image size changes sorted by arch
959fc3fe1c2SSimon Glass        # Print out a summary of these
960fc3fe1c2SSimon Glass        for arch, target_list in arch_list.iteritems():
961fc3fe1c2SSimon Glass            # Get total difference for each type
962fc3fe1c2SSimon Glass            totals = {}
963fc3fe1c2SSimon Glass            for result in target_list:
964fc3fe1c2SSimon Glass                total = 0
965fc3fe1c2SSimon Glass                for name, diff in result.iteritems():
966fc3fe1c2SSimon Glass                    if name.startswith('_'):
967fc3fe1c2SSimon Glass                        continue
968fc3fe1c2SSimon Glass                    total += diff
969fc3fe1c2SSimon Glass                    if name in totals:
970fc3fe1c2SSimon Glass                        totals[name] += diff
971fc3fe1c2SSimon Glass                    else:
972fc3fe1c2SSimon Glass                        totals[name] = diff
973fc3fe1c2SSimon Glass                result['_total_diff'] = total
974fc3fe1c2SSimon Glass                result['_outcome'] = board_dict[result['_target']]
975fc3fe1c2SSimon Glass
976fc3fe1c2SSimon Glass            count = len(target_list)
977fc3fe1c2SSimon Glass            printed_arch = False
978fc3fe1c2SSimon Glass            for name in sorted(totals):
979fc3fe1c2SSimon Glass                diff = totals[name]
980fc3fe1c2SSimon Glass                if diff:
981fc3fe1c2SSimon Glass                    # Display the average difference in this name for this
982fc3fe1c2SSimon Glass                    # architecture
983fc3fe1c2SSimon Glass                    avg_diff = float(diff) / count
984fc3fe1c2SSimon Glass                    color = self.col.RED if avg_diff > 0 else self.col.GREEN
985fc3fe1c2SSimon Glass                    msg = ' %s %+1.1f' % (name, avg_diff)
986fc3fe1c2SSimon Glass                    if not printed_arch:
9874653a882SSimon Glass                        Print('%10s: (for %d/%d boards)' % (arch, count,
9884653a882SSimon Glass                              arch_count[arch]), newline=False)
989fc3fe1c2SSimon Glass                        printed_arch = True
9904653a882SSimon Glass                    Print(msg, colour=color, newline=False)
991fc3fe1c2SSimon Glass
992fc3fe1c2SSimon Glass            if printed_arch:
9934653a882SSimon Glass                Print()
994fc3fe1c2SSimon Glass                if show_detail:
995fc3fe1c2SSimon Glass                    self.PrintSizeDetail(target_list, show_bloat)
996fc3fe1c2SSimon Glass
997fc3fe1c2SSimon Glass
998fc3fe1c2SSimon Glass    def PrintResultSummary(self, board_selected, board_dict, err_lines,
999e30965dbSSimon Glass                           err_line_boards, warn_lines, warn_line_boards,
1000843312dcSSimon Glass                           config, show_sizes, show_detail, show_bloat,
1001843312dcSSimon Glass                           show_config):
1002fc3fe1c2SSimon Glass        """Compare results with the base results and display delta.
1003fc3fe1c2SSimon Glass
1004fc3fe1c2SSimon Glass        Only boards mentioned in board_selected will be considered. This
1005fc3fe1c2SSimon Glass        function is intended to be called repeatedly with the results of
1006fc3fe1c2SSimon Glass        each commit. It therefore shows a 'diff' between what it saw in
1007fc3fe1c2SSimon Glass        the last call and what it sees now.
1008fc3fe1c2SSimon Glass
1009fc3fe1c2SSimon Glass        Args:
1010fc3fe1c2SSimon Glass            board_selected: Dict containing boards to summarise, keyed by
1011fc3fe1c2SSimon Glass                board.target
1012fc3fe1c2SSimon Glass            board_dict: Dict containing boards for which we built this
1013fc3fe1c2SSimon Glass                commit, keyed by board.target. The value is an Outcome object.
1014fc3fe1c2SSimon Glass            err_lines: A list of errors for this commit, or [] if there is
1015fc3fe1c2SSimon Glass                none, or we don't want to print errors
1016ed966657SSimon Glass            err_line_boards: Dict keyed by error line, containing a list of
1017ed966657SSimon Glass                the Board objects with that error
1018e30965dbSSimon Glass            warn_lines: A list of warnings for this commit, or [] if there is
1019e30965dbSSimon Glass                none, or we don't want to print errors
1020e30965dbSSimon Glass            warn_line_boards: Dict keyed by warning line, containing a list of
1021e30965dbSSimon Glass                the Board objects with that warning
1022843312dcSSimon Glass            config: Dictionary keyed by filename - e.g. '.config'. Each
1023843312dcSSimon Glass                    value is itself a dictionary:
1024843312dcSSimon Glass                        key: config name
1025843312dcSSimon Glass                        value: config value
1026fc3fe1c2SSimon Glass            show_sizes: Show image size deltas
1027fc3fe1c2SSimon Glass            show_detail: Show detail for each board
1028fc3fe1c2SSimon Glass            show_bloat: Show detail for each function
1029843312dcSSimon Glass            show_config: Show config changes
1030fc3fe1c2SSimon Glass        """
1031e30965dbSSimon Glass        def _BoardList(line, line_boards):
1032ed966657SSimon Glass            """Helper function to get a line of boards containing a line
1033ed966657SSimon Glass
1034ed966657SSimon Glass            Args:
1035ed966657SSimon Glass                line: Error line to search for
1036ed966657SSimon Glass            Return:
1037ed966657SSimon Glass                String containing a list of boards with that error line, or
1038ed966657SSimon Glass                '' if the user has not requested such a list
1039ed966657SSimon Glass            """
1040ed966657SSimon Glass            if self._list_error_boards:
1041ed966657SSimon Glass                names = []
1042e30965dbSSimon Glass                for board in line_boards[line]:
1043f66153beSSimon Glass                    if not board.target in names:
1044ed966657SSimon Glass                        names.append(board.target)
1045ed966657SSimon Glass                names_str = '(%s) ' % ','.join(names)
1046ed966657SSimon Glass            else:
1047ed966657SSimon Glass                names_str = ''
1048ed966657SSimon Glass            return names_str
1049ed966657SSimon Glass
1050e30965dbSSimon Glass        def _CalcErrorDelta(base_lines, base_line_boards, lines, line_boards,
1051e30965dbSSimon Glass                            char):
1052e30965dbSSimon Glass            better_lines = []
1053e30965dbSSimon Glass            worse_lines = []
1054e30965dbSSimon Glass            for line in lines:
1055e30965dbSSimon Glass                if line not in base_lines:
1056e30965dbSSimon Glass                    worse_lines.append(char + '+' +
1057e30965dbSSimon Glass                            _BoardList(line, line_boards) + line)
1058e30965dbSSimon Glass            for line in base_lines:
1059e30965dbSSimon Glass                if line not in lines:
1060e30965dbSSimon Glass                    better_lines.append(char + '-' +
1061e30965dbSSimon Glass                            _BoardList(line, base_line_boards) + line)
1062e30965dbSSimon Glass            return better_lines, worse_lines
1063e30965dbSSimon Glass
1064843312dcSSimon Glass        def _CalcConfig(delta, name, config):
1065843312dcSSimon Glass            """Calculate configuration changes
1066843312dcSSimon Glass
1067843312dcSSimon Glass            Args:
1068843312dcSSimon Glass                delta: Type of the delta, e.g. '+'
1069843312dcSSimon Glass                name: name of the file which changed (e.g. .config)
1070843312dcSSimon Glass                config: configuration change dictionary
1071843312dcSSimon Glass                    key: config name
1072843312dcSSimon Glass                    value: config value
1073843312dcSSimon Glass            Returns:
1074843312dcSSimon Glass                String containing the configuration changes which can be
1075843312dcSSimon Glass                    printed
1076843312dcSSimon Glass            """
1077843312dcSSimon Glass            out = ''
1078843312dcSSimon Glass            for key in sorted(config.keys()):
1079843312dcSSimon Glass                out += '%s=%s ' % (key, config[key])
10808270e3c1SSimon Glass            return '%s %s: %s' % (delta, name, out)
1081843312dcSSimon Glass
10828270e3c1SSimon Glass        def _AddConfig(lines, name, config_plus, config_minus, config_change):
10838270e3c1SSimon Glass            """Add changes in configuration to a list
1084843312dcSSimon Glass
1085843312dcSSimon Glass            Args:
10868270e3c1SSimon Glass                lines: list to add to
10878270e3c1SSimon Glass                name: config file name
1088843312dcSSimon Glass                config_plus: configurations added, dictionary
1089843312dcSSimon Glass                    key: config name
1090843312dcSSimon Glass                    value: config value
1091843312dcSSimon Glass                config_minus: configurations removed, dictionary
1092843312dcSSimon Glass                    key: config name
1093843312dcSSimon Glass                    value: config value
1094843312dcSSimon Glass                config_change: configurations changed, dictionary
1095843312dcSSimon Glass                    key: config name
1096843312dcSSimon Glass                    value: config value
1097843312dcSSimon Glass            """
1098843312dcSSimon Glass            if config_plus:
10998270e3c1SSimon Glass                lines.append(_CalcConfig('+', name, config_plus))
1100843312dcSSimon Glass            if config_minus:
11018270e3c1SSimon Glass                lines.append(_CalcConfig('-', name, config_minus))
1102843312dcSSimon Glass            if config_change:
11038270e3c1SSimon Glass                lines.append(_CalcConfig('c', name, config_change))
11048270e3c1SSimon Glass
11058270e3c1SSimon Glass        def _OutputConfigInfo(lines):
11068270e3c1SSimon Glass            for line in lines:
11078270e3c1SSimon Glass                if not line:
11088270e3c1SSimon Glass                    continue
11098270e3c1SSimon Glass                if line[0] == '+':
11108270e3c1SSimon Glass                    col = self.col.GREEN
11118270e3c1SSimon Glass                elif line[0] == '-':
11128270e3c1SSimon Glass                    col = self.col.RED
11138270e3c1SSimon Glass                elif line[0] == 'c':
11148270e3c1SSimon Glass                    col = self.col.YELLOW
11158270e3c1SSimon Glass                Print('   ' + line, newline=True, colour=col)
11168270e3c1SSimon Glass
1117843312dcSSimon Glass
1118fc3fe1c2SSimon Glass        better = []     # List of boards fixed since last commit
1119fc3fe1c2SSimon Glass        worse = []      # List of new broken boards since last commit
1120fc3fe1c2SSimon Glass        new = []        # List of boards that didn't exist last time
1121fc3fe1c2SSimon Glass        unknown = []    # List of boards that were not built
1122fc3fe1c2SSimon Glass
1123fc3fe1c2SSimon Glass        for target in board_dict:
1124fc3fe1c2SSimon Glass            if target not in board_selected:
1125fc3fe1c2SSimon Glass                continue
1126fc3fe1c2SSimon Glass
1127fc3fe1c2SSimon Glass            # If the board was built last time, add its outcome to a list
1128fc3fe1c2SSimon Glass            if target in self._base_board_dict:
1129fc3fe1c2SSimon Glass                base_outcome = self._base_board_dict[target].rc
1130fc3fe1c2SSimon Glass                outcome = board_dict[target]
1131fc3fe1c2SSimon Glass                if outcome.rc == OUTCOME_UNKNOWN:
1132fc3fe1c2SSimon Glass                    unknown.append(target)
1133fc3fe1c2SSimon Glass                elif outcome.rc < base_outcome:
1134fc3fe1c2SSimon Glass                    better.append(target)
1135fc3fe1c2SSimon Glass                elif outcome.rc > base_outcome:
1136fc3fe1c2SSimon Glass                    worse.append(target)
1137fc3fe1c2SSimon Glass            else:
1138fc3fe1c2SSimon Glass                new.append(target)
1139fc3fe1c2SSimon Glass
1140fc3fe1c2SSimon Glass        # Get a list of errors that have appeared, and disappeared
1141e30965dbSSimon Glass        better_err, worse_err = _CalcErrorDelta(self._base_err_lines,
1142e30965dbSSimon Glass                self._base_err_line_boards, err_lines, err_line_boards, '')
1143e30965dbSSimon Glass        better_warn, worse_warn = _CalcErrorDelta(self._base_warn_lines,
1144e30965dbSSimon Glass                self._base_warn_line_boards, warn_lines, warn_line_boards, 'w')
1145fc3fe1c2SSimon Glass
1146fc3fe1c2SSimon Glass        # Display results by arch
1147e30965dbSSimon Glass        if (better or worse or unknown or new or worse_err or better_err
1148e30965dbSSimon Glass                or worse_warn or better_warn):
1149fc3fe1c2SSimon Glass            arch_list = {}
1150fc3fe1c2SSimon Glass            self.AddOutcome(board_selected, arch_list, better, '',
1151fc3fe1c2SSimon Glass                    self.col.GREEN)
1152fc3fe1c2SSimon Glass            self.AddOutcome(board_selected, arch_list, worse, '+',
1153fc3fe1c2SSimon Glass                    self.col.RED)
1154fc3fe1c2SSimon Glass            self.AddOutcome(board_selected, arch_list, new, '*', self.col.BLUE)
1155fc3fe1c2SSimon Glass            if self._show_unknown:
1156fc3fe1c2SSimon Glass                self.AddOutcome(board_selected, arch_list, unknown, '?',
1157fc3fe1c2SSimon Glass                        self.col.MAGENTA)
1158fc3fe1c2SSimon Glass            for arch, target_list in arch_list.iteritems():
11594653a882SSimon Glass                Print('%10s: %s' % (arch, target_list))
116028370c1bSSimon Glass                self._error_lines += 1
1161fc3fe1c2SSimon Glass            if better_err:
11624653a882SSimon Glass                Print('\n'.join(better_err), colour=self.col.GREEN)
116328370c1bSSimon Glass                self._error_lines += 1
1164fc3fe1c2SSimon Glass            if worse_err:
11654653a882SSimon Glass                Print('\n'.join(worse_err), colour=self.col.RED)
116628370c1bSSimon Glass                self._error_lines += 1
1167e30965dbSSimon Glass            if better_warn:
11684653a882SSimon Glass                Print('\n'.join(better_warn), colour=self.col.CYAN)
1169e30965dbSSimon Glass                self._error_lines += 1
1170e30965dbSSimon Glass            if worse_warn:
11714653a882SSimon Glass                Print('\n'.join(worse_warn), colour=self.col.MAGENTA)
1172e30965dbSSimon Glass                self._error_lines += 1
1173fc3fe1c2SSimon Glass
1174fc3fe1c2SSimon Glass        if show_sizes:
1175fc3fe1c2SSimon Glass            self.PrintSizeSummary(board_selected, board_dict, show_detail,
1176fc3fe1c2SSimon Glass                                  show_bloat)
1177fc3fe1c2SSimon Glass
11788270e3c1SSimon Glass        if show_config and self._base_config:
11798270e3c1SSimon Glass            summary = {}
11808270e3c1SSimon Glass            arch_config_plus = {}
11818270e3c1SSimon Glass            arch_config_minus = {}
11828270e3c1SSimon Glass            arch_config_change = {}
11838270e3c1SSimon Glass            arch_list = []
11848270e3c1SSimon Glass
11858270e3c1SSimon Glass            for target in board_dict:
11868270e3c1SSimon Glass                if target not in board_selected:
11878270e3c1SSimon Glass                    continue
11888270e3c1SSimon Glass                arch = board_selected[target].arch
11898270e3c1SSimon Glass                if arch not in arch_list:
11908270e3c1SSimon Glass                    arch_list.append(arch)
11918270e3c1SSimon Glass
11928270e3c1SSimon Glass            for arch in arch_list:
11938270e3c1SSimon Glass                arch_config_plus[arch] = {}
11948270e3c1SSimon Glass                arch_config_minus[arch] = {}
11958270e3c1SSimon Glass                arch_config_change[arch] = {}
11968270e3c1SSimon Glass                for name in CONFIG_FILENAMES:
11978270e3c1SSimon Glass                    arch_config_plus[arch][name] = {}
11988270e3c1SSimon Glass                    arch_config_minus[arch][name] = {}
11998270e3c1SSimon Glass                    arch_config_change[arch][name] = {}
12008270e3c1SSimon Glass
12018270e3c1SSimon Glass            for target in board_dict:
12028270e3c1SSimon Glass                if target not in board_selected:
12038270e3c1SSimon Glass                    continue
12048270e3c1SSimon Glass
12058270e3c1SSimon Glass                arch = board_selected[target].arch
12068270e3c1SSimon Glass
1207843312dcSSimon Glass                all_config_plus = {}
1208843312dcSSimon Glass                all_config_minus = {}
1209843312dcSSimon Glass                all_config_change = {}
12108270e3c1SSimon Glass                tbase = self._base_config[target]
12118270e3c1SSimon Glass                tconfig = config[target]
12128270e3c1SSimon Glass                lines = []
1213843312dcSSimon Glass                for name in CONFIG_FILENAMES:
12148270e3c1SSimon Glass                    if not tconfig.config[name]:
1215843312dcSSimon Glass                        continue
1216843312dcSSimon Glass                    config_plus = {}
1217843312dcSSimon Glass                    config_minus = {}
1218843312dcSSimon Glass                    config_change = {}
12198270e3c1SSimon Glass                    base = tbase.config[name]
12208270e3c1SSimon Glass                    for key, value in tconfig.config[name].iteritems():
1221843312dcSSimon Glass                        if key not in base:
1222843312dcSSimon Glass                            config_plus[key] = value
1223843312dcSSimon Glass                            all_config_plus[key] = value
1224843312dcSSimon Glass                    for key, value in base.iteritems():
12258270e3c1SSimon Glass                        if key not in tconfig.config[name]:
1226843312dcSSimon Glass                            config_minus[key] = value
1227843312dcSSimon Glass                            all_config_minus[key] = value
1228843312dcSSimon Glass                    for key, value in base.iteritems():
12298270e3c1SSimon Glass                        new_value = tconfig.config.get(key)
12308270e3c1SSimon Glass                        if new_value and value != new_value:
1231843312dcSSimon Glass                            desc = '%s -> %s' % (value, new_value)
1232843312dcSSimon Glass                            config_change[key] = desc
1233843312dcSSimon Glass                            all_config_change[key] = desc
12348270e3c1SSimon Glass
12358270e3c1SSimon Glass                    arch_config_plus[arch][name].update(config_plus)
12368270e3c1SSimon Glass                    arch_config_minus[arch][name].update(config_minus)
12378270e3c1SSimon Glass                    arch_config_change[arch][name].update(config_change)
12388270e3c1SSimon Glass
12398270e3c1SSimon Glass                    _AddConfig(lines, name, config_plus, config_minus,
12408270e3c1SSimon Glass                               config_change)
12418270e3c1SSimon Glass                _AddConfig(lines, 'all', all_config_plus, all_config_minus,
1242843312dcSSimon Glass                           all_config_change)
12438270e3c1SSimon Glass                summary[target] = '\n'.join(lines)
12448270e3c1SSimon Glass
12458270e3c1SSimon Glass            lines_by_target = {}
12468270e3c1SSimon Glass            for target, lines in summary.iteritems():
12478270e3c1SSimon Glass                if lines in lines_by_target:
12488270e3c1SSimon Glass                    lines_by_target[lines].append(target)
12498270e3c1SSimon Glass                else:
12508270e3c1SSimon Glass                    lines_by_target[lines] = [target]
12518270e3c1SSimon Glass
12528270e3c1SSimon Glass            for arch in arch_list:
12538270e3c1SSimon Glass                lines = []
12548270e3c1SSimon Glass                all_plus = {}
12558270e3c1SSimon Glass                all_minus = {}
12568270e3c1SSimon Glass                all_change = {}
12578270e3c1SSimon Glass                for name in CONFIG_FILENAMES:
12588270e3c1SSimon Glass                    all_plus.update(arch_config_plus[arch][name])
12598270e3c1SSimon Glass                    all_minus.update(arch_config_minus[arch][name])
12608270e3c1SSimon Glass                    all_change.update(arch_config_change[arch][name])
12618270e3c1SSimon Glass                    _AddConfig(lines, name, arch_config_plus[arch][name],
12628270e3c1SSimon Glass                               arch_config_minus[arch][name],
12638270e3c1SSimon Glass                               arch_config_change[arch][name])
12648270e3c1SSimon Glass                _AddConfig(lines, 'all', all_plus, all_minus, all_change)
12658270e3c1SSimon Glass                #arch_summary[target] = '\n'.join(lines)
12668270e3c1SSimon Glass                if lines:
12678270e3c1SSimon Glass                    Print('%s:' % arch)
12688270e3c1SSimon Glass                    _OutputConfigInfo(lines)
12698270e3c1SSimon Glass
12708270e3c1SSimon Glass            for lines, targets in lines_by_target.iteritems():
12718270e3c1SSimon Glass                if not lines:
12728270e3c1SSimon Glass                    continue
12738270e3c1SSimon Glass                Print('%s :' % ' '.join(sorted(targets)))
12748270e3c1SSimon Glass                _OutputConfigInfo(lines.split('\n'))
12758270e3c1SSimon Glass
1276843312dcSSimon Glass
1277fc3fe1c2SSimon Glass        # Save our updated information for the next call to this function
1278fc3fe1c2SSimon Glass        self._base_board_dict = board_dict
1279fc3fe1c2SSimon Glass        self._base_err_lines = err_lines
1280e30965dbSSimon Glass        self._base_warn_lines = warn_lines
1281e30965dbSSimon Glass        self._base_err_line_boards = err_line_boards
1282e30965dbSSimon Glass        self._base_warn_line_boards = warn_line_boards
1283843312dcSSimon Glass        self._base_config = config
1284fc3fe1c2SSimon Glass
1285fc3fe1c2SSimon Glass        # Get a list of boards that did not get built, if needed
1286fc3fe1c2SSimon Glass        not_built = []
1287fc3fe1c2SSimon Glass        for board in board_selected:
1288fc3fe1c2SSimon Glass            if not board in board_dict:
1289fc3fe1c2SSimon Glass                not_built.append(board)
1290fc3fe1c2SSimon Glass        if not_built:
12914653a882SSimon Glass            Print("Boards not built (%d): %s" % (len(not_built),
12924653a882SSimon Glass                  ', '.join(not_built)))
1293fc3fe1c2SSimon Glass
1294b2ea7ab2SSimon Glass    def ProduceResultSummary(self, commit_upto, commits, board_selected):
1295e30965dbSSimon Glass            (board_dict, err_lines, err_line_boards, warn_lines,
1296843312dcSSimon Glass                    warn_line_boards, config) = self.GetResultSummary(
1297ed966657SSimon Glass                    board_selected, commit_upto,
1298843312dcSSimon Glass                    read_func_sizes=self._show_bloat,
1299843312dcSSimon Glass                    read_config=self._show_config)
1300b2ea7ab2SSimon Glass            if commits:
1301b2ea7ab2SSimon Glass                msg = '%02d: %s' % (commit_upto + 1,
1302b2ea7ab2SSimon Glass                        commits[commit_upto].subject)
13034653a882SSimon Glass                Print(msg, colour=self.col.BLUE)
1304b2ea7ab2SSimon Glass            self.PrintResultSummary(board_selected, board_dict,
1305ed966657SSimon Glass                    err_lines if self._show_errors else [], err_line_boards,
1306e30965dbSSimon Glass                    warn_lines if self._show_errors else [], warn_line_boards,
1307843312dcSSimon Glass                    config, self._show_sizes, self._show_detail,
1308843312dcSSimon Glass                    self._show_bloat, self._show_config)
1309fc3fe1c2SSimon Glass
1310b2ea7ab2SSimon Glass    def ShowSummary(self, commits, board_selected):
1311fc3fe1c2SSimon Glass        """Show a build summary for U-Boot for a given board list.
1312fc3fe1c2SSimon Glass
1313fc3fe1c2SSimon Glass        Reset the result summary, then repeatedly call GetResultSummary on
1314fc3fe1c2SSimon Glass        each commit's results, then display the differences we see.
1315fc3fe1c2SSimon Glass
1316fc3fe1c2SSimon Glass        Args:
1317fc3fe1c2SSimon Glass            commit: Commit objects to summarise
1318fc3fe1c2SSimon Glass            board_selected: Dict containing boards to summarise
1319fc3fe1c2SSimon Glass        """
1320fea5858eSSimon Glass        self.commit_count = len(commits) if commits else 1
1321fc3fe1c2SSimon Glass        self.commits = commits
1322fc3fe1c2SSimon Glass        self.ResetResultSummary(board_selected)
132328370c1bSSimon Glass        self._error_lines = 0
1324fc3fe1c2SSimon Glass
1325fc3fe1c2SSimon Glass        for commit_upto in range(0, self.commit_count, self._step):
1326b2ea7ab2SSimon Glass            self.ProduceResultSummary(commit_upto, commits, board_selected)
132728370c1bSSimon Glass        if not self._error_lines:
13284653a882SSimon Glass            Print('(no errors to report)', colour=self.col.GREEN)
1329fc3fe1c2SSimon Glass
1330fc3fe1c2SSimon Glass
1331fc3fe1c2SSimon Glass    def SetupBuild(self, board_selected, commits):
1332fc3fe1c2SSimon Glass        """Set up ready to start a build.
1333fc3fe1c2SSimon Glass
1334fc3fe1c2SSimon Glass        Args:
1335fc3fe1c2SSimon Glass            board_selected: Selected boards to build
1336fc3fe1c2SSimon Glass            commits: Selected commits to build
1337fc3fe1c2SSimon Glass        """
1338fc3fe1c2SSimon Glass        # First work out how many commits we will build
1339fea5858eSSimon Glass        count = (self.commit_count + self._step - 1) / self._step
1340fc3fe1c2SSimon Glass        self.count = len(board_selected) * count
1341fc3fe1c2SSimon Glass        self.upto = self.warned = self.fail = 0
1342fc3fe1c2SSimon Glass        self._timestamps = collections.deque()
1343fc3fe1c2SSimon Glass
1344fc3fe1c2SSimon Glass    def GetThreadDir(self, thread_num):
1345fc3fe1c2SSimon Glass        """Get the directory path to the working dir for a thread.
1346fc3fe1c2SSimon Glass
1347fc3fe1c2SSimon Glass        Args:
1348fc3fe1c2SSimon Glass            thread_num: Number of thread to check.
1349fc3fe1c2SSimon Glass        """
1350fc3fe1c2SSimon Glass        return os.path.join(self._working_dir, '%02d' % thread_num)
1351fc3fe1c2SSimon Glass
1352fea5858eSSimon Glass    def _PrepareThread(self, thread_num, setup_git):
1353fc3fe1c2SSimon Glass        """Prepare the working directory for a thread.
1354fc3fe1c2SSimon Glass
1355fc3fe1c2SSimon Glass        This clones or fetches the repo into the thread's work directory.
1356fc3fe1c2SSimon Glass
1357fc3fe1c2SSimon Glass        Args:
1358fc3fe1c2SSimon Glass            thread_num: Thread number (0, 1, ...)
1359fea5858eSSimon Glass            setup_git: True to set up a git repo clone
1360fc3fe1c2SSimon Glass        """
1361fc3fe1c2SSimon Glass        thread_dir = self.GetThreadDir(thread_num)
1362190064b4SSimon Glass        builderthread.Mkdir(thread_dir)
1363fc3fe1c2SSimon Glass        git_dir = os.path.join(thread_dir, '.git')
1364fc3fe1c2SSimon Glass
1365fc3fe1c2SSimon Glass        # Clone the repo if it doesn't already exist
1366fc3fe1c2SSimon Glass        # TODO(sjg@chromium): Perhaps some git hackery to symlink instead, so
1367fc3fe1c2SSimon Glass        # we have a private index but uses the origin repo's contents?
1368fea5858eSSimon Glass        if setup_git and self.git_dir:
1369fc3fe1c2SSimon Glass            src_dir = os.path.abspath(self.git_dir)
1370fc3fe1c2SSimon Glass            if os.path.exists(git_dir):
1371fc3fe1c2SSimon Glass                gitutil.Fetch(git_dir, thread_dir)
1372fc3fe1c2SSimon Glass            else:
137321f0eb33SSimon Glass                Print('\rCloning repo for thread %d' % thread_num,
137421f0eb33SSimon Glass                      newline=False)
1375fc3fe1c2SSimon Glass                gitutil.Clone(src_dir, thread_dir)
137621f0eb33SSimon Glass                Print('\r%s\r' % (' ' * 30), newline=False)
1377fc3fe1c2SSimon Glass
1378fea5858eSSimon Glass    def _PrepareWorkingSpace(self, max_threads, setup_git):
1379fc3fe1c2SSimon Glass        """Prepare the working directory for use.
1380fc3fe1c2SSimon Glass
1381fc3fe1c2SSimon Glass        Set up the git repo for each thread.
1382fc3fe1c2SSimon Glass
1383fc3fe1c2SSimon Glass        Args:
1384fc3fe1c2SSimon Glass            max_threads: Maximum number of threads we expect to need.
1385fea5858eSSimon Glass            setup_git: True to set up a git repo clone
1386fc3fe1c2SSimon Glass        """
1387190064b4SSimon Glass        builderthread.Mkdir(self._working_dir)
1388fc3fe1c2SSimon Glass        for thread in range(max_threads):
1389fea5858eSSimon Glass            self._PrepareThread(thread, setup_git)
1390fc3fe1c2SSimon Glass
1391fc3fe1c2SSimon Glass    def _PrepareOutputSpace(self):
1392fc3fe1c2SSimon Glass        """Get the output directories ready to receive files.
1393fc3fe1c2SSimon Glass
1394fc3fe1c2SSimon Glass        We delete any output directories which look like ones we need to
1395fc3fe1c2SSimon Glass        create. Having left over directories is confusing when the user wants
1396fc3fe1c2SSimon Glass        to check the output manually.
1397fc3fe1c2SSimon Glass        """
13981a915675SSimon Glass        if not self.commits:
13991a915675SSimon Glass            return
1400fc3fe1c2SSimon Glass        dir_list = []
1401fc3fe1c2SSimon Glass        for commit_upto in range(self.commit_count):
1402fc3fe1c2SSimon Glass            dir_list.append(self._GetOutputDir(commit_upto))
1403fc3fe1c2SSimon Glass
1404b222abe7SSimon Glass        to_remove = []
1405fc3fe1c2SSimon Glass        for dirname in glob.glob(os.path.join(self.base_dir, '*')):
1406fc3fe1c2SSimon Glass            if dirname not in dir_list:
1407b222abe7SSimon Glass                to_remove.append(dirname)
1408b222abe7SSimon Glass        if to_remove:
1409b222abe7SSimon Glass            Print('Removing %d old build directories' % len(to_remove),
1410b222abe7SSimon Glass                  newline=False)
1411b222abe7SSimon Glass            for dirname in to_remove:
1412fc3fe1c2SSimon Glass                shutil.rmtree(dirname)
1413fc3fe1c2SSimon Glass
1414e5a0e5d8SSimon Glass    def BuildBoards(self, commits, board_selected, keep_outputs, verbose):
1415fc3fe1c2SSimon Glass        """Build all commits for a list of boards
1416fc3fe1c2SSimon Glass
1417fc3fe1c2SSimon Glass        Args:
1418fc3fe1c2SSimon Glass            commits: List of commits to be build, each a Commit object
1419fc3fe1c2SSimon Glass            boards_selected: Dict of selected boards, key is target name,
1420fc3fe1c2SSimon Glass                    value is Board object
1421fc3fe1c2SSimon Glass            keep_outputs: True to save build output files
1422e5a0e5d8SSimon Glass            verbose: Display build results as they are completed
14232c3deb97SSimon Glass        Returns:
14242c3deb97SSimon Glass            Tuple containing:
14252c3deb97SSimon Glass                - number of boards that failed to build
14262c3deb97SSimon Glass                - number of boards that issued warnings
1427fc3fe1c2SSimon Glass        """
1428fea5858eSSimon Glass        self.commit_count = len(commits) if commits else 1
1429fc3fe1c2SSimon Glass        self.commits = commits
1430e5a0e5d8SSimon Glass        self._verbose = verbose
1431fc3fe1c2SSimon Glass
1432fc3fe1c2SSimon Glass        self.ResetResultSummary(board_selected)
1433f3d015cbSThierry Reding        builderthread.Mkdir(self.base_dir, parents = True)
1434fea5858eSSimon Glass        self._PrepareWorkingSpace(min(self.num_threads, len(board_selected)),
1435fea5858eSSimon Glass                commits is not None)
1436fc3fe1c2SSimon Glass        self._PrepareOutputSpace()
1437745b395aSSimon Glass        Print('\rStarting build...', newline=False)
1438fc3fe1c2SSimon Glass        self.SetupBuild(board_selected, commits)
1439fc3fe1c2SSimon Glass        self.ProcessResult(None)
1440fc3fe1c2SSimon Glass
1441fc3fe1c2SSimon Glass        # Create jobs to build all commits for each board
1442fc3fe1c2SSimon Glass        for brd in board_selected.itervalues():
1443190064b4SSimon Glass            job = builderthread.BuilderJob()
1444fc3fe1c2SSimon Glass            job.board = brd
1445fc3fe1c2SSimon Glass            job.commits = commits
1446fc3fe1c2SSimon Glass            job.keep_outputs = keep_outputs
1447fc3fe1c2SSimon Glass            job.step = self._step
1448fc3fe1c2SSimon Glass            self.queue.put(job)
1449fc3fe1c2SSimon Glass
1450d436e381SSimon Glass        term = threading.Thread(target=self.queue.join)
1451d436e381SSimon Glass        term.setDaemon(True)
1452d436e381SSimon Glass        term.start()
1453d436e381SSimon Glass        while term.isAlive():
1454d436e381SSimon Glass            term.join(100)
1455fc3fe1c2SSimon Glass
1456fc3fe1c2SSimon Glass        # Wait until we have processed all output
1457fc3fe1c2SSimon Glass        self.out_queue.join()
14584653a882SSimon Glass        Print()
1459fc3fe1c2SSimon Glass        self.ClearLine(0)
14602c3deb97SSimon Glass        return (self.fail, self.warned)
1461