blob: 55d4d770c5cbc8951944efe2c65f3bce3af51a33 [file] [log] [blame]
Tom Rini83d290c2018-05-06 17:58:06 -04001# SPDX-License-Identifier: GPL-2.0+
Simon Glassfc3fe1c2013-04-03 11:07:16 +00002# Copyright (c) 2013 The Chromium OS Authors.
3#
Simon Glassfc3fe1c2013-04-03 11:07:16 +00004
Simon Glass9ef05b92023-07-19 17:48:30 -06005"""Control module for buildman
6
7This holds the main control logic for buildman, when not running tests.
8"""
9
Simon Glass5d679f82024-06-23 11:55:15 -060010import getpass
Simon Glassfc3fe1c2013-04-03 11:07:16 +000011import multiprocessing
12import os
Simon Glass883a3212014-09-05 19:00:18 -060013import shutil
Simon Glassfc3fe1c2013-04-03 11:07:16 +000014import sys
Simon Glass5d679f82024-06-23 11:55:15 -060015import tempfile
16import time
Simon Glassfc3fe1c2013-04-03 11:07:16 +000017
Simon Glassc52bd222022-07-11 19:04:03 -060018from buildman import boards
Simon Glass0ede00f2020-04-17 18:09:02 -060019from buildman import bsettings
Simon Glass2b4806e2022-01-22 05:07:33 -070020from buildman import cfgutil
Simon Glass0ede00f2020-04-17 18:09:02 -060021from buildman import toolchain
22from buildman.builder import Builder
Simon Glassbf776672020-04-17 18:09:04 -060023from patman import gitutil
24from patman import patchstream
Simon Glass4583c002023-02-23 18:18:04 -070025from u_boot_pylib import command
26from u_boot_pylib import terminal
Simon Glass5d679f82024-06-23 11:55:15 -060027from u_boot_pylib import tools
28from u_boot_pylib.terminal import print_clear, tprint
Simon Glassfc3fe1c2013-04-03 11:07:16 +000029
Simon Glassb8be2bd2023-07-19 17:48:31 -060030TEST_BUILDER = None
31
Simon Glass5d679f82024-06-23 11:55:15 -060032# Space-separated list of buildman process IDs currently running jobs
33RUNNING_FNAME = f'buildmanq.{getpass.getuser()}'
34
35# Lock file for access to RUNNING_FILE
36LOCK_FNAME = f'{RUNNING_FNAME}.lock'
37
38# Wait time for access to lock (seconds)
39LOCK_WAIT_S = 10
40
41# Wait time to start running
42RUN_WAIT_S = 300
43
Simon Glass9ef05b92023-07-19 17:48:30 -060044def get_plural(count):
Simon Glassfc3fe1c2013-04-03 11:07:16 +000045 """Returns a plural 's' if count is not 1"""
46 return 's' if count != 1 else ''
47
Simon Glass1d0c55d2023-07-19 17:49:00 -060048
49def count_build_commits(commits, step):
50 """Calculate the number of commits to be built
51
52 Args:
53 commits (list of Commit): Commits to build or None
54 step (int): Step value for commits, typically 1
55
56 Returns:
57 Number of commits that will be built
58 """
59 if commits:
60 count = len(commits)
61 return (count + step - 1) // step
62 return 0
63
64
65def get_action_summary(is_summary, commit_count, selected, threads, jobs):
Simon Glassfc3fe1c2013-04-03 11:07:16 +000066 """Return a string summarising the intended action.
67
Simon Glass1d3a5a52023-07-19 17:48:45 -060068 Args:
69 is_summary (bool): True if this is a summary (otherwise it is building)
70 commits (list): List of commits being built
71 selected (list of Board): List of Board objects that are marked
72 step (int): Step increment through commits
73 threads (int): Number of processor threads being used
74 jobs (int): Number of jobs to build at once
75
Simon Glassfc3fe1c2013-04-03 11:07:16 +000076 Returns:
77 Summary string.
78 """
Simon Glass1d0c55d2023-07-19 17:49:00 -060079 if commit_count:
80 commit_str = f'{commit_count} commit{get_plural(commit_count)}'
Simon Glassfea58582014-08-09 15:32:59 -060081 else:
82 commit_str = 'current source'
Simon Glassb8be2bd2023-07-19 17:48:31 -060083 msg = (f"{'Summary of' if is_summary else 'Building'} "
84 f'{commit_str} for {len(selected)} boards')
Simon Glass1d3a5a52023-07-19 17:48:45 -060085 msg += (f' ({threads} thread{get_plural(threads)}, '
86 f'{jobs} job{get_plural(jobs)} per thread)')
Simon Glassb8be2bd2023-07-19 17:48:31 -060087 return msg
Simon Glassfc3fe1c2013-04-03 11:07:16 +000088
Simon Glassb8be2bd2023-07-19 17:48:31 -060089# pylint: disable=R0913
Simon Glass1b820ee2023-07-19 17:48:46 -060090def show_actions(series, why_selected, boards_selected, output_dir,
91 board_warnings, step, threads, jobs, verbose):
Simon Glassfc3fe1c2013-04-03 11:07:16 +000092 """Display a list of actions that we would take, if not a dry run.
93
94 Args:
95 series: Series object
96 why_selected: Dictionary where each key is a buildman argument
Simon Glass8d7523c2017-01-23 05:38:56 -070097 provided by the user, and the value is the list of boards
98 brought in by that argument. For example, 'arm' might bring
99 in 400 boards, so in this case the key would be 'arm' and
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000100 the value would be a list of board names.
101 boards_selected: Dict of selected boards, key is target name,
102 value is Board object
Simon Glassd233dfb2023-07-19 17:48:36 -0600103 output_dir (str): Output directory for builder
Simon Glass06890362018-06-11 23:26:46 -0600104 board_warnings: List of warnings obtained from board selected
Simon Glass1b820ee2023-07-19 17:48:46 -0600105 step (int): Step increment through commits
106 threads (int): Number of processor threads being used
107 jobs (int): Number of jobs to build at once
108 verbose (bool): True to indicate why each board was selected
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000109 """
110 col = terminal.Color()
Simon Glassc05aa032019-10-31 07:42:53 -0600111 print('Dry run, so not doing much. But I would do this:')
112 print()
Simon Glassfea58582014-08-09 15:32:59 -0600113 if series:
114 commits = series.commits
115 else:
116 commits = None
Simon Glass1d0c55d2023-07-19 17:49:00 -0600117 print(get_action_summary(False, count_build_commits(commits, step),
118 boards_selected, threads, jobs))
Simon Glassd233dfb2023-07-19 17:48:36 -0600119 print(f'Build directory: {output_dir}')
Simon Glassfea58582014-08-09 15:32:59 -0600120 if commits:
Simon Glass1b820ee2023-07-19 17:48:46 -0600121 for upto in range(0, len(series.commits), step):
Simon Glassfea58582014-08-09 15:32:59 -0600122 commit = series.commits[upto]
Simon Glass252ac582022-01-29 14:14:17 -0700123 print(' ', col.build(col.YELLOW, commit.hash[:8], bright=False), end=' ')
Simon Glassc05aa032019-10-31 07:42:53 -0600124 print(commit.subject)
125 print()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000126 for arg in why_selected:
Simon Glasse8effc32024-07-11 09:10:04 +0100127 # When -x is used, only the 'all' member exists
128 if arg != 'all' or len(why_selected) == 1:
Simon Glassb8be2bd2023-07-19 17:48:31 -0600129 print(arg, f': {len(why_selected[arg])} boards')
Simon Glass1b820ee2023-07-19 17:48:46 -0600130 if verbose:
Simon Glassb8be2bd2023-07-19 17:48:31 -0600131 print(f" {' '.join(why_selected[arg])}")
132 print('Total boards to build for each '
133 f"commit: {len(why_selected['all'])}\n")
Simon Glass06890362018-06-11 23:26:46 -0600134 if board_warnings:
135 for warning in board_warnings:
Simon Glass252ac582022-01-29 14:14:17 -0700136 print(col.build(col.YELLOW, warning))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000137
Simon Glass9ef05b92023-07-19 17:48:30 -0600138def show_toolchain_prefix(brds, toolchains):
Simon Glass57cb9d52019-12-05 15:59:14 -0700139 """Show information about a the tool chain used by one or more boards
140
Simon Glass4e9162d2020-03-18 09:42:47 -0600141 The function checks that all boards use the same toolchain, then prints
142 the correct value for CROSS_COMPILE.
Simon Glass57cb9d52019-12-05 15:59:14 -0700143
144 Args:
145 boards: Boards object containing selected boards
146 toolchains: Toolchains object containing available toolchains
Simon Glass57cb9d52019-12-05 15:59:14 -0700147
148 Return:
149 None on success, string error message otherwise
150 """
Simon Glass6014db62022-07-11 19:04:02 -0600151 board_selected = brds.get_selected_dict()
Simon Glass57cb9d52019-12-05 15:59:14 -0700152 tc_set = set()
Simon Glasscc2c0d12022-07-11 19:04:00 -0600153 for brd in board_selected.values():
Simon Glass57cb9d52019-12-05 15:59:14 -0700154 tc_set.add(toolchains.Select(brd.arch))
155 if len(tc_set) != 1:
Simon Glassea782332023-07-19 17:48:56 -0600156 sys.exit('Supplied boards must share one toolchain')
Simon Glassb8be2bd2023-07-19 17:48:31 -0600157 tchain = tc_set.pop()
158 print(tchain.GetEnvArgs(toolchain.VAR_CROSS_COMPILE))
Simon Glass57cb9d52019-12-05 15:59:14 -0700159
Simon Glassad037872023-07-19 17:49:28 -0600160def show_arch(brds):
161 """Show information about a the architecture used by one or more boards
162
163 The function checks that all boards use the same architecture, then prints
164 the correct value for ARCH.
165
166 Args:
167 boards: Boards object containing selected boards
168
169 Return:
170 None on success, string error message otherwise
171 """
172 board_selected = brds.get_selected_dict()
173 arch_set = set()
174 for brd in board_selected.values():
175 arch_set.add(brd.arch)
176 if len(arch_set) != 1:
177 sys.exit('Supplied boards must share one arch')
178 print(arch_set.pop())
179
Tom Rinid7713ad2022-11-09 19:14:53 -0700180def get_allow_missing(opt_allow, opt_no_allow, num_selected, has_branch):
Simon Glassb8be2bd2023-07-19 17:48:31 -0600181 """Figure out whether to allow external blobs
182
183 Uses the allow-missing setting and the provided arguments to decide whether
184 missing external blobs should be allowed
185
186 Args:
187 opt_allow (bool): True if --allow-missing flag is set
188 opt_no_allow (bool): True if --no-allow-missing flag is set
189 num_selected (int): Number of selected board
190 has_branch (bool): True if a git branch (to build) has been provided
191
192 Returns:
193 bool: True to allow missing external blobs, False to produce an error if
194 external blobs are used
195 """
Tom Rinid7713ad2022-11-09 19:14:53 -0700196 allow_missing = False
Simon Glass42d42cf2023-07-19 17:49:05 -0600197 am_setting = bsettings.get_global_item_value('allow-missing')
Tom Rinid7713ad2022-11-09 19:14:53 -0700198 if am_setting:
199 if am_setting == 'always':
200 allow_missing = True
201 if 'multiple' in am_setting and num_selected > 1:
202 allow_missing = True
203 if 'branch' in am_setting and has_branch:
204 allow_missing = True
205
206 if opt_allow:
207 allow_missing = True
208 if opt_no_allow:
209 allow_missing = False
210 return allow_missing
211
Simon Glassd230c012023-07-19 17:48:33 -0600212
Simon Glassaeb23812023-07-19 17:48:48 -0600213def count_commits(branch, count, col, git_dir):
214 """Could the number of commits in the branch/ranch being built
215
216 Args:
217 branch (str): Name of branch to build, or None if none
218 count (int): Number of commits to build, or -1 for all
219 col (Terminal.Color): Color object to use
220 git_dir (str): Git directory to use, e.g. './.git'
221
222 Returns:
223 tuple:
224 Number of commits being built
225 True if the 'branch' string contains a range rather than a simple
226 name
227 """
228 has_range = branch and '..' in branch
229 if count == -1:
230 if not branch:
231 count = 1
232 else:
233 if has_range:
234 count, msg = gitutil.count_commits_in_range(git_dir, branch)
235 else:
236 count, msg = gitutil.count_commits_in_branch(git_dir, branch)
237 if count is None:
238 sys.exit(col.build(col.RED, msg))
239 elif count == 0:
240 sys.exit(col.build(col.RED,
241 f"Range '{branch}' has no commits"))
242 if msg:
243 print(col.build(col.YELLOW, msg))
244 count += 1 # Build upstream commit also
245
246 if not count:
247 msg = (f"No commits found to process in branch '{branch}': "
248 "set branch's upstream or use -c flag")
249 sys.exit(col.build(col.RED, msg))
250 return count, has_range
251
252
Simon Glass9df59e42023-07-19 17:48:40 -0600253def determine_series(selected, col, git_dir, count, branch, work_in_output):
Simon Glassd230c012023-07-19 17:48:33 -0600254 """Determine the series which is to be built, if any
255
Simon Glass6378bad2023-07-19 17:48:50 -0600256 If there is a series, the commits in that series are numbered by setting
257 their sequence value (starting from 0). This is used by tests.
258
Simon Glassd230c012023-07-19 17:48:33 -0600259 Args:
Simon Glass1d3a5a52023-07-19 17:48:45 -0600260 selected (list of Board): List of Board objects that are marked
Simon Glass9df59e42023-07-19 17:48:40 -0600261 selected
262 col (Terminal.Color): Color object to use
Simon Glassd230c012023-07-19 17:48:33 -0600263 git_dir (str): Git directory to use, e.g. './.git'
Simon Glass9df59e42023-07-19 17:48:40 -0600264 count (int): Number of commits in branch
265 branch (str): Name of branch to build, or None if none
266 work_in_output (bool): True to work in the output directory
Simon Glassd230c012023-07-19 17:48:33 -0600267
268 Returns:
269 Series: Series to build, or None for none
270
271 Read the metadata from the commits. First look at the upstream commit,
272 then the ones in the branch. We would like to do something like
273 upstream/master~..branch but that isn't possible if upstream/master is
274 a merge commit (it will list all the commits that form part of the
275 merge)
276
277 Conflicting tags are not a problem for buildman, since it does not use
278 them. For example, Series-version is not useful for buildman. On the
279 other hand conflicting tags will cause an error. So allow later tags
280 to overwrite earlier ones by setting allow_overwrite=True
281 """
Simon Glass9df59e42023-07-19 17:48:40 -0600282
283 # Work out how many commits to build. We want to build everything on the
284 # branch. We also build the upstream commit as a control so we can see
285 # problems introduced by the first commit on the branch.
Simon Glassaeb23812023-07-19 17:48:48 -0600286 count, has_range = count_commits(branch, count, col, git_dir)
Simon Glass9df59e42023-07-19 17:48:40 -0600287 if work_in_output:
288 if len(selected) != 1:
289 sys.exit(col.build(col.RED,
290 '-w can only be used with a single board'))
291 if count != 1:
292 sys.exit(col.build(col.RED,
293 '-w can only be used with a single commit'))
294
Simon Glassd230c012023-07-19 17:48:33 -0600295 if branch:
296 if count == -1:
297 if has_range:
298 range_expr = branch
299 else:
300 range_expr = gitutil.get_range_in_branch(git_dir, branch)
301 upstream_commit = gitutil.get_upstream(git_dir, branch)
302 series = patchstream.get_metadata_for_list(upstream_commit,
303 git_dir, 1, series=None, allow_overwrite=True)
304
305 series = patchstream.get_metadata_for_list(range_expr,
306 git_dir, None, series, allow_overwrite=True)
307 else:
308 # Honour the count
309 series = patchstream.get_metadata_for_list(branch,
310 git_dir, count, series=None, allow_overwrite=True)
Simon Glass6378bad2023-07-19 17:48:50 -0600311
312 # Number the commits for test purposes
313 for i, commit in enumerate(series.commits):
314 commit.sequence = i
Simon Glassd230c012023-07-19 17:48:33 -0600315 else:
316 series = None
317 return series
318
319
Simon Glassf7a36d52023-07-19 17:48:34 -0600320def do_fetch_arch(toolchains, col, fetch_arch):
321 """Handle the --fetch-arch option
322
323 Args:
324 toolchains (Toolchains): Tool chains to use
325 col (terminal.Color): Color object to build
326 fetch_arch (str): Argument passed to the --fetch-arch option
327
328 Returns:
329 int: Return code for buildman
330 """
331 if fetch_arch == 'list':
332 sorted_list = toolchains.ListArchs()
333 print(col.build(
334 col.BLUE,
335 f"Available architectures: {' '.join(sorted_list)}\n"))
336 return 0
337
338 if fetch_arch == 'all':
339 fetch_arch = ','.join(toolchains.ListArchs())
340 print(col.build(col.CYAN,
341 f'\nDownloading toolchains: {fetch_arch}'))
342 for arch in fetch_arch.split(','):
343 print()
344 ret = toolchains.FetchAndInstall(arch)
345 if ret:
346 return ret
347 return 0
348
349
Simon Glassb8680642023-07-19 17:48:42 -0600350def get_toolchains(toolchains, col, override_toolchain, fetch_arch,
351 list_tool_chains, verbose):
352 """Get toolchains object to use
353
354 Args:
355 toolchains (Toolchains or None): Toolchains to use. If None, then a
356 Toolchains object will be created and scanned
357 col (Terminal.Color): Color object
358 override_toolchain (str or None): Override value for toolchain, or None
359 fetch_arch (bool): True to fetch the toolchain for the architectures
360 list_tool_chains (bool): True to list all tool chains
361 verbose (bool): True for verbose output when listing toolchains
362
363 Returns:
364 Either:
365 int: Operation completed and buildman should exit with exit code
366 Toolchains: Toolchains object to use
367 """
368 no_toolchains = toolchains is None
369 if no_toolchains:
370 toolchains = toolchain.Toolchains(override_toolchain)
371
372 if fetch_arch:
373 return do_fetch_arch(toolchains, col, fetch_arch)
374
375 if no_toolchains:
376 toolchains.GetSettings()
377 toolchains.Scan(list_tool_chains and verbose)
378 if list_tool_chains:
379 toolchains.List()
380 print()
381 return 0
382 return toolchains
383
384
Simon Glassba8d0992023-07-19 17:49:30 -0600385def get_boards_obj(output_dir, regen_board_list, maintainer_check, full_check,
386 threads, verbose):
Simon Glass180c7182023-07-19 17:48:41 -0600387 """Object the Boards object to use
388
389 Creates the output directory and ensures there is a boards.cfg file, then
390 read it in.
391
392 Args:
393 output_dir (str): Output directory to use
394 regen_board_list (bool): True to just regenerate the board list
395 maintainer_check (bool): True to just run a maintainer check
Simon Glassba8d0992023-07-19 17:49:30 -0600396 full_check (bool): True to just run a full check of Kconfig and
397 maintainers
Simon Glass180c7182023-07-19 17:48:41 -0600398 threads (int or None): Number of threads to use to create boards file
399 verbose (bool): False to suppress output from boards-file generation
400
401 Returns:
402 Either:
403 int: Operation completed and buildman should exit with exit code
404 Boards: Boards object to use
405 """
406 brds = boards.Boards()
407 nr_cpus = threads or multiprocessing.cpu_count()
Simon Glassba8d0992023-07-19 17:49:30 -0600408 if maintainer_check or full_check:
409 warnings = brds.build_board_list(jobs=nr_cpus,
410 warn_targets=full_check)[1]
Simon Glass180c7182023-07-19 17:48:41 -0600411 if warnings:
412 for warn in warnings:
413 print(warn, file=sys.stderr)
414 return 2
415 return 0
416
417 if not os.path.exists(output_dir):
418 os.makedirs(output_dir)
419 board_file = os.path.join(output_dir, 'boards.cfg')
420 if regen_board_list and regen_board_list != '-':
421 board_file = regen_board_list
422
423 okay = brds.ensure_board_list(board_file, nr_cpus, force=regen_board_list,
424 quiet=not verbose)
425 if regen_board_list:
426 return 0 if okay else 2
427 brds.read_boards(board_file)
428 return brds
429
430
Simon Glass0d4874f2023-07-19 17:48:39 -0600431def determine_boards(brds, args, col, opt_boards, exclude_list):
432 """Determine which boards to build
433
434 Each element of args and exclude can refer to a board name, arch or SoC
435
436 Args:
437 brds (Boards): Boards object
438 args (list of str): Arguments describing boards to build
439 col (Terminal.Color): Color object
440 opt_boards (list of str): Specific boards to build, or None for all
441 exclude_list (list of str): Arguments describing boards to exclude
442
443 Returns:
444 tuple:
445 list of Board: List of Board objects that are marked selected
446 why_selected: Dictionary where each key is a buildman argument
447 provided by the user, and the value is the list of boards
448 brought in by that argument. For example, 'arm' might bring
449 in 400 boards, so in this case the key would be 'arm' and
450 the value would be a list of board names.
451 board_warnings: List of warnings obtained from board selected
452 """
453 exclude = []
454 if exclude_list:
455 for arg in exclude_list:
456 exclude += arg.split(',')
457
458 if opt_boards:
459 requested_boards = []
460 for brd in opt_boards:
461 requested_boards += brd.split(',')
462 else:
463 requested_boards = None
464 why_selected, board_warnings = brds.select_boards(args, exclude,
465 requested_boards)
466 selected = brds.get_selected()
467 if not selected:
468 sys.exit(col.build(col.RED, 'No matching boards found'))
469 return selected, why_selected, board_warnings
470
471
Simon Glass529957c2023-07-19 17:49:04 -0600472def adjust_args(args, series, selected):
473 """Adjust arguments according to various constraints
Simon Glass168d7922023-07-19 17:48:47 -0600474
475 Updates verbose, show_errors, threads, jobs and step
476
477 Args:
Simon Glass529957c2023-07-19 17:49:04 -0600478 args (Namespace): Namespace object to adjust
Simon Glass168d7922023-07-19 17:48:47 -0600479 series (Series): Series being built / summarised
480 selected (list of Board): List of Board objects that are marked
481 """
Simon Glass529957c2023-07-19 17:49:04 -0600482 if not series and not args.dry_run:
483 args.verbose = True
484 if not args.summary:
485 args.show_errors = True
Simon Glass168d7922023-07-19 17:48:47 -0600486
487 # By default we have one thread per CPU. But if there are not enough jobs
488 # we can have fewer threads and use a high '-j' value for make.
Simon Glass529957c2023-07-19 17:49:04 -0600489 if args.threads is None:
490 args.threads = min(multiprocessing.cpu_count(), len(selected))
491 if not args.jobs:
492 args.jobs = max(1, (multiprocessing.cpu_count() +
Simon Glass168d7922023-07-19 17:48:47 -0600493 len(selected) - 1) // len(selected))
494
Simon Glass529957c2023-07-19 17:49:04 -0600495 if not args.step:
496 args.step = len(series.commits) - 1
Simon Glass168d7922023-07-19 17:48:47 -0600497
Simon Glass4ec76822023-07-19 17:48:53 -0600498 # We can't show function sizes without board details at present
Simon Glass529957c2023-07-19 17:49:04 -0600499 if args.show_bloat:
500 args.show_detail = True
Simon Glass4ec76822023-07-19 17:48:53 -0600501
Simon Glasse48b9462023-07-19 17:48:49 -0600502
503def setup_output_dir(output_dir, work_in_output, branch, no_subdirs, col,
504 clean_dir):
505 """Set up the output directory
506
507 Args:
508 output_dir (str): Output directory provided by the user, or None if none
509 work_in_output (bool): True to work in the output directory
510 branch (str): Name of branch to build, or None if none
511 no_subdirs (bool): True to put the output in the top-level output dir
512 clean_dir: Used for tests only, indicates that the existing output_dir
513 should be removed before starting the build
514
515 Returns:
516 str: Updated output directory pathname
517 """
518 if not output_dir:
519 if work_in_output:
520 sys.exit(col.build(col.RED, '-w requires that you specify -o'))
521 output_dir = '..'
522 if branch and not no_subdirs:
523 # As a special case allow the board directory to be placed in the
524 # output directory itself rather than any subdirectory.
525 dirname = branch.replace('/', '_')
526 output_dir = os.path.join(output_dir, dirname)
527 if clean_dir and os.path.exists(output_dir):
528 shutil.rmtree(output_dir)
529 return output_dir
530
Simon Glassa659b8d2023-07-19 17:48:57 -0600531
Simon Glass529957c2023-07-19 17:49:04 -0600532def run_builder(builder, commits, board_selected, args):
Simon Glass68f917c2023-07-19 17:48:54 -0600533 """Run the builder or show the summary
534
535 Args:
536 commits (list of Commit): List of commits being built, None if no branch
537 boards_selected (dict): Dict of selected boards:
538 key: target name
539 value: Board object
Simon Glass529957c2023-07-19 17:49:04 -0600540 args (Namespace): Namespace to use
Simon Glass68f917c2023-07-19 17:48:54 -0600541
542 Returns:
543 int: Return code for buildman
544 """
Simon Glass529957c2023-07-19 17:49:04 -0600545 gnu_make = command.output(os.path.join(args.git,
Simon Glassa659b8d2023-07-19 17:48:57 -0600546 'scripts/show-gnu-make'), raise_on_error=False).rstrip()
547 if not gnu_make:
548 sys.exit('GNU Make not found')
549 builder.gnu_make = gnu_make
550
Simon Glass529957c2023-07-19 17:49:04 -0600551 if not args.ide:
552 commit_count = count_build_commits(commits, args.step)
553 tprint(get_action_summary(args.summary, commit_count, board_selected,
554 args.threads, args.jobs))
Simon Glass68f917c2023-07-19 17:48:54 -0600555
Simon Glass37edf5f2023-07-19 17:49:06 -0600556 builder.set_display_options(
557 args.show_errors, args.show_sizes, args.show_detail, args.show_bloat,
558 args.list_error_boards, args.show_config, args.show_environment,
559 args.filter_dtb_warnings, args.filter_migration_warnings, args.ide)
Simon Glass529957c2023-07-19 17:49:04 -0600560 if args.summary:
Simon Glass37edf5f2023-07-19 17:49:06 -0600561 builder.show_summary(commits, board_selected)
Simon Glass68f917c2023-07-19 17:48:54 -0600562 else:
Simon Glass37edf5f2023-07-19 17:49:06 -0600563 fail, warned, excs = builder.build_boards(
Simon Glass529957c2023-07-19 17:49:04 -0600564 commits, board_selected, args.keep_outputs, args.verbose)
Simon Glass68f917c2023-07-19 17:48:54 -0600565 if excs:
566 return 102
567 if fail:
568 return 100
Simon Glass529957c2023-07-19 17:49:04 -0600569 if warned and not args.ignore_warnings:
Simon Glass68f917c2023-07-19 17:48:54 -0600570 return 101
571 return 0
Simon Glasse48b9462023-07-19 17:48:49 -0600572
Simon Glass75584e12023-07-19 17:48:58 -0600573
574def calc_adjust_cfg(adjust_cfg, reproducible_builds):
575 """Calculate the value to use for adjust_cfg
576
577 Args:
578 adjust_cfg (list of str): List of configuration changes. See cfgutil for
579 details
580 reproducible_builds (bool): True to adjust the configuration to get
581 reproduceable builds
582
583 Returns:
584 adjust_cfg (list of str): List of configuration changes
585 """
586 adjust_cfg = cfgutil.convert_list_to_dict(adjust_cfg)
587
588 # Drop LOCALVERSION_AUTO since it changes the version string on every commit
589 if reproducible_builds:
590 # If these are mentioned, leave the local version alone
591 if 'LOCALVERSION' in adjust_cfg or 'LOCALVERSION_AUTO' in adjust_cfg:
592 print('Not dropping LOCALVERSION_AUTO for reproducible build')
593 else:
594 adjust_cfg['LOCALVERSION_AUTO'] = '~'
595 return adjust_cfg
596
597
Simon Glass5d679f82024-06-23 11:55:15 -0600598def read_procs(tmpdir=tempfile.gettempdir()):
599 """Read the list of running buildman processes
600
601 If the list is corrupted, returns an empty list
602
603 Args:
604 tmpdir (str): Temporary directory to use (for testing only)
605 """
606 running_fname = os.path.join(tmpdir, RUNNING_FNAME)
607 procs = []
608 if os.path.exists(running_fname):
609 items = tools.read_file(running_fname, binary=False).split()
610 try:
611 procs = [int(x) for x in items]
612 except ValueError: # Handle invalid format
613 pass
614 return procs
615
616
617def check_pid(pid):
618 """Check for existence of a unix PID
619
620 https://stackoverflow.com/questions/568271/how-to-check-if-there-exists-a-process-with-a-given-pid-in-python
621
622 Args:
623 pid (int): PID to check
624
625 Returns:
626 True if it exists, else False
627 """
628 try:
629 os.kill(pid, 0)
630 except OSError:
631 return False
632 else:
633 return True
634
635
636def write_procs(procs, tmpdir=tempfile.gettempdir()):
637 """Write the list of running buildman processes
638
639 Args:
640 tmpdir (str): Temporary directory to use (for testing only)
641 """
642 running_fname = os.path.join(tmpdir, RUNNING_FNAME)
643 tools.write_file(running_fname, ' '.join([str(p) for p in procs]),
644 binary=False)
645
646 # Allow another user to access the file
647 os.chmod(running_fname, 0o666)
648
649def wait_for_process_limit(limit, tmpdir=tempfile.gettempdir(),
650 pid=os.getpid()):
651 """Wait until the number of buildman processes drops to the limit
652
653 This uses FileLock to protect a 'running' file, which contains a list of
654 PIDs of running buildman processes. The number of PIDs in the file indicates
655 the number of running processes.
656
657 When buildman starts up, it calls this function to wait until it is OK to
658 start the build.
659
660 On exit, no attempt is made to remove the PID from the file, since other
661 buildman processes will notice that the PID is no-longer valid, and ignore
662 it.
663
664 Two timeouts are provided:
665 LOCK_WAIT_S: length of time to wait for the lock; if this occurs, the
666 lock is busted / removed before trying again
667 RUN_WAIT_S: length of time to wait to be allowed to run; if this occurs,
668 the build starts, with the PID being added to the file.
669
670 Args:
671 limit (int): Maximum number of buildman processes, including this one;
672 must be > 0
673 tmpdir (str): Temporary directory to use (for testing only)
674 pid (int): Current process ID (for testing only)
675 """
676 from filelock import Timeout, FileLock
677
678 running_fname = os.path.join(tmpdir, RUNNING_FNAME)
679 lock_fname = os.path.join(tmpdir, LOCK_FNAME)
680 lock = FileLock(lock_fname)
681
682 # Allow another user to access the file
683 col = terminal.Color()
684 tprint('Waiting for other buildman processes...', newline=False,
685 colour=col.RED)
686
687 claimed = False
688 deadline = time.time() + RUN_WAIT_S
689 while True:
690 try:
691 with lock.acquire(timeout=LOCK_WAIT_S):
692 os.chmod(lock_fname, 0o666)
693 procs = read_procs(tmpdir)
694
695 # Drop PIDs which are not running
696 procs = list(filter(check_pid, procs))
697
698 # If we haven't hit the limit, add ourself
699 if len(procs) < limit:
700 tprint('done...', newline=False)
701 claimed = True
702 if time.time() >= deadline:
703 tprint('timeout...', newline=False)
704 claimed = True
705 if claimed:
706 write_procs(procs + [pid], tmpdir)
707 break
708
709 except Timeout:
710 tprint('failed to get lock: busting...', newline=False)
711 os.remove(lock_fname)
712
713 time.sleep(1)
714 tprint('starting build', newline=False)
715 print_clear()
716
Simon Glass529957c2023-07-19 17:49:04 -0600717def do_buildman(args, toolchains=None, make_func=None, brds=None,
Simon Glass9ef05b92023-07-19 17:48:30 -0600718 clean_dir=False, test_thread_exceptions=False):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000719 """The main control code for buildman
720
721 Args:
Simon Glass529957c2023-07-19 17:49:04 -0600722 args: ArgumentParser object
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000723 args: Command line arguments (list of strings)
Simon Glassd4144e42014-09-05 19:00:13 -0600724 toolchains: Toolchains to use - this should be a Toolchains()
725 object. If None, then it will be created and scanned
726 make_func: Make function to use for the builder. This is called
727 to execute 'make'. If this is None, the normal function
728 will be used, which calls the 'make' tool with suitable
729 arguments. This setting is useful for tests.
Simon Glasscc2c0d12022-07-11 19:04:00 -0600730 brds: Boards() object to use, containing a list of available
Simon Glass823e60b2014-09-05 19:00:16 -0600731 boards. If this is None it will be created and scanned.
Simon Glass24993312021-04-11 16:27:25 +1200732 clean_dir: Used for tests only, indicates that the existing output_dir
733 should be removed before starting the build
Simon Glass8116c782021-04-11 16:27:27 +1200734 test_thread_exceptions: Uses for tests only, True to make the threads
735 raise an exception instead of reporting their result. This simulates
736 a failure in the code somewhere
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000737 """
Simon Glassb8be2bd2023-07-19 17:48:31 -0600738 # Used so testing can obtain the builder: pylint: disable=W0603
739 global TEST_BUILDER
Simon Glass883a3212014-09-05 19:00:18 -0600740
Simon Glass0157b182022-01-29 14:14:11 -0700741 gitutil.setup()
Simon Glass713bea32016-07-27 20:33:02 -0600742 col = terminal.Color()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000743
Simon Glass529957c2023-07-19 17:49:04 -0600744 git_dir = os.path.join(args.git, '.git')
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000745
Simon Glass529957c2023-07-19 17:49:04 -0600746 toolchains = get_toolchains(toolchains, col, args.override_toolchain,
747 args.fetch_arch, args.list_tool_chains,
748 args.verbose)
Simon Glass1c81e082023-08-03 12:51:36 -0600749 if isinstance(toolchains, int):
750 return toolchains
751
Simon Glasse48b9462023-07-19 17:48:49 -0600752 output_dir = setup_output_dir(
Simon Glass529957c2023-07-19 17:49:04 -0600753 args.output_dir, args.work_in_output, args.branch,
754 args.no_subdirs, col, clean_dir)
Simon Glasseb70a2c2020-04-09 15:08:51 -0600755
Simon Glass7c66ead2019-12-05 15:59:13 -0700756 # Work out what subset of the boards we are building
Simon Glasscc2c0d12022-07-11 19:04:00 -0600757 if not brds:
Simon Glass529957c2023-07-19 17:49:04 -0600758 brds = get_boards_obj(output_dir, args.regen_board_list,
Simon Glassba8d0992023-07-19 17:49:30 -0600759 args.maintainer_check, args.full_check,
Simon Glass283dcb62023-09-07 10:00:18 -0600760 args.threads, args.verbose and
761 not args.print_arch and not args.print_prefix)
Simon Glass180c7182023-07-19 17:48:41 -0600762 if isinstance(brds, int):
763 return brds
Simon Glass7c66ead2019-12-05 15:59:13 -0700764
Simon Glass0d4874f2023-07-19 17:48:39 -0600765 selected, why_selected, board_warnings = determine_boards(
Simon Glass529957c2023-07-19 17:49:04 -0600766 brds, args.terms, col, args.boards, args.exclude)
Simon Glass7c66ead2019-12-05 15:59:13 -0700767
Simon Glass529957c2023-07-19 17:49:04 -0600768 if args.print_prefix:
Simon Glassea782332023-07-19 17:48:56 -0600769 show_toolchain_prefix(brds, toolchains)
Simon Glass57cb9d52019-12-05 15:59:14 -0700770 return 0
771
Simon Glassad037872023-07-19 17:49:28 -0600772 if args.print_arch:
773 show_arch(brds)
774 return 0
775
Simon Glass529957c2023-07-19 17:49:04 -0600776 series = determine_series(selected, col, git_dir, args.count,
777 args.branch, args.work_in_output)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000778
Simon Glass529957c2023-07-19 17:49:04 -0600779 adjust_args(args, series, selected)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000780
Simon Glass168d7922023-07-19 17:48:47 -0600781 # For a dry run, just show our actions as a sanity check
Simon Glass529957c2023-07-19 17:49:04 -0600782 if args.dry_run:
Simon Glass168d7922023-07-19 17:48:47 -0600783 show_actions(series, why_selected, selected, output_dir, board_warnings,
Simon Glass529957c2023-07-19 17:49:04 -0600784 args.step, args.threads, args.jobs,
785 args.verbose)
Simon Glass168d7922023-07-19 17:48:47 -0600786 return 0
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000787
Simon Glass529957c2023-07-19 17:49:04 -0600788 # Create a new builder with the selected args
Simon Glassd230c012023-07-19 17:48:33 -0600789 builder = Builder(toolchains, output_dir, git_dir,
Simon Glass529957c2023-07-19 17:49:04 -0600790 args.threads, args.jobs, checkout=True,
791 show_unknown=args.show_unknown, step=args.step,
Tom Rinie13fcae2024-07-05 14:34:07 -0600792 no_subdirs=args.no_subdirs, full_path=args.full_path,
793 verbose_build=args.verbose_build,
794 mrproper=args.mrproper,
795 fallback_mrproper=args.fallback_mrproper,
Simon Glass529957c2023-07-19 17:49:04 -0600796 per_board_out_dir=args.per_board_out_dir,
797 config_only=args.config_only,
798 squash_config_y=not args.preserve_config_y,
799 warnings_as_errors=args.warnings_as_errors,
800 work_in_output=args.work_in_output,
Simon Glass2b4806e2022-01-22 05:07:33 -0700801 test_thread_exceptions=test_thread_exceptions,
Simon Glass529957c2023-07-19 17:49:04 -0600802 adjust_cfg=calc_adjust_cfg(args.adjust_cfg,
803 args.reproducible_builds),
804 allow_missing=get_allow_missing(args.allow_missing,
805 args.no_allow_missing,
806 len(selected), args.branch),
807 no_lto=args.no_lto,
808 reproducible_builds=args.reproducible_builds,
809 force_build = args.force_build,
810 force_build_failures = args.force_build_failures,
811 force_reconfig = args.force_reconfig, in_tree = args.in_tree,
Simon Glassba134c32024-08-15 13:57:45 -0600812 force_config_on_failure=not args.quick, make_func=make_func,
813 dtc_skip=args.dtc_skip)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000814
Simon Glassffd06d32023-07-19 17:48:52 -0600815 TEST_BUILDER = builder
Simon Glassf0207d72023-07-19 17:48:37 -0600816
Simon Glass5d679f82024-06-23 11:55:15 -0600817 if args.process_limit:
818 wait_for_process_limit(args.process_limit)
819
Simon Glass985d7ae2023-07-19 17:48:55 -0600820 return run_builder(builder, series.commits if series else None,
Simon Glass529957c2023-07-19 17:49:04 -0600821 brds.get_selected_dict(), args)