builderthread.py 31 KB

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