xref: /OK3568_Linux_fs/u-boot/tools/buildman/builderthread.py (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun# Copyright (c) 2014 Google, Inc
2*4882a593Smuzhiyun#
3*4882a593Smuzhiyun# SPDX-License-Identifier:      GPL-2.0+
4*4882a593Smuzhiyun#
5*4882a593Smuzhiyun
6*4882a593Smuzhiyunimport errno
7*4882a593Smuzhiyunimport glob
8*4882a593Smuzhiyunimport os
9*4882a593Smuzhiyunimport shutil
10*4882a593Smuzhiyunimport threading
11*4882a593Smuzhiyun
12*4882a593Smuzhiyunimport command
13*4882a593Smuzhiyunimport gitutil
14*4882a593Smuzhiyun
15*4882a593SmuzhiyunRETURN_CODE_RETRY = -1
16*4882a593Smuzhiyun
17*4882a593Smuzhiyundef Mkdir(dirname, parents = False):
18*4882a593Smuzhiyun    """Make a directory if it doesn't already exist.
19*4882a593Smuzhiyun
20*4882a593Smuzhiyun    Args:
21*4882a593Smuzhiyun        dirname: Directory to create
22*4882a593Smuzhiyun    """
23*4882a593Smuzhiyun    try:
24*4882a593Smuzhiyun        if parents:
25*4882a593Smuzhiyun            os.makedirs(dirname)
26*4882a593Smuzhiyun        else:
27*4882a593Smuzhiyun            os.mkdir(dirname)
28*4882a593Smuzhiyun    except OSError as err:
29*4882a593Smuzhiyun        if err.errno == errno.EEXIST:
30*4882a593Smuzhiyun            pass
31*4882a593Smuzhiyun        else:
32*4882a593Smuzhiyun            raise
33*4882a593Smuzhiyun
34*4882a593Smuzhiyunclass BuilderJob:
35*4882a593Smuzhiyun    """Holds information about a job to be performed by a thread
36*4882a593Smuzhiyun
37*4882a593Smuzhiyun    Members:
38*4882a593Smuzhiyun        board: Board object to build
39*4882a593Smuzhiyun        commits: List of commit options to build.
40*4882a593Smuzhiyun    """
41*4882a593Smuzhiyun    def __init__(self):
42*4882a593Smuzhiyun        self.board = None
43*4882a593Smuzhiyun        self.commits = []
44*4882a593Smuzhiyun
45*4882a593Smuzhiyun
46*4882a593Smuzhiyunclass ResultThread(threading.Thread):
47*4882a593Smuzhiyun    """This thread processes results from builder threads.
48*4882a593Smuzhiyun
49*4882a593Smuzhiyun    It simply passes the results on to the builder. There is only one
50*4882a593Smuzhiyun    result thread, and this helps to serialise the build output.
51*4882a593Smuzhiyun    """
52*4882a593Smuzhiyun    def __init__(self, builder):
53*4882a593Smuzhiyun        """Set up a new result thread
54*4882a593Smuzhiyun
55*4882a593Smuzhiyun        Args:
56*4882a593Smuzhiyun            builder: Builder which will be sent each result
57*4882a593Smuzhiyun        """
58*4882a593Smuzhiyun        threading.Thread.__init__(self)
59*4882a593Smuzhiyun        self.builder = builder
60*4882a593Smuzhiyun
61*4882a593Smuzhiyun    def run(self):
62*4882a593Smuzhiyun        """Called to start up the result thread.
63*4882a593Smuzhiyun
64*4882a593Smuzhiyun        We collect the next result job and pass it on to the build.
65*4882a593Smuzhiyun        """
66*4882a593Smuzhiyun        while True:
67*4882a593Smuzhiyun            result = self.builder.out_queue.get()
68*4882a593Smuzhiyun            self.builder.ProcessResult(result)
69*4882a593Smuzhiyun            self.builder.out_queue.task_done()
70*4882a593Smuzhiyun
71*4882a593Smuzhiyun
72*4882a593Smuzhiyunclass BuilderThread(threading.Thread):
73*4882a593Smuzhiyun    """This thread builds U-Boot for a particular board.
74*4882a593Smuzhiyun
75*4882a593Smuzhiyun    An input queue provides each new job. We run 'make' to build U-Boot
76*4882a593Smuzhiyun    and then pass the results on to the output queue.
77*4882a593Smuzhiyun
78*4882a593Smuzhiyun    Members:
79*4882a593Smuzhiyun        builder: The builder which contains information we might need
80*4882a593Smuzhiyun        thread_num: Our thread number (0-n-1), used to decide on a
81*4882a593Smuzhiyun                temporary directory
82*4882a593Smuzhiyun    """
83*4882a593Smuzhiyun    def __init__(self, builder, thread_num, incremental, per_board_out_dir):
84*4882a593Smuzhiyun        """Set up a new builder thread"""
85*4882a593Smuzhiyun        threading.Thread.__init__(self)
86*4882a593Smuzhiyun        self.builder = builder
87*4882a593Smuzhiyun        self.thread_num = thread_num
88*4882a593Smuzhiyun        self.incremental = incremental
89*4882a593Smuzhiyun        self.per_board_out_dir = per_board_out_dir
90*4882a593Smuzhiyun
91*4882a593Smuzhiyun    def Make(self, commit, brd, stage, cwd, *args, **kwargs):
92*4882a593Smuzhiyun        """Run 'make' on a particular commit and board.
93*4882a593Smuzhiyun
94*4882a593Smuzhiyun        The source code will already be checked out, so the 'commit'
95*4882a593Smuzhiyun        argument is only for information.
96*4882a593Smuzhiyun
97*4882a593Smuzhiyun        Args:
98*4882a593Smuzhiyun            commit: Commit object that is being built
99*4882a593Smuzhiyun            brd: Board object that is being built
100*4882a593Smuzhiyun            stage: Stage of the build. Valid stages are:
101*4882a593Smuzhiyun                        mrproper - can be called to clean source
102*4882a593Smuzhiyun                        config - called to configure for a board
103*4882a593Smuzhiyun                        build - the main make invocation - it does the build
104*4882a593Smuzhiyun            args: A list of arguments to pass to 'make'
105*4882a593Smuzhiyun            kwargs: A list of keyword arguments to pass to command.RunPipe()
106*4882a593Smuzhiyun
107*4882a593Smuzhiyun        Returns:
108*4882a593Smuzhiyun            CommandResult object
109*4882a593Smuzhiyun        """
110*4882a593Smuzhiyun        return self.builder.do_make(commit, brd, stage, cwd, *args,
111*4882a593Smuzhiyun                **kwargs)
112*4882a593Smuzhiyun
113*4882a593Smuzhiyun    def RunCommit(self, commit_upto, brd, work_dir, do_config, config_only,
114*4882a593Smuzhiyun                  force_build, force_build_failures):
115*4882a593Smuzhiyun        """Build a particular commit.
116*4882a593Smuzhiyun
117*4882a593Smuzhiyun        If the build is already done, and we are not forcing a build, we skip
118*4882a593Smuzhiyun        the build and just return the previously-saved results.
119*4882a593Smuzhiyun
120*4882a593Smuzhiyun        Args:
121*4882a593Smuzhiyun            commit_upto: Commit number to build (0...n-1)
122*4882a593Smuzhiyun            brd: Board object to build
123*4882a593Smuzhiyun            work_dir: Directory to which the source will be checked out
124*4882a593Smuzhiyun            do_config: True to run a make <board>_defconfig on the source
125*4882a593Smuzhiyun            config_only: Only configure the source, do not build it
126*4882a593Smuzhiyun            force_build: Force a build even if one was previously done
127*4882a593Smuzhiyun            force_build_failures: Force a bulid if the previous result showed
128*4882a593Smuzhiyun                failure
129*4882a593Smuzhiyun
130*4882a593Smuzhiyun        Returns:
131*4882a593Smuzhiyun            tuple containing:
132*4882a593Smuzhiyun                - CommandResult object containing the results of the build
133*4882a593Smuzhiyun                - boolean indicating whether 'make config' is still needed
134*4882a593Smuzhiyun        """
135*4882a593Smuzhiyun        # Create a default result - it will be overwritte by the call to
136*4882a593Smuzhiyun        # self.Make() below, in the event that we do a build.
137*4882a593Smuzhiyun        result = command.CommandResult()
138*4882a593Smuzhiyun        result.return_code = 0
139*4882a593Smuzhiyun        if self.builder.in_tree:
140*4882a593Smuzhiyun            out_dir = work_dir
141*4882a593Smuzhiyun        else:
142*4882a593Smuzhiyun            if self.per_board_out_dir:
143*4882a593Smuzhiyun                out_rel_dir = os.path.join('..', brd.target)
144*4882a593Smuzhiyun            else:
145*4882a593Smuzhiyun                out_rel_dir = 'build'
146*4882a593Smuzhiyun            out_dir = os.path.join(work_dir, out_rel_dir)
147*4882a593Smuzhiyun
148*4882a593Smuzhiyun        # Check if the job was already completed last time
149*4882a593Smuzhiyun        done_file = self.builder.GetDoneFile(commit_upto, brd.target)
150*4882a593Smuzhiyun        result.already_done = os.path.exists(done_file)
151*4882a593Smuzhiyun        will_build = (force_build or force_build_failures or
152*4882a593Smuzhiyun            not result.already_done)
153*4882a593Smuzhiyun        if result.already_done:
154*4882a593Smuzhiyun            # Get the return code from that build and use it
155*4882a593Smuzhiyun            with open(done_file, 'r') as fd:
156*4882a593Smuzhiyun                result.return_code = int(fd.readline())
157*4882a593Smuzhiyun
158*4882a593Smuzhiyun            # Check the signal that the build needs to be retried
159*4882a593Smuzhiyun            if result.return_code == RETURN_CODE_RETRY:
160*4882a593Smuzhiyun                will_build = True
161*4882a593Smuzhiyun            elif will_build:
162*4882a593Smuzhiyun                err_file = self.builder.GetErrFile(commit_upto, brd.target)
163*4882a593Smuzhiyun                if os.path.exists(err_file) and os.stat(err_file).st_size:
164*4882a593Smuzhiyun                    result.stderr = 'bad'
165*4882a593Smuzhiyun                elif not force_build:
166*4882a593Smuzhiyun                    # The build passed, so no need to build it again
167*4882a593Smuzhiyun                    will_build = False
168*4882a593Smuzhiyun
169*4882a593Smuzhiyun        if will_build:
170*4882a593Smuzhiyun            # We are going to have to build it. First, get a toolchain
171*4882a593Smuzhiyun            if not self.toolchain:
172*4882a593Smuzhiyun                try:
173*4882a593Smuzhiyun                    self.toolchain = self.builder.toolchains.Select(brd.arch)
174*4882a593Smuzhiyun                except ValueError as err:
175*4882a593Smuzhiyun                    result.return_code = 10
176*4882a593Smuzhiyun                    result.stdout = ''
177*4882a593Smuzhiyun                    result.stderr = str(err)
178*4882a593Smuzhiyun                    # TODO(sjg@chromium.org): This gets swallowed, but needs
179*4882a593Smuzhiyun                    # to be reported.
180*4882a593Smuzhiyun
181*4882a593Smuzhiyun            if self.toolchain:
182*4882a593Smuzhiyun                # Checkout the right commit
183*4882a593Smuzhiyun                if self.builder.commits:
184*4882a593Smuzhiyun                    commit = self.builder.commits[commit_upto]
185*4882a593Smuzhiyun                    if self.builder.checkout:
186*4882a593Smuzhiyun                        git_dir = os.path.join(work_dir, '.git')
187*4882a593Smuzhiyun                        gitutil.Checkout(commit.hash, git_dir, work_dir,
188*4882a593Smuzhiyun                                         force=True)
189*4882a593Smuzhiyun                else:
190*4882a593Smuzhiyun                    commit = 'current'
191*4882a593Smuzhiyun
192*4882a593Smuzhiyun                # Set up the environment and command line
193*4882a593Smuzhiyun                env = self.toolchain.MakeEnvironment(self.builder.full_path)
194*4882a593Smuzhiyun                Mkdir(out_dir)
195*4882a593Smuzhiyun                args = []
196*4882a593Smuzhiyun                cwd = work_dir
197*4882a593Smuzhiyun                src_dir = os.path.realpath(work_dir)
198*4882a593Smuzhiyun                if not self.builder.in_tree:
199*4882a593Smuzhiyun                    if commit_upto is None:
200*4882a593Smuzhiyun                        # In this case we are building in the original source
201*4882a593Smuzhiyun                        # directory (i.e. the current directory where buildman
202*4882a593Smuzhiyun                        # is invoked. The output directory is set to this
203*4882a593Smuzhiyun                        # thread's selected work directory.
204*4882a593Smuzhiyun                        #
205*4882a593Smuzhiyun                        # Symlinks can confuse U-Boot's Makefile since
206*4882a593Smuzhiyun                        # we may use '..' in our path, so remove them.
207*4882a593Smuzhiyun                        out_dir = os.path.realpath(out_dir)
208*4882a593Smuzhiyun                        args.append('O=%s' % out_dir)
209*4882a593Smuzhiyun                        cwd = None
210*4882a593Smuzhiyun                        src_dir = os.getcwd()
211*4882a593Smuzhiyun                    else:
212*4882a593Smuzhiyun                        args.append('O=%s' % out_rel_dir)
213*4882a593Smuzhiyun                if self.builder.verbose_build:
214*4882a593Smuzhiyun                    args.append('V=1')
215*4882a593Smuzhiyun                else:
216*4882a593Smuzhiyun                    args.append('-s')
217*4882a593Smuzhiyun                if self.builder.num_jobs is not None:
218*4882a593Smuzhiyun                    args.extend(['-j', str(self.builder.num_jobs)])
219*4882a593Smuzhiyun                config_args = ['%s_defconfig' % brd.target]
220*4882a593Smuzhiyun                config_out = ''
221*4882a593Smuzhiyun                args.extend(self.builder.toolchains.GetMakeArguments(brd))
222*4882a593Smuzhiyun
223*4882a593Smuzhiyun                # If we need to reconfigure, do that now
224*4882a593Smuzhiyun                if do_config:
225*4882a593Smuzhiyun                    config_out = ''
226*4882a593Smuzhiyun                    if not self.incremental:
227*4882a593Smuzhiyun                        result = self.Make(commit, brd, 'mrproper', cwd,
228*4882a593Smuzhiyun                                'mrproper', *args, env=env)
229*4882a593Smuzhiyun                        config_out += result.combined
230*4882a593Smuzhiyun                    result = self.Make(commit, brd, 'config', cwd,
231*4882a593Smuzhiyun                            *(args + config_args), env=env)
232*4882a593Smuzhiyun                    config_out += result.combined
233*4882a593Smuzhiyun                    do_config = False   # No need to configure next time
234*4882a593Smuzhiyun                if result.return_code == 0:
235*4882a593Smuzhiyun                    if config_only:
236*4882a593Smuzhiyun                        args.append('cfg')
237*4882a593Smuzhiyun                    result = self.Make(commit, brd, 'build', cwd, *args,
238*4882a593Smuzhiyun                            env=env)
239*4882a593Smuzhiyun                result.stderr = result.stderr.replace(src_dir + '/', '')
240*4882a593Smuzhiyun                if self.builder.verbose_build:
241*4882a593Smuzhiyun                    result.stdout = config_out + result.stdout
242*4882a593Smuzhiyun            else:
243*4882a593Smuzhiyun                result.return_code = 1
244*4882a593Smuzhiyun                result.stderr = 'No tool chain for %s\n' % brd.arch
245*4882a593Smuzhiyun            result.already_done = False
246*4882a593Smuzhiyun
247*4882a593Smuzhiyun        result.toolchain = self.toolchain
248*4882a593Smuzhiyun        result.brd = brd
249*4882a593Smuzhiyun        result.commit_upto = commit_upto
250*4882a593Smuzhiyun        result.out_dir = out_dir
251*4882a593Smuzhiyun        return result, do_config
252*4882a593Smuzhiyun
253*4882a593Smuzhiyun    def _WriteResult(self, result, keep_outputs):
254*4882a593Smuzhiyun        """Write a built result to the output directory.
255*4882a593Smuzhiyun
256*4882a593Smuzhiyun        Args:
257*4882a593Smuzhiyun            result: CommandResult object containing result to write
258*4882a593Smuzhiyun            keep_outputs: True to store the output binaries, False
259*4882a593Smuzhiyun                to delete them
260*4882a593Smuzhiyun        """
261*4882a593Smuzhiyun        # Fatal error
262*4882a593Smuzhiyun        if result.return_code < 0:
263*4882a593Smuzhiyun            return
264*4882a593Smuzhiyun
265*4882a593Smuzhiyun        # If we think this might have been aborted with Ctrl-C, record the
266*4882a593Smuzhiyun        # failure but not that we are 'done' with this board. A retry may fix
267*4882a593Smuzhiyun        # it.
268*4882a593Smuzhiyun        maybe_aborted =  result.stderr and 'No child processes' in result.stderr
269*4882a593Smuzhiyun
270*4882a593Smuzhiyun        if result.already_done:
271*4882a593Smuzhiyun            return
272*4882a593Smuzhiyun
273*4882a593Smuzhiyun        # Write the output and stderr
274*4882a593Smuzhiyun        output_dir = self.builder._GetOutputDir(result.commit_upto)
275*4882a593Smuzhiyun        Mkdir(output_dir)
276*4882a593Smuzhiyun        build_dir = self.builder.GetBuildDir(result.commit_upto,
277*4882a593Smuzhiyun                result.brd.target)
278*4882a593Smuzhiyun        Mkdir(build_dir)
279*4882a593Smuzhiyun
280*4882a593Smuzhiyun        outfile = os.path.join(build_dir, 'log')
281*4882a593Smuzhiyun        with open(outfile, 'w') as fd:
282*4882a593Smuzhiyun            if result.stdout:
283*4882a593Smuzhiyun                # We don't want unicode characters in log files
284*4882a593Smuzhiyun                fd.write(result.stdout.decode('UTF-8').encode('ASCII', 'replace'))
285*4882a593Smuzhiyun
286*4882a593Smuzhiyun        errfile = self.builder.GetErrFile(result.commit_upto,
287*4882a593Smuzhiyun                result.brd.target)
288*4882a593Smuzhiyun        if result.stderr:
289*4882a593Smuzhiyun            with open(errfile, 'w') as fd:
290*4882a593Smuzhiyun                # We don't want unicode characters in log files
291*4882a593Smuzhiyun                fd.write(result.stderr.decode('UTF-8').encode('ASCII', 'replace'))
292*4882a593Smuzhiyun        elif os.path.exists(errfile):
293*4882a593Smuzhiyun            os.remove(errfile)
294*4882a593Smuzhiyun
295*4882a593Smuzhiyun        if result.toolchain:
296*4882a593Smuzhiyun            # Write the build result and toolchain information.
297*4882a593Smuzhiyun            done_file = self.builder.GetDoneFile(result.commit_upto,
298*4882a593Smuzhiyun                    result.brd.target)
299*4882a593Smuzhiyun            with open(done_file, 'w') as fd:
300*4882a593Smuzhiyun                if maybe_aborted:
301*4882a593Smuzhiyun                    # Special code to indicate we need to retry
302*4882a593Smuzhiyun                    fd.write('%s' % RETURN_CODE_RETRY)
303*4882a593Smuzhiyun                else:
304*4882a593Smuzhiyun                    fd.write('%s' % result.return_code)
305*4882a593Smuzhiyun            with open(os.path.join(build_dir, 'toolchain'), 'w') as fd:
306*4882a593Smuzhiyun                print >>fd, 'gcc', result.toolchain.gcc
307*4882a593Smuzhiyun                print >>fd, 'path', result.toolchain.path
308*4882a593Smuzhiyun                print >>fd, 'cross', result.toolchain.cross
309*4882a593Smuzhiyun                print >>fd, 'arch', result.toolchain.arch
310*4882a593Smuzhiyun                fd.write('%s' % result.return_code)
311*4882a593Smuzhiyun
312*4882a593Smuzhiyun            # Write out the image and function size information and an objdump
313*4882a593Smuzhiyun            env = result.toolchain.MakeEnvironment(self.builder.full_path)
314*4882a593Smuzhiyun            lines = []
315*4882a593Smuzhiyun            for fname in ['u-boot', 'spl/u-boot-spl']:
316*4882a593Smuzhiyun                cmd = ['%snm' % self.toolchain.cross, '--size-sort', fname]
317*4882a593Smuzhiyun                nm_result = command.RunPipe([cmd], capture=True,
318*4882a593Smuzhiyun                        capture_stderr=True, cwd=result.out_dir,
319*4882a593Smuzhiyun                        raise_on_error=False, env=env)
320*4882a593Smuzhiyun                if nm_result.stdout:
321*4882a593Smuzhiyun                    nm = self.builder.GetFuncSizesFile(result.commit_upto,
322*4882a593Smuzhiyun                                    result.brd.target, fname)
323*4882a593Smuzhiyun                    with open(nm, 'w') as fd:
324*4882a593Smuzhiyun                        print >>fd, nm_result.stdout,
325*4882a593Smuzhiyun
326*4882a593Smuzhiyun                cmd = ['%sobjdump' % self.toolchain.cross, '-h', fname]
327*4882a593Smuzhiyun                dump_result = command.RunPipe([cmd], capture=True,
328*4882a593Smuzhiyun                        capture_stderr=True, cwd=result.out_dir,
329*4882a593Smuzhiyun                        raise_on_error=False, env=env)
330*4882a593Smuzhiyun                rodata_size = ''
331*4882a593Smuzhiyun                if dump_result.stdout:
332*4882a593Smuzhiyun                    objdump = self.builder.GetObjdumpFile(result.commit_upto,
333*4882a593Smuzhiyun                                    result.brd.target, fname)
334*4882a593Smuzhiyun                    with open(objdump, 'w') as fd:
335*4882a593Smuzhiyun                        print >>fd, dump_result.stdout,
336*4882a593Smuzhiyun                    for line in dump_result.stdout.splitlines():
337*4882a593Smuzhiyun                        fields = line.split()
338*4882a593Smuzhiyun                        if len(fields) > 5 and fields[1] == '.rodata':
339*4882a593Smuzhiyun                            rodata_size = fields[2]
340*4882a593Smuzhiyun
341*4882a593Smuzhiyun                cmd = ['%ssize' % self.toolchain.cross, fname]
342*4882a593Smuzhiyun                size_result = command.RunPipe([cmd], capture=True,
343*4882a593Smuzhiyun                        capture_stderr=True, cwd=result.out_dir,
344*4882a593Smuzhiyun                        raise_on_error=False, env=env)
345*4882a593Smuzhiyun                if size_result.stdout:
346*4882a593Smuzhiyun                    lines.append(size_result.stdout.splitlines()[1] + ' ' +
347*4882a593Smuzhiyun                                 rodata_size)
348*4882a593Smuzhiyun
349*4882a593Smuzhiyun            # Write out the image sizes file. This is similar to the output
350*4882a593Smuzhiyun            # of binutil's 'size' utility, but it omits the header line and
351*4882a593Smuzhiyun            # adds an additional hex value at the end of each line for the
352*4882a593Smuzhiyun            # rodata size
353*4882a593Smuzhiyun            if len(lines):
354*4882a593Smuzhiyun                sizes = self.builder.GetSizesFile(result.commit_upto,
355*4882a593Smuzhiyun                                result.brd.target)
356*4882a593Smuzhiyun                with open(sizes, 'w') as fd:
357*4882a593Smuzhiyun                    print >>fd, '\n'.join(lines)
358*4882a593Smuzhiyun
359*4882a593Smuzhiyun        # Write out the configuration files, with a special case for SPL
360*4882a593Smuzhiyun        for dirname in ['', 'spl', 'tpl']:
361*4882a593Smuzhiyun            self.CopyFiles(result.out_dir, build_dir, dirname, ['u-boot.cfg',
362*4882a593Smuzhiyun                'spl/u-boot-spl.cfg', 'tpl/u-boot-tpl.cfg', '.config',
363*4882a593Smuzhiyun                'include/autoconf.mk', 'include/generated/autoconf.h'])
364*4882a593Smuzhiyun
365*4882a593Smuzhiyun        # Now write the actual build output
366*4882a593Smuzhiyun        if keep_outputs:
367*4882a593Smuzhiyun            self.CopyFiles(result.out_dir, build_dir, '', ['u-boot*', '*.bin',
368*4882a593Smuzhiyun                '*.map', '*.img', 'MLO', 'SPL', 'include/autoconf.mk',
369*4882a593Smuzhiyun                'spl/u-boot-spl*'])
370*4882a593Smuzhiyun
371*4882a593Smuzhiyun    def CopyFiles(self, out_dir, build_dir, dirname, patterns):
372*4882a593Smuzhiyun        """Copy files from the build directory to the output.
373*4882a593Smuzhiyun
374*4882a593Smuzhiyun        Args:
375*4882a593Smuzhiyun            out_dir: Path to output directory containing the files
376*4882a593Smuzhiyun            build_dir: Place to copy the files
377*4882a593Smuzhiyun            dirname: Source directory, '' for normal U-Boot, 'spl' for SPL
378*4882a593Smuzhiyun            patterns: A list of filenames (strings) to copy, each relative
379*4882a593Smuzhiyun               to the build directory
380*4882a593Smuzhiyun        """
381*4882a593Smuzhiyun        for pattern in patterns:
382*4882a593Smuzhiyun            file_list = glob.glob(os.path.join(out_dir, dirname, pattern))
383*4882a593Smuzhiyun            for fname in file_list:
384*4882a593Smuzhiyun                target = os.path.basename(fname)
385*4882a593Smuzhiyun                if dirname:
386*4882a593Smuzhiyun                    base, ext = os.path.splitext(target)
387*4882a593Smuzhiyun                    if ext:
388*4882a593Smuzhiyun                        target = '%s-%s%s' % (base, dirname, ext)
389*4882a593Smuzhiyun                shutil.copy(fname, os.path.join(build_dir, target))
390*4882a593Smuzhiyun
391*4882a593Smuzhiyun    def RunJob(self, job):
392*4882a593Smuzhiyun        """Run a single job
393*4882a593Smuzhiyun
394*4882a593Smuzhiyun        A job consists of a building a list of commits for a particular board.
395*4882a593Smuzhiyun
396*4882a593Smuzhiyun        Args:
397*4882a593Smuzhiyun            job: Job to build
398*4882a593Smuzhiyun        """
399*4882a593Smuzhiyun        brd = job.board
400*4882a593Smuzhiyun        work_dir = self.builder.GetThreadDir(self.thread_num)
401*4882a593Smuzhiyun        self.toolchain = None
402*4882a593Smuzhiyun        if job.commits:
403*4882a593Smuzhiyun            # Run 'make board_defconfig' on the first commit
404*4882a593Smuzhiyun            do_config = True
405*4882a593Smuzhiyun            commit_upto  = 0
406*4882a593Smuzhiyun            force_build = False
407*4882a593Smuzhiyun            for commit_upto in range(0, len(job.commits), job.step):
408*4882a593Smuzhiyun                result, request_config = self.RunCommit(commit_upto, brd,
409*4882a593Smuzhiyun                        work_dir, do_config, self.builder.config_only,
410*4882a593Smuzhiyun                        force_build or self.builder.force_build,
411*4882a593Smuzhiyun                        self.builder.force_build_failures)
412*4882a593Smuzhiyun                failed = result.return_code or result.stderr
413*4882a593Smuzhiyun                did_config = do_config
414*4882a593Smuzhiyun                if failed and not do_config:
415*4882a593Smuzhiyun                    # If our incremental build failed, try building again
416*4882a593Smuzhiyun                    # with a reconfig.
417*4882a593Smuzhiyun                    if self.builder.force_config_on_failure:
418*4882a593Smuzhiyun                        result, request_config = self.RunCommit(commit_upto,
419*4882a593Smuzhiyun                            brd, work_dir, True, False, True, False)
420*4882a593Smuzhiyun                        did_config = True
421*4882a593Smuzhiyun                if not self.builder.force_reconfig:
422*4882a593Smuzhiyun                    do_config = request_config
423*4882a593Smuzhiyun
424*4882a593Smuzhiyun                # If we built that commit, then config is done. But if we got
425*4882a593Smuzhiyun                # an warning, reconfig next time to force it to build the same
426*4882a593Smuzhiyun                # files that created warnings this time. Otherwise an
427*4882a593Smuzhiyun                # incremental build may not build the same file, and we will
428*4882a593Smuzhiyun                # think that the warning has gone away.
429*4882a593Smuzhiyun                # We could avoid this by using -Werror everywhere...
430*4882a593Smuzhiyun                # For errors, the problem doesn't happen, since presumably
431*4882a593Smuzhiyun                # the build stopped and didn't generate output, so will retry
432*4882a593Smuzhiyun                # that file next time. So we could detect warnings and deal
433*4882a593Smuzhiyun                # with them specially here. For now, we just reconfigure if
434*4882a593Smuzhiyun                # anything goes work.
435*4882a593Smuzhiyun                # Of course this is substantially slower if there are build
436*4882a593Smuzhiyun                # errors/warnings (e.g. 2-3x slower even if only 10% of builds
437*4882a593Smuzhiyun                # have problems).
438*4882a593Smuzhiyun                if (failed and not result.already_done and not did_config and
439*4882a593Smuzhiyun                        self.builder.force_config_on_failure):
440*4882a593Smuzhiyun                    # If this build failed, try the next one with a
441*4882a593Smuzhiyun                    # reconfigure.
442*4882a593Smuzhiyun                    # Sometimes if the board_config.h file changes it can mess
443*4882a593Smuzhiyun                    # with dependencies, and we get:
444*4882a593Smuzhiyun                    # make: *** No rule to make target `include/autoconf.mk',
445*4882a593Smuzhiyun                    #     needed by `depend'.
446*4882a593Smuzhiyun                    do_config = True
447*4882a593Smuzhiyun                    force_build = True
448*4882a593Smuzhiyun                else:
449*4882a593Smuzhiyun                    force_build = False
450*4882a593Smuzhiyun                    if self.builder.force_config_on_failure:
451*4882a593Smuzhiyun                        if failed:
452*4882a593Smuzhiyun                            do_config = True
453*4882a593Smuzhiyun                    result.commit_upto = commit_upto
454*4882a593Smuzhiyun                    if result.return_code < 0:
455*4882a593Smuzhiyun                        raise ValueError('Interrupt')
456*4882a593Smuzhiyun
457*4882a593Smuzhiyun                # We have the build results, so output the result
458*4882a593Smuzhiyun                self._WriteResult(result, job.keep_outputs)
459*4882a593Smuzhiyun                self.builder.out_queue.put(result)
460*4882a593Smuzhiyun        else:
461*4882a593Smuzhiyun            # Just build the currently checked-out build
462*4882a593Smuzhiyun            result, request_config = self.RunCommit(None, brd, work_dir, True,
463*4882a593Smuzhiyun                        self.builder.config_only, True,
464*4882a593Smuzhiyun                        self.builder.force_build_failures)
465*4882a593Smuzhiyun            result.commit_upto = 0
466*4882a593Smuzhiyun            self._WriteResult(result, job.keep_outputs)
467*4882a593Smuzhiyun            self.builder.out_queue.put(result)
468*4882a593Smuzhiyun
469*4882a593Smuzhiyun    def run(self):
470*4882a593Smuzhiyun        """Our thread's run function
471*4882a593Smuzhiyun
472*4882a593Smuzhiyun        This thread picks a job from the queue, runs it, and then goes to the
473*4882a593Smuzhiyun        next job.
474*4882a593Smuzhiyun        """
475*4882a593Smuzhiyun        while True:
476*4882a593Smuzhiyun            job = self.builder.queue.get()
477*4882a593Smuzhiyun            self.RunJob(job)
478*4882a593Smuzhiyun            self.builder.queue.task_done()
479