Prepare v2023.10
[platform/kernel/u-boot.git] / tools / buildman / builderthread.py
1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright (c) 2014 Google, Inc
3 #
4
5 """Implementation the bulider threads
6
7 This module provides the BuilderThread class, which handles calling the builder
8 based on the jobs provided.
9 """
10
11 import errno
12 import glob
13 import io
14 import os
15 import shutil
16 import sys
17 import threading
18
19 from buildman import cfgutil
20 from patman import gitutil
21 from u_boot_pylib import command
22
23 RETURN_CODE_RETRY = -1
24 BASE_ELF_FILENAMES = ['u-boot', 'spl/u-boot-spl', 'tpl/u-boot-tpl']
25
26 def mkdir(dirname, parents=False):
27     """Make a directory if it doesn't already exist.
28
29     Args:
30         dirname (str): Directory to create
31         parents (bool): True to also make parent directories
32
33     Raises:
34         OSError: File already exists
35     """
36     try:
37         if parents:
38             os.makedirs(dirname)
39         else:
40             os.mkdir(dirname)
41     except OSError as err:
42         if err.errno == errno.EEXIST:
43             if os.path.realpath('.') == os.path.realpath(dirname):
44                 print(f"Cannot create the current working directory '{dirname}'!")
45                 sys.exit(1)
46         else:
47             raise
48
49
50 def _remove_old_outputs(out_dir):
51     """Remove any old output-target files
52
53     Args:
54         out_dir (str): Output directory for the build
55
56     Since we use a build directory that was previously used by another
57     board, it may have produced an SPL image. If we don't remove it (i.e.
58     see do_config and self.mrproper below) then it will appear to be the
59     output of this build, even if it does not produce SPL images.
60     """
61     for elf in BASE_ELF_FILENAMES:
62         fname = os.path.join(out_dir, elf)
63         if os.path.exists(fname):
64             os.remove(fname)
65
66
67 def copy_files(out_dir, build_dir, dirname, patterns):
68     """Copy files from the build directory to the output.
69
70     Args:
71         out_dir (str): Path to output directory containing the files
72         build_dir (str): Place to copy the files
73         dirname (str): Source directory, '' for normal U-Boot, 'spl' for SPL
74         patterns (list of str): A list of filenames to copy, each relative
75            to the build directory
76     """
77     for pattern in patterns:
78         file_list = glob.glob(os.path.join(out_dir, dirname, pattern))
79         for fname in file_list:
80             target = os.path.basename(fname)
81             if dirname:
82                 base, ext = os.path.splitext(target)
83                 if ext:
84                     target = f'{base}-{dirname}{ext}'
85             shutil.copy(fname, os.path.join(build_dir, target))
86
87
88 # pylint: disable=R0903
89 class BuilderJob:
90     """Holds information about a job to be performed by a thread
91
92     Members:
93         brd: Board object to build
94         commits: List of Commit objects to build
95         keep_outputs: True to save build output files
96         step: 1 to process every commit, n to process every nth commit
97         work_in_output: Use the output directory as the work directory and
98             don't write to a separate output directory.
99     """
100     def __init__(self):
101         self.brd = None
102         self.commits = []
103         self.keep_outputs = False
104         self.step = 1
105         self.work_in_output = False
106
107
108 class ResultThread(threading.Thread):
109     """This thread processes results from builder threads.
110
111     It simply passes the results on to the builder. There is only one
112     result thread, and this helps to serialise the build output.
113     """
114     def __init__(self, builder):
115         """Set up a new result thread
116
117         Args:
118             builder: Builder which will be sent each result
119         """
120         threading.Thread.__init__(self)
121         self.builder = builder
122
123     def run(self):
124         """Called to start up the result thread.
125
126         We collect the next result job and pass it on to the build.
127         """
128         while True:
129             result = self.builder.out_queue.get()
130             self.builder.process_result(result)
131             self.builder.out_queue.task_done()
132
133
134 class BuilderThread(threading.Thread):
135     """This thread builds U-Boot for a particular board.
136
137     An input queue provides each new job. We run 'make' to build U-Boot
138     and then pass the results on to the output queue.
139
140     Members:
141         builder: The builder which contains information we might need
142         thread_num: Our thread number (0-n-1), used to decide on a
143             temporary directory. If this is -1 then there are no threads
144             and we are the (only) main process
145         mrproper: Use 'make mrproper' before each reconfigure
146         per_board_out_dir: True to build in a separate persistent directory per
147             board rather than a thread-specific directory
148         test_exception: Used for testing; True to raise an exception instead of
149             reporting the build result
150     """
151     def __init__(self, builder, thread_num, mrproper, per_board_out_dir,
152                  test_exception=False):
153         """Set up a new builder thread"""
154         threading.Thread.__init__(self)
155         self.builder = builder
156         self.thread_num = thread_num
157         self.mrproper = mrproper
158         self.per_board_out_dir = per_board_out_dir
159         self.test_exception = test_exception
160         self.toolchain = None
161
162     def make(self, commit, brd, stage, cwd, *args, **kwargs):
163         """Run 'make' on a particular commit and board.
164
165         The source code will already be checked out, so the 'commit'
166         argument is only for information.
167
168         Args:
169             commit (Commit): Commit that is being built
170             brd (Board): Board that is being built
171             stage (str): Stage of the build. Valid stages are:
172                         mrproper - can be called to clean source
173                         config - called to configure for a board
174                         build - the main make invocation - it does the build
175             cwd (str): Working directory to set, or None to leave it alone
176             *args (list of str): Arguments to pass to 'make'
177             **kwargs (dict): A list of keyword arguments to pass to
178                 command.run_pipe()
179
180         Returns:
181             CommandResult object
182         """
183         return self.builder.do_make(commit, brd, stage, cwd, *args,
184                 **kwargs)
185
186     def _build_args(self, brd, out_dir, out_rel_dir, work_dir, commit_upto):
187         """Set up arguments to the args list based on the settings
188
189         Args:
190             brd (Board): Board to create arguments for
191             out_dir (str): Path to output directory containing the files
192             out_rel_dir (str): Output directory relative to the current dir
193             work_dir (str): Directory to which the source will be checked out
194             commit_upto (int): Commit number to build (0...n-1)
195
196         Returns:
197             tuple:
198                 list of str: Arguments to pass to make
199                 str: Current working directory, or None if no commit
200                 str: Source directory (typically the work directory)
201         """
202         args = []
203         cwd = work_dir
204         src_dir = os.path.realpath(work_dir)
205         if not self.builder.in_tree:
206             if commit_upto is None:
207                 # In this case we are building in the original source directory
208                 # (i.e. the current directory where buildman is invoked. The
209                 # output directory is set to this thread's selected work
210                 # directory.
211                 #
212                 # Symlinks can confuse U-Boot's Makefile since we may use '..'
213                 # in our path, so remove them.
214                 real_dir = os.path.realpath(out_dir)
215                 args.append(f'O={real_dir}')
216                 cwd = None
217                 src_dir = os.getcwd()
218             else:
219                 args.append(f'O={out_rel_dir}')
220         if self.builder.verbose_build:
221             args.append('V=1')
222         else:
223             args.append('-s')
224         if self.builder.num_jobs is not None:
225             args.extend(['-j', str(self.builder.num_jobs)])
226         if self.builder.warnings_as_errors:
227             args.append('KCFLAGS=-Werror')
228             args.append('HOSTCFLAGS=-Werror')
229         if self.builder.allow_missing:
230             args.append('BINMAN_ALLOW_MISSING=1')
231         if self.builder.no_lto:
232             args.append('NO_LTO=1')
233         if self.builder.reproducible_builds:
234             args.append('SOURCE_DATE_EPOCH=0')
235         args.extend(self.builder.toolchains.GetMakeArguments(brd))
236         args.extend(self.toolchain.MakeArgs())
237         return args, cwd, src_dir
238
239     def _reconfigure(self, commit, brd, cwd, args, env, config_args, config_out,
240                      cmd_list):
241         """Reconfigure the build
242
243         Args:
244             commit (Commit): Commit only being built
245             brd (Board): Board being built
246             cwd (str): Current working directory
247             args (list of str): Arguments to pass to make
248             env (dict): Environment strings
249             config_args (list of str): defconfig arg for this board
250             cmd_list (list of str): List to add the commands to, for logging
251
252         Returns:
253             CommandResult object
254         """
255         if self.mrproper:
256             result = self.make(commit, brd, 'mrproper', cwd, 'mrproper', *args,
257                                env=env)
258             config_out.write(result.combined)
259             cmd_list.append([self.builder.gnu_make, 'mrproper', *args])
260         result = self.make(commit, brd, 'config', cwd, *(args + config_args),
261                            env=env)
262         cmd_list.append([self.builder.gnu_make] + args + config_args)
263         config_out.write(result.combined)
264         return result
265
266     def _build(self, commit, brd, cwd, args, env, cmd_list, config_only):
267         """Perform the build
268
269         Args:
270             commit (Commit): Commit only being built
271             brd (Board): Board being built
272             cwd (str): Current working directory
273             args (list of str): Arguments to pass to make
274             env (dict): Environment strings
275             cmd_list (list of str): List to add the commands to, for logging
276             config_only (bool): True if this is a config-only build (using the
277                 'make cfg' target)
278
279         Returns:
280             CommandResult object
281         """
282         if config_only:
283             args.append('cfg')
284         result = self.make(commit, brd, 'build', cwd, *args, env=env)
285         cmd_list.append([self.builder.gnu_make] + args)
286         if (result.return_code == 2 and
287             ('Some images are invalid' in result.stderr)):
288             # This is handled later by the check for output in stderr
289             result.return_code = 0
290         return result
291
292     def _read_done_file(self, commit_upto, brd, force_build,
293                         force_build_failures):
294         """Check the 'done' file and see if this commit should be built
295
296         Args:
297             commit (Commit): Commit only being built
298             brd (Board): Board being built
299             force_build (bool): Force a build even if one was previously done
300             force_build_failures (bool): Force a bulid if the previous result
301                 showed failure
302
303         Returns:
304             tuple:
305                 bool: True if build should be built
306                 CommandResult: if there was a previous run:
307                     - already_done set to True
308                     - return_code set to return code
309                     - result.stderr set to 'bad' if stderr output was recorded
310         """
311         result = command.CommandResult()
312         done_file = self.builder.get_done_file(commit_upto, brd.target)
313         result.already_done = os.path.exists(done_file)
314         will_build = (force_build or force_build_failures or
315             not result.already_done)
316         if result.already_done:
317             with open(done_file, 'r', encoding='utf-8') as outf:
318                 try:
319                     result.return_code = int(outf.readline())
320                 except ValueError:
321                     # The file may be empty due to running out of disk space.
322                     # Try a rebuild
323                     result.return_code = RETURN_CODE_RETRY
324
325             # Check the signal that the build needs to be retried
326             if result.return_code == RETURN_CODE_RETRY:
327                 will_build = True
328             elif will_build:
329                 err_file = self.builder.get_err_file(commit_upto, brd.target)
330                 if os.path.exists(err_file) and os.stat(err_file).st_size:
331                     result.stderr = 'bad'
332                 elif not force_build:
333                     # The build passed, so no need to build it again
334                     will_build = False
335         return will_build, result
336
337     def _decide_dirs(self, brd, work_dir, work_in_output):
338         """Decide the output directory to use
339
340         Args:
341             work_dir (str): Directory to which the source will be checked out
342             work_in_output (bool): Use the output directory as the work
343                 directory and don't write to a separate output directory.
344
345         Returns:
346             tuple:
347                 out_dir (str): Output directory for the build
348                 out_rel_dir (str): Output directory relatie to the current dir
349         """
350         if work_in_output or self.builder.in_tree:
351             out_rel_dir = None
352             out_dir = work_dir
353         else:
354             if self.per_board_out_dir:
355                 out_rel_dir = os.path.join('..', brd.target)
356             else:
357                 out_rel_dir = 'build'
358             out_dir = os.path.join(work_dir, out_rel_dir)
359         return out_dir, out_rel_dir
360
361     def _checkout(self, commit_upto, work_dir):
362         """Checkout the right commit
363
364         Args:
365             commit_upto (int): Commit number to build (0...n-1)
366             work_dir (str): Directory to which the source will be checked out
367
368         Returns:
369             Commit: Commit being built, or 'current' for current source
370         """
371         if self.builder.commits:
372             commit = self.builder.commits[commit_upto]
373             if self.builder.checkout:
374                 git_dir = os.path.join(work_dir, '.git')
375                 gitutil.checkout(commit.hash, git_dir, work_dir, force=True)
376         else:
377             commit = 'current'
378         return commit
379
380     def _config_and_build(self, commit_upto, brd, work_dir, do_config,
381                           config_only, adjust_cfg, commit, out_dir, out_rel_dir,
382                           result):
383         """Do the build, configuring first if necessary
384
385         Args:
386             commit_upto (int): Commit number to build (0...n-1)
387             brd (Board): Board to create arguments for
388             work_dir (str): Directory to which the source will be checked out
389             do_config (bool): True to run a make <board>_defconfig on the source
390             config_only (bool): Only configure the source, do not build it
391             adjust_cfg (list of str): See the cfgutil module and run_commit()
392             commit (Commit): Commit only being built
393             out_dir (str): Output directory for the build
394             out_rel_dir (str): Output directory relatie to the current dir
395             result (CommandResult): Previous result
396
397         Returns:
398             tuple:
399                 result (CommandResult): Result of the build
400                 do_config (bool): indicates whether 'make config' is needed on
401                     the next incremental build
402         """
403         # Set up the environment and command line
404         env = self.toolchain.MakeEnvironment(self.builder.full_path)
405         mkdir(out_dir)
406
407         args, cwd, src_dir = self._build_args(brd, out_dir, out_rel_dir,
408                                               work_dir, commit_upto)
409         config_args = [f'{brd.target}_defconfig']
410         config_out = io.StringIO()
411
412         _remove_old_outputs(out_dir)
413
414         # If we need to reconfigure, do that now
415         cfg_file = os.path.join(out_dir, '.config')
416         cmd_list = []
417         if do_config or adjust_cfg:
418             result = self._reconfigure(
419                 commit, brd, cwd, args, env, config_args, config_out, cmd_list)
420             do_config = False   # No need to configure next time
421             if adjust_cfg:
422                 cfgutil.adjust_cfg_file(cfg_file, adjust_cfg)
423
424         # Now do the build, if everything looks OK
425         if result.return_code == 0:
426             result = self._build(commit, brd, cwd, args, env, cmd_list,
427                                  config_only)
428             if adjust_cfg:
429                 errs = cfgutil.check_cfg_file(cfg_file, adjust_cfg)
430                 if errs:
431                     result.stderr += errs
432                     result.return_code = 1
433         result.stderr = result.stderr.replace(src_dir + '/', '')
434         if self.builder.verbose_build:
435             result.stdout = config_out.getvalue() + result.stdout
436         result.cmd_list = cmd_list
437         return result, do_config
438
439     def run_commit(self, commit_upto, brd, work_dir, do_config, config_only,
440                   force_build, force_build_failures, work_in_output,
441                   adjust_cfg):
442         """Build a particular commit.
443
444         If the build is already done, and we are not forcing a build, we skip
445         the build and just return the previously-saved results.
446
447         Args:
448             commit_upto (int): Commit number to build (0...n-1)
449             brd (Board): Board to build
450             work_dir (str): Directory to which the source will be checked out
451             do_config (bool): True to run a make <board>_defconfig on the source
452             config_only (bool): Only configure the source, do not build it
453             force_build (bool): Force a build even if one was previously done
454             force_build_failures (bool): Force a bulid if the previous result
455                 showed failure
456             work_in_output (bool) : Use the output directory as the work
457                 directory and don't write to a separate output directory.
458             adjust_cfg (list of str): List of changes to make to .config file
459                 before building. Each is one of (where C is either CONFIG_xxx
460                 or just xxx):
461                      C to enable C
462                      ~C to disable C
463                      C=val to set the value of C (val must have quotes if C is
464                          a string Kconfig
465
466         Returns:
467             tuple containing:
468                 - CommandResult object containing the results of the build
469                 - boolean indicating whether 'make config' is still needed
470         """
471         # Create a default result - it will be overwritte by the call to
472         # self.make() below, in the event that we do a build.
473         out_dir, out_rel_dir = self._decide_dirs(brd, work_dir, work_in_output)
474
475         # Check if the job was already completed last time
476         will_build, result = self._read_done_file(commit_upto, brd, force_build,
477                                                   force_build_failures)
478
479         if will_build:
480             # We are going to have to build it. First, get a toolchain
481             if not self.toolchain:
482                 try:
483                     self.toolchain = self.builder.toolchains.Select(brd.arch)
484                 except ValueError as err:
485                     result.return_code = 10
486                     result.stdout = ''
487                     result.stderr = f'Tool chain error for {brd.arch}: {str(err)}'
488
489             if self.toolchain:
490                 commit = self._checkout(commit_upto, work_dir)
491                 result, do_config = self._config_and_build(
492                     commit_upto, brd, work_dir, do_config, config_only,
493                     adjust_cfg, commit, out_dir, out_rel_dir, result)
494             result.already_done = False
495
496         result.toolchain = self.toolchain
497         result.brd = brd
498         result.commit_upto = commit_upto
499         result.out_dir = out_dir
500         return result, do_config
501
502     def _write_result(self, result, keep_outputs, work_in_output):
503         """Write a built result to the output directory.
504
505         Args:
506             result (CommandResult): result to write
507             keep_outputs (bool): True to store the output binaries, False
508                 to delete them
509             work_in_output (bool): Use the output directory as the work
510                 directory and don't write to a separate output directory.
511         """
512         # If we think this might have been aborted with Ctrl-C, record the
513         # failure but not that we are 'done' with this board. A retry may fix
514         # it.
515         maybe_aborted = result.stderr and 'No child processes' in result.stderr
516
517         if result.return_code >= 0 and result.already_done:
518             return
519
520         # Write the output and stderr
521         output_dir = self.builder.get_output_dir(result.commit_upto)
522         mkdir(output_dir)
523         build_dir = self.builder.get_build_dir(result.commit_upto,
524                 result.brd.target)
525         mkdir(build_dir)
526
527         outfile = os.path.join(build_dir, 'log')
528         with open(outfile, 'w', encoding='utf-8') as outf:
529             if result.stdout:
530                 outf.write(result.stdout)
531
532         errfile = self.builder.get_err_file(result.commit_upto,
533                 result.brd.target)
534         if result.stderr:
535             with open(errfile, 'w', encoding='utf-8') as outf:
536                 outf.write(result.stderr)
537         elif os.path.exists(errfile):
538             os.remove(errfile)
539
540         # Fatal error
541         if result.return_code < 0:
542             return
543
544         if result.toolchain:
545             # Write the build result and toolchain information.
546             done_file = self.builder.get_done_file(result.commit_upto,
547                     result.brd.target)
548             with open(done_file, 'w', encoding='utf-8') as outf:
549                 if maybe_aborted:
550                     # Special code to indicate we need to retry
551                     outf.write(f'{RETURN_CODE_RETRY}')
552                 else:
553                     outf.write(f'{result.return_code}')
554             with open(os.path.join(build_dir, 'toolchain'), 'w',
555                       encoding='utf-8') as outf:
556                 print('gcc', result.toolchain.gcc, file=outf)
557                 print('path', result.toolchain.path, file=outf)
558                 print('cross', result.toolchain.cross, file=outf)
559                 print('arch', result.toolchain.arch, file=outf)
560                 outf.write(f'{result.return_code}')
561
562             # Write out the image and function size information and an objdump
563             env = result.toolchain.MakeEnvironment(self.builder.full_path)
564             with open(os.path.join(build_dir, 'out-env'), 'wb') as outf:
565                 for var in sorted(env.keys()):
566                     outf.write(b'%s="%s"' % (var, env[var]))
567
568             with open(os.path.join(build_dir, 'out-cmd'), 'w',
569                       encoding='utf-8') as outf:
570                 for cmd in result.cmd_list:
571                     print(' '.join(cmd), file=outf)
572
573             lines = []
574             for fname in BASE_ELF_FILENAMES:
575                 cmd = [f'{self.toolchain.cross}nm', '--size-sort', fname]
576                 nm_result = command.run_pipe([cmd], capture=True,
577                         capture_stderr=True, cwd=result.out_dir,
578                         raise_on_error=False, env=env)
579                 if nm_result.stdout:
580                     nm_fname = self.builder.get_func_sizes_file(
581                         result.commit_upto, result.brd.target, fname)
582                     with open(nm_fname, 'w', encoding='utf-8') as outf:
583                         print(nm_result.stdout, end=' ', file=outf)
584
585                 cmd = [f'{self.toolchain.cross}objdump', '-h', fname]
586                 dump_result = command.run_pipe([cmd], capture=True,
587                         capture_stderr=True, cwd=result.out_dir,
588                         raise_on_error=False, env=env)
589                 rodata_size = ''
590                 if dump_result.stdout:
591                     objdump = self.builder.get_objdump_file(result.commit_upto,
592                                     result.brd.target, fname)
593                     with open(objdump, 'w', encoding='utf-8') as outf:
594                         print(dump_result.stdout, end=' ', file=outf)
595                     for line in dump_result.stdout.splitlines():
596                         fields = line.split()
597                         if len(fields) > 5 and fields[1] == '.rodata':
598                             rodata_size = fields[2]
599
600                 cmd = [f'{self.toolchain.cross}size', fname]
601                 size_result = command.run_pipe([cmd], capture=True,
602                         capture_stderr=True, cwd=result.out_dir,
603                         raise_on_error=False, env=env)
604                 if size_result.stdout:
605                     lines.append(size_result.stdout.splitlines()[1] + ' ' +
606                                  rodata_size)
607
608             # Extract the environment from U-Boot and dump it out
609             cmd = [f'{self.toolchain.cross}objcopy', '-O', 'binary',
610                    '-j', '.rodata.default_environment',
611                    'env/built-in.o', 'uboot.env']
612             command.run_pipe([cmd], capture=True,
613                             capture_stderr=True, cwd=result.out_dir,
614                             raise_on_error=False, env=env)
615             if not work_in_output:
616                 copy_files(result.out_dir, build_dir, '', ['uboot.env'])
617
618             # Write out the image sizes file. This is similar to the output
619             # of binutil's 'size' utility, but it omits the header line and
620             # adds an additional hex value at the end of each line for the
621             # rodata size
622             if lines:
623                 sizes = self.builder.get_sizes_file(result.commit_upto,
624                                 result.brd.target)
625                 with open(sizes, 'w', encoding='utf-8') as outf:
626                     print('\n'.join(lines), file=outf)
627
628         if not work_in_output:
629             # Write out the configuration files, with a special case for SPL
630             for dirname in ['', 'spl', 'tpl']:
631                 copy_files(
632                     result.out_dir, build_dir, dirname,
633                     ['u-boot.cfg', 'spl/u-boot-spl.cfg', 'tpl/u-boot-tpl.cfg',
634                      '.config', 'include/autoconf.mk',
635                      'include/generated/autoconf.h'])
636
637             # Now write the actual build output
638             if keep_outputs:
639                 copy_files(
640                     result.out_dir, build_dir, '',
641                     ['u-boot*', '*.bin', '*.map', '*.img', 'MLO', 'SPL',
642                      'include/autoconf.mk', 'spl/u-boot-spl*'])
643
644     def _send_result(self, result):
645         """Send a result to the builder for processing
646
647         Args:
648             result (CommandResult): results of the build
649
650         Raises:
651             ValueError: self.test_exception is true (for testing)
652         """
653         if self.test_exception:
654             raise ValueError('test exception')
655         if self.thread_num != -1:
656             self.builder.out_queue.put(result)
657         else:
658             self.builder.process_result(result)
659
660     def run_job(self, job):
661         """Run a single job
662
663         A job consists of a building a list of commits for a particular board.
664
665         Args:
666             job (Job): Job to build
667
668         Raises:
669             ValueError: Thread was interrupted
670         """
671         brd = job.brd
672         work_dir = self.builder.get_thread_dir(self.thread_num)
673         self.toolchain = None
674         if job.commits:
675             # Run 'make board_defconfig' on the first commit
676             do_config = True
677             commit_upto  = 0
678             force_build = False
679             for commit_upto in range(0, len(job.commits), job.step):
680                 result, request_config = self.run_commit(commit_upto, brd,
681                         work_dir, do_config, self.builder.config_only,
682                         force_build or self.builder.force_build,
683                         self.builder.force_build_failures,
684                         job.work_in_output, job.adjust_cfg)
685                 failed = result.return_code or result.stderr
686                 did_config = do_config
687                 if failed and not do_config:
688                     # If our incremental build failed, try building again
689                     # with a reconfig.
690                     if self.builder.force_config_on_failure:
691                         result, request_config = self.run_commit(commit_upto,
692                             brd, work_dir, True, False, True, False,
693                             job.work_in_output, job.adjust_cfg)
694                         did_config = True
695                 if not self.builder.force_reconfig:
696                     do_config = request_config
697
698                 # If we built that commit, then config is done. But if we got
699                 # an warning, reconfig next time to force it to build the same
700                 # files that created warnings this time. Otherwise an
701                 # incremental build may not build the same file, and we will
702                 # think that the warning has gone away.
703                 # We could avoid this by using -Werror everywhere...
704                 # For errors, the problem doesn't happen, since presumably
705                 # the build stopped and didn't generate output, so will retry
706                 # that file next time. So we could detect warnings and deal
707                 # with them specially here. For now, we just reconfigure if
708                 # anything goes work.
709                 # Of course this is substantially slower if there are build
710                 # errors/warnings (e.g. 2-3x slower even if only 10% of builds
711                 # have problems).
712                 if (failed and not result.already_done and not did_config and
713                         self.builder.force_config_on_failure):
714                     # If this build failed, try the next one with a
715                     # reconfigure.
716                     # Sometimes if the board_config.h file changes it can mess
717                     # with dependencies, and we get:
718                     # make: *** No rule to make target `include/autoconf.mk',
719                     #     needed by `depend'.
720                     do_config = True
721                     force_build = True
722                 else:
723                     force_build = False
724                     if self.builder.force_config_on_failure:
725                         if failed:
726                             do_config = True
727                     result.commit_upto = commit_upto
728                     if result.return_code < 0:
729                         raise ValueError('Interrupt')
730
731                 # We have the build results, so output the result
732                 self._write_result(result, job.keep_outputs, job.work_in_output)
733                 self._send_result(result)
734         else:
735             # Just build the currently checked-out build
736             result, request_config = self.run_commit(None, brd, work_dir, True,
737                         self.builder.config_only, True,
738                         self.builder.force_build_failures, job.work_in_output,
739                         job.adjust_cfg)
740             result.commit_upto = 0
741             self._write_result(result, job.keep_outputs, job.work_in_output)
742             self._send_result(result)
743
744     def run(self):
745         """Our thread's run function
746
747         This thread picks a job from the queue, runs it, and then goes to the
748         next job.
749         """
750         while True:
751             job = self.builder.queue.get()
752             try:
753                 self.run_job(job)
754             except Exception as exc:
755                 print('Thread exception (use -T0 to run without threads):',
756                       exc)
757                 self.builder.thread_exceptions.append(exc)
758             self.builder.queue.task_done()