blob: 464835c5be5c94c889543090a801cffe72cee6fa [file] [log] [blame]
Tom Rini10e47792018-05-06 17:58:06 -04001# SPDX-License-Identifier: GPL-2.0+
Simon Glassc05694f2013-04-03 11:07:16 +00002# Copyright (c) 2013 The Chromium OS Authors.
3#
Simon Glassc05694f2013-04-03 11:07:16 +00004
Simon Glassc1e1e1d2023-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 Glassc229d322024-06-23 11:55:15 -060010import getpass
Simon Glassc05694f2013-04-03 11:07:16 +000011import multiprocessing
12import os
Simon Glassa10ebe12014-09-05 19:00:18 -060013import shutil
Simon Glassc05694f2013-04-03 11:07:16 +000014import sys
Simon Glassc229d322024-06-23 11:55:15 -060015import tempfile
16import time
Simon Glassc05694f2013-04-03 11:07:16 +000017
Simon Glass20751d62022-07-11 19:04:03 -060018from buildman import boards
Simon Glassf0d9c102020-04-17 18:09:02 -060019from buildman import bsettings
Simon Glasse5650a82022-01-22 05:07:33 -070020from buildman import cfgutil
Simon Glassf0d9c102020-04-17 18:09:02 -060021from buildman import toolchain
22from buildman.builder import Builder
Simon Glassa997ea52020-04-17 18:09:04 -060023from patman import gitutil
24from patman import patchstream
Simon Glass131444f2023-02-23 18:18:04 -070025from u_boot_pylib import command
26from u_boot_pylib import terminal
Simon Glassc229d322024-06-23 11:55:15 -060027from u_boot_pylib import tools
28from u_boot_pylib.terminal import print_clear, tprint
Simon Glassc05694f2013-04-03 11:07:16 +000029
Simon Glassaf0e29f2023-07-19 17:48:31 -060030TEST_BUILDER = None
31
Simon Glassc229d322024-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 Glassc1e1e1d2023-07-19 17:48:30 -060044def get_plural(count):
Simon Glassc05694f2013-04-03 11:07:16 +000045 """Returns a plural 's' if count is not 1"""
46 return 's' if count != 1 else ''
47
Simon Glass14905d32023-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 Glassc05694f2013-04-03 11:07:16 +000066 """Return a string summarising the intended action.
67
Simon Glassf50a7282023-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 Glassc05694f2013-04-03 11:07:16 +000076 Returns:
77 Summary string.
78 """
Simon Glass14905d32023-07-19 17:49:00 -060079 if commit_count:
80 commit_str = f'{commit_count} commit{get_plural(commit_count)}'
Simon Glassd326ad72014-08-09 15:32:59 -060081 else:
82 commit_str = 'current source'
Simon Glassaf0e29f2023-07-19 17:48:31 -060083 msg = (f"{'Summary of' if is_summary else 'Building'} "
84 f'{commit_str} for {len(selected)} boards')
Simon Glassf50a7282023-07-19 17:48:45 -060085 msg += (f' ({threads} thread{get_plural(threads)}, '
86 f'{jobs} job{get_plural(jobs)} per thread)')
Simon Glassaf0e29f2023-07-19 17:48:31 -060087 return msg
Simon Glassc05694f2013-04-03 11:07:16 +000088
Simon Glassaf0e29f2023-07-19 17:48:31 -060089# pylint: disable=R0913
Simon Glass01c671d2023-07-19 17:48:46 -060090def show_actions(series, why_selected, boards_selected, output_dir,
91 board_warnings, step, threads, jobs, verbose):
Simon Glassc05694f2013-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 Glass6af145f2017-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 Glassc05694f2013-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 Glassf83559c2023-07-19 17:48:36 -0600103 output_dir (str): Output directory for builder
Simon Glassd9eb9f02018-06-11 23:26:46 -0600104 board_warnings: List of warnings obtained from board selected
Simon Glass01c671d2023-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 Glassc05694f2013-04-03 11:07:16 +0000109 """
110 col = terminal.Color()
Simon Glassc78ed662019-10-31 07:42:53 -0600111 print('Dry run, so not doing much. But I would do this:')
112 print()
Simon Glassd326ad72014-08-09 15:32:59 -0600113 if series:
114 commits = series.commits
115 else:
116 commits = None
Simon Glass14905d32023-07-19 17:49:00 -0600117 print(get_action_summary(False, count_build_commits(commits, step),
118 boards_selected, threads, jobs))
Simon Glassf83559c2023-07-19 17:48:36 -0600119 print(f'Build directory: {output_dir}')
Simon Glassd326ad72014-08-09 15:32:59 -0600120 if commits:
Simon Glass01c671d2023-07-19 17:48:46 -0600121 for upto in range(0, len(series.commits), step):
Simon Glassd326ad72014-08-09 15:32:59 -0600122 commit = series.commits[upto]
Simon Glassf45d3742022-01-29 14:14:17 -0700123 print(' ', col.build(col.YELLOW, commit.hash[:8], bright=False), end=' ')
Simon Glassc78ed662019-10-31 07:42:53 -0600124 print(commit.subject)
125 print()
Simon Glassc05694f2013-04-03 11:07:16 +0000126 for arg in why_selected:
127 if arg != 'all':
Simon Glassaf0e29f2023-07-19 17:48:31 -0600128 print(arg, f': {len(why_selected[arg])} boards')
Simon Glass01c671d2023-07-19 17:48:46 -0600129 if verbose:
Simon Glassaf0e29f2023-07-19 17:48:31 -0600130 print(f" {' '.join(why_selected[arg])}")
131 print('Total boards to build for each '
132 f"commit: {len(why_selected['all'])}\n")
Simon Glassd9eb9f02018-06-11 23:26:46 -0600133 if board_warnings:
134 for warning in board_warnings:
Simon Glassf45d3742022-01-29 14:14:17 -0700135 print(col.build(col.YELLOW, warning))
Simon Glassc05694f2013-04-03 11:07:16 +0000136
Simon Glassc1e1e1d2023-07-19 17:48:30 -0600137def show_toolchain_prefix(brds, toolchains):
Simon Glass48ac42e2019-12-05 15:59:14 -0700138 """Show information about a the tool chain used by one or more boards
139
Simon Glass2df44be2020-03-18 09:42:47 -0600140 The function checks that all boards use the same toolchain, then prints
141 the correct value for CROSS_COMPILE.
Simon Glass48ac42e2019-12-05 15:59:14 -0700142
143 Args:
144 boards: Boards object containing selected boards
145 toolchains: Toolchains object containing available toolchains
Simon Glass48ac42e2019-12-05 15:59:14 -0700146
147 Return:
148 None on success, string error message otherwise
149 """
Simon Glass127a2392022-07-11 19:04:02 -0600150 board_selected = brds.get_selected_dict()
Simon Glass48ac42e2019-12-05 15:59:14 -0700151 tc_set = set()
Simon Glass5df45222022-07-11 19:04:00 -0600152 for brd in board_selected.values():
Simon Glass48ac42e2019-12-05 15:59:14 -0700153 tc_set.add(toolchains.Select(brd.arch))
154 if len(tc_set) != 1:
Simon Glasseaf1be22023-07-19 17:48:56 -0600155 sys.exit('Supplied boards must share one toolchain')
Simon Glassaf0e29f2023-07-19 17:48:31 -0600156 tchain = tc_set.pop()
157 print(tchain.GetEnvArgs(toolchain.VAR_CROSS_COMPILE))
Simon Glass48ac42e2019-12-05 15:59:14 -0700158
Simon Glassa8a0ce72023-07-19 17:49:28 -0600159def show_arch(brds):
160 """Show information about a the architecture used by one or more boards
161
162 The function checks that all boards use the same architecture, then prints
163 the correct value for ARCH.
164
165 Args:
166 boards: Boards object containing selected boards
167
168 Return:
169 None on success, string error message otherwise
170 """
171 board_selected = brds.get_selected_dict()
172 arch_set = set()
173 for brd in board_selected.values():
174 arch_set.add(brd.arch)
175 if len(arch_set) != 1:
176 sys.exit('Supplied boards must share one arch')
177 print(arch_set.pop())
178
Tom Rini93ebd462022-11-09 19:14:53 -0700179def get_allow_missing(opt_allow, opt_no_allow, num_selected, has_branch):
Simon Glassaf0e29f2023-07-19 17:48:31 -0600180 """Figure out whether to allow external blobs
181
182 Uses the allow-missing setting and the provided arguments to decide whether
183 missing external blobs should be allowed
184
185 Args:
186 opt_allow (bool): True if --allow-missing flag is set
187 opt_no_allow (bool): True if --no-allow-missing flag is set
188 num_selected (int): Number of selected board
189 has_branch (bool): True if a git branch (to build) has been provided
190
191 Returns:
192 bool: True to allow missing external blobs, False to produce an error if
193 external blobs are used
194 """
Tom Rini93ebd462022-11-09 19:14:53 -0700195 allow_missing = False
Simon Glass06b83a52023-07-19 17:49:05 -0600196 am_setting = bsettings.get_global_item_value('allow-missing')
Tom Rini93ebd462022-11-09 19:14:53 -0700197 if am_setting:
198 if am_setting == 'always':
199 allow_missing = True
200 if 'multiple' in am_setting and num_selected > 1:
201 allow_missing = True
202 if 'branch' in am_setting and has_branch:
203 allow_missing = True
204
205 if opt_allow:
206 allow_missing = True
207 if opt_no_allow:
208 allow_missing = False
209 return allow_missing
210
Simon Glass2183b842023-07-19 17:48:33 -0600211
Simon Glassfdbff802023-07-19 17:48:48 -0600212def count_commits(branch, count, col, git_dir):
213 """Could the number of commits in the branch/ranch being built
214
215 Args:
216 branch (str): Name of branch to build, or None if none
217 count (int): Number of commits to build, or -1 for all
218 col (Terminal.Color): Color object to use
219 git_dir (str): Git directory to use, e.g. './.git'
220
221 Returns:
222 tuple:
223 Number of commits being built
224 True if the 'branch' string contains a range rather than a simple
225 name
226 """
227 has_range = branch and '..' in branch
228 if count == -1:
229 if not branch:
230 count = 1
231 else:
232 if has_range:
233 count, msg = gitutil.count_commits_in_range(git_dir, branch)
234 else:
235 count, msg = gitutil.count_commits_in_branch(git_dir, branch)
236 if count is None:
237 sys.exit(col.build(col.RED, msg))
238 elif count == 0:
239 sys.exit(col.build(col.RED,
240 f"Range '{branch}' has no commits"))
241 if msg:
242 print(col.build(col.YELLOW, msg))
243 count += 1 # Build upstream commit also
244
245 if not count:
246 msg = (f"No commits found to process in branch '{branch}': "
247 "set branch's upstream or use -c flag")
248 sys.exit(col.build(col.RED, msg))
249 return count, has_range
250
251
Simon Glass100196e2023-07-19 17:48:40 -0600252def determine_series(selected, col, git_dir, count, branch, work_in_output):
Simon Glass2183b842023-07-19 17:48:33 -0600253 """Determine the series which is to be built, if any
254
Simon Glass1b140492023-07-19 17:48:50 -0600255 If there is a series, the commits in that series are numbered by setting
256 their sequence value (starting from 0). This is used by tests.
257
Simon Glass2183b842023-07-19 17:48:33 -0600258 Args:
Simon Glassf50a7282023-07-19 17:48:45 -0600259 selected (list of Board): List of Board objects that are marked
Simon Glass100196e2023-07-19 17:48:40 -0600260 selected
261 col (Terminal.Color): Color object to use
262 git_dir (str): Git directory to use, e.g. './.git'
Simon Glass2183b842023-07-19 17:48:33 -0600263 count (int): Number of commits in branch
Simon Glass2183b842023-07-19 17:48:33 -0600264 branch (str): Name of branch to build, or None if none
Simon Glass100196e2023-07-19 17:48:40 -0600265 work_in_output (bool): True to work in the output directory
Simon Glass2183b842023-07-19 17:48:33 -0600266
267 Returns:
268 Series: Series to build, or None for none
269
270 Read the metadata from the commits. First look at the upstream commit,
271 then the ones in the branch. We would like to do something like
272 upstream/master~..branch but that isn't possible if upstream/master is
273 a merge commit (it will list all the commits that form part of the
274 merge)
275
276 Conflicting tags are not a problem for buildman, since it does not use
277 them. For example, Series-version is not useful for buildman. On the
278 other hand conflicting tags will cause an error. So allow later tags
279 to overwrite earlier ones by setting allow_overwrite=True
280 """
Simon Glass100196e2023-07-19 17:48:40 -0600281
282 # Work out how many commits to build. We want to build everything on the
283 # branch. We also build the upstream commit as a control so we can see
284 # problems introduced by the first commit on the branch.
Simon Glassfdbff802023-07-19 17:48:48 -0600285 count, has_range = count_commits(branch, count, col, git_dir)
Simon Glass100196e2023-07-19 17:48:40 -0600286 if work_in_output:
287 if len(selected) != 1:
288 sys.exit(col.build(col.RED,
289 '-w can only be used with a single board'))
290 if count != 1:
291 sys.exit(col.build(col.RED,
292 '-w can only be used with a single commit'))
293
Simon Glass2183b842023-07-19 17:48:33 -0600294 if branch:
295 if count == -1:
296 if has_range:
297 range_expr = branch
298 else:
299 range_expr = gitutil.get_range_in_branch(git_dir, branch)
300 upstream_commit = gitutil.get_upstream(git_dir, branch)
301 series = patchstream.get_metadata_for_list(upstream_commit,
302 git_dir, 1, series=None, allow_overwrite=True)
303
304 series = patchstream.get_metadata_for_list(range_expr,
305 git_dir, None, series, allow_overwrite=True)
306 else:
307 # Honour the count
308 series = patchstream.get_metadata_for_list(branch,
309 git_dir, count, series=None, allow_overwrite=True)
Simon Glass1b140492023-07-19 17:48:50 -0600310
311 # Number the commits for test purposes
312 for i, commit in enumerate(series.commits):
313 commit.sequence = i
Simon Glass2183b842023-07-19 17:48:33 -0600314 else:
315 series = None
316 return series
317
318
Simon Glass6c4beca2023-07-19 17:48:34 -0600319def do_fetch_arch(toolchains, col, fetch_arch):
320 """Handle the --fetch-arch option
321
322 Args:
323 toolchains (Toolchains): Tool chains to use
324 col (terminal.Color): Color object to build
325 fetch_arch (str): Argument passed to the --fetch-arch option
326
327 Returns:
328 int: Return code for buildman
329 """
330 if fetch_arch == 'list':
331 sorted_list = toolchains.ListArchs()
332 print(col.build(
333 col.BLUE,
334 f"Available architectures: {' '.join(sorted_list)}\n"))
335 return 0
336
337 if fetch_arch == 'all':
338 fetch_arch = ','.join(toolchains.ListArchs())
339 print(col.build(col.CYAN,
340 f'\nDownloading toolchains: {fetch_arch}'))
341 for arch in fetch_arch.split(','):
342 print()
343 ret = toolchains.FetchAndInstall(arch)
344 if ret:
345 return ret
346 return 0
347
348
Simon Glass3bd8e302023-07-19 17:48:42 -0600349def get_toolchains(toolchains, col, override_toolchain, fetch_arch,
350 list_tool_chains, verbose):
351 """Get toolchains object to use
352
353 Args:
354 toolchains (Toolchains or None): Toolchains to use. If None, then a
355 Toolchains object will be created and scanned
356 col (Terminal.Color): Color object
357 override_toolchain (str or None): Override value for toolchain, or None
358 fetch_arch (bool): True to fetch the toolchain for the architectures
359 list_tool_chains (bool): True to list all tool chains
360 verbose (bool): True for verbose output when listing toolchains
361
362 Returns:
363 Either:
364 int: Operation completed and buildman should exit with exit code
365 Toolchains: Toolchains object to use
366 """
367 no_toolchains = toolchains is None
368 if no_toolchains:
369 toolchains = toolchain.Toolchains(override_toolchain)
370
371 if fetch_arch:
372 return do_fetch_arch(toolchains, col, fetch_arch)
373
374 if no_toolchains:
375 toolchains.GetSettings()
376 toolchains.Scan(list_tool_chains and verbose)
377 if list_tool_chains:
378 toolchains.List()
379 print()
380 return 0
381 return toolchains
382
383
Simon Glass66d4c882023-07-19 17:49:30 -0600384def get_boards_obj(output_dir, regen_board_list, maintainer_check, full_check,
385 threads, verbose):
Simon Glassabcc11d2023-07-19 17:48:41 -0600386 """Object the Boards object to use
387
388 Creates the output directory and ensures there is a boards.cfg file, then
389 read it in.
390
391 Args:
392 output_dir (str): Output directory to use
393 regen_board_list (bool): True to just regenerate the board list
394 maintainer_check (bool): True to just run a maintainer check
Simon Glass66d4c882023-07-19 17:49:30 -0600395 full_check (bool): True to just run a full check of Kconfig and
396 maintainers
Simon Glassabcc11d2023-07-19 17:48:41 -0600397 threads (int or None): Number of threads to use to create boards file
398 verbose (bool): False to suppress output from boards-file generation
399
400 Returns:
401 Either:
402 int: Operation completed and buildman should exit with exit code
403 Boards: Boards object to use
404 """
405 brds = boards.Boards()
406 nr_cpus = threads or multiprocessing.cpu_count()
Simon Glass66d4c882023-07-19 17:49:30 -0600407 if maintainer_check or full_check:
408 warnings = brds.build_board_list(jobs=nr_cpus,
409 warn_targets=full_check)[1]
Simon Glassabcc11d2023-07-19 17:48:41 -0600410 if warnings:
411 for warn in warnings:
412 print(warn, file=sys.stderr)
413 return 2
414 return 0
415
416 if not os.path.exists(output_dir):
417 os.makedirs(output_dir)
418 board_file = os.path.join(output_dir, 'boards.cfg')
419 if regen_board_list and regen_board_list != '-':
420 board_file = regen_board_list
421
422 okay = brds.ensure_board_list(board_file, nr_cpus, force=regen_board_list,
423 quiet=not verbose)
424 if regen_board_list:
425 return 0 if okay else 2
426 brds.read_boards(board_file)
427 return brds
428
429
Simon Glass72f8bff2023-07-19 17:48:39 -0600430def determine_boards(brds, args, col, opt_boards, exclude_list):
431 """Determine which boards to build
432
433 Each element of args and exclude can refer to a board name, arch or SoC
434
435 Args:
436 brds (Boards): Boards object
437 args (list of str): Arguments describing boards to build
438 col (Terminal.Color): Color object
439 opt_boards (list of str): Specific boards to build, or None for all
440 exclude_list (list of str): Arguments describing boards to exclude
441
442 Returns:
443 tuple:
444 list of Board: List of Board objects that are marked selected
445 why_selected: Dictionary where each key is a buildman argument
446 provided by the user, and the value is the list of boards
447 brought in by that argument. For example, 'arm' might bring
448 in 400 boards, so in this case the key would be 'arm' and
449 the value would be a list of board names.
450 board_warnings: List of warnings obtained from board selected
451 """
452 exclude = []
453 if exclude_list:
454 for arg in exclude_list:
455 exclude += arg.split(',')
456
457 if opt_boards:
458 requested_boards = []
459 for brd in opt_boards:
460 requested_boards += brd.split(',')
461 else:
462 requested_boards = None
463 why_selected, board_warnings = brds.select_boards(args, exclude,
464 requested_boards)
465 selected = brds.get_selected()
466 if not selected:
467 sys.exit(col.build(col.RED, 'No matching boards found'))
468 return selected, why_selected, board_warnings
469
470
Simon Glassa56ac992023-07-19 17:49:04 -0600471def adjust_args(args, series, selected):
472 """Adjust arguments according to various constraints
Simon Glass70274872023-07-19 17:48:47 -0600473
474 Updates verbose, show_errors, threads, jobs and step
475
476 Args:
Simon Glassa56ac992023-07-19 17:49:04 -0600477 args (Namespace): Namespace object to adjust
Simon Glass70274872023-07-19 17:48:47 -0600478 series (Series): Series being built / summarised
479 selected (list of Board): List of Board objects that are marked
480 """
Simon Glassa56ac992023-07-19 17:49:04 -0600481 if not series and not args.dry_run:
482 args.verbose = True
483 if not args.summary:
484 args.show_errors = True
Simon Glass70274872023-07-19 17:48:47 -0600485
486 # By default we have one thread per CPU. But if there are not enough jobs
487 # we can have fewer threads and use a high '-j' value for make.
Simon Glassa56ac992023-07-19 17:49:04 -0600488 if args.threads is None:
489 args.threads = min(multiprocessing.cpu_count(), len(selected))
490 if not args.jobs:
491 args.jobs = max(1, (multiprocessing.cpu_count() +
Simon Glass70274872023-07-19 17:48:47 -0600492 len(selected) - 1) // len(selected))
493
Simon Glassa56ac992023-07-19 17:49:04 -0600494 if not args.step:
495 args.step = len(series.commits) - 1
Simon Glass70274872023-07-19 17:48:47 -0600496
Simon Glass31353f22023-07-19 17:48:53 -0600497 # We can't show function sizes without board details at present
Simon Glassa56ac992023-07-19 17:49:04 -0600498 if args.show_bloat:
499 args.show_detail = True
Simon Glass31353f22023-07-19 17:48:53 -0600500
Simon Glass3b300c52023-07-19 17:48:49 -0600501
502def setup_output_dir(output_dir, work_in_output, branch, no_subdirs, col,
503 clean_dir):
504 """Set up the output directory
505
506 Args:
507 output_dir (str): Output directory provided by the user, or None if none
508 work_in_output (bool): True to work in the output directory
509 branch (str): Name of branch to build, or None if none
510 no_subdirs (bool): True to put the output in the top-level output dir
511 clean_dir: Used for tests only, indicates that the existing output_dir
512 should be removed before starting the build
513
514 Returns:
515 str: Updated output directory pathname
516 """
517 if not output_dir:
518 if work_in_output:
519 sys.exit(col.build(col.RED, '-w requires that you specify -o'))
520 output_dir = '..'
521 if branch and not no_subdirs:
522 # As a special case allow the board directory to be placed in the
523 # output directory itself rather than any subdirectory.
524 dirname = branch.replace('/', '_')
525 output_dir = os.path.join(output_dir, dirname)
526 if clean_dir and os.path.exists(output_dir):
527 shutil.rmtree(output_dir)
528 return output_dir
529
Simon Glassfdd750b2023-07-19 17:48:57 -0600530
Simon Glassa56ac992023-07-19 17:49:04 -0600531def run_builder(builder, commits, board_selected, args):
Simon Glass927c8272023-07-19 17:48:54 -0600532 """Run the builder or show the summary
533
534 Args:
535 commits (list of Commit): List of commits being built, None if no branch
536 boards_selected (dict): Dict of selected boards:
537 key: target name
538 value: Board object
Simon Glassa56ac992023-07-19 17:49:04 -0600539 args (Namespace): Namespace to use
Simon Glass927c8272023-07-19 17:48:54 -0600540
541 Returns:
542 int: Return code for buildman
543 """
Simon Glassa56ac992023-07-19 17:49:04 -0600544 gnu_make = command.output(os.path.join(args.git,
Simon Glassfdd750b2023-07-19 17:48:57 -0600545 'scripts/show-gnu-make'), raise_on_error=False).rstrip()
546 if not gnu_make:
547 sys.exit('GNU Make not found')
548 builder.gnu_make = gnu_make
549
Simon Glassa56ac992023-07-19 17:49:04 -0600550 if not args.ide:
551 commit_count = count_build_commits(commits, args.step)
552 tprint(get_action_summary(args.summary, commit_count, board_selected,
553 args.threads, args.jobs))
Simon Glass927c8272023-07-19 17:48:54 -0600554
Simon Glassbc74d942023-07-19 17:49:06 -0600555 builder.set_display_options(
556 args.show_errors, args.show_sizes, args.show_detail, args.show_bloat,
557 args.list_error_boards, args.show_config, args.show_environment,
558 args.filter_dtb_warnings, args.filter_migration_warnings, args.ide)
Simon Glassa56ac992023-07-19 17:49:04 -0600559 if args.summary:
Simon Glassbc74d942023-07-19 17:49:06 -0600560 builder.show_summary(commits, board_selected)
Simon Glass927c8272023-07-19 17:48:54 -0600561 else:
Simon Glassbc74d942023-07-19 17:49:06 -0600562 fail, warned, excs = builder.build_boards(
Simon Glassa56ac992023-07-19 17:49:04 -0600563 commits, board_selected, args.keep_outputs, args.verbose)
Simon Glass927c8272023-07-19 17:48:54 -0600564 if excs:
565 return 102
566 if fail:
567 return 100
Simon Glassa56ac992023-07-19 17:49:04 -0600568 if warned and not args.ignore_warnings:
Simon Glass927c8272023-07-19 17:48:54 -0600569 return 101
570 return 0
Simon Glass3b300c52023-07-19 17:48:49 -0600571
Simon Glassf7524f32023-07-19 17:48:58 -0600572
573def calc_adjust_cfg(adjust_cfg, reproducible_builds):
574 """Calculate the value to use for adjust_cfg
575
576 Args:
577 adjust_cfg (list of str): List of configuration changes. See cfgutil for
578 details
579 reproducible_builds (bool): True to adjust the configuration to get
580 reproduceable builds
581
582 Returns:
583 adjust_cfg (list of str): List of configuration changes
584 """
585 adjust_cfg = cfgutil.convert_list_to_dict(adjust_cfg)
586
587 # Drop LOCALVERSION_AUTO since it changes the version string on every commit
588 if reproducible_builds:
589 # If these are mentioned, leave the local version alone
590 if 'LOCALVERSION' in adjust_cfg or 'LOCALVERSION_AUTO' in adjust_cfg:
591 print('Not dropping LOCALVERSION_AUTO for reproducible build')
592 else:
593 adjust_cfg['LOCALVERSION_AUTO'] = '~'
594 return adjust_cfg
595
Simon Glassc229d322024-06-23 11:55:15 -0600596
597def read_procs(tmpdir=tempfile.gettempdir()):
598 """Read the list of running buildman processes
599
600 If the list is corrupted, returns an empty list
601
602 Args:
603 tmpdir (str): Temporary directory to use (for testing only)
604 """
605 running_fname = os.path.join(tmpdir, RUNNING_FNAME)
606 procs = []
607 if os.path.exists(running_fname):
608 items = tools.read_file(running_fname, binary=False).split()
609 try:
610 procs = [int(x) for x in items]
611 except ValueError: # Handle invalid format
612 pass
613 return procs
614
615
616def check_pid(pid):
617 """Check for existence of a unix PID
618
619 https://stackoverflow.com/questions/568271/how-to-check-if-there-exists-a-process-with-a-given-pid-in-python
620
621 Args:
622 pid (int): PID to check
623
624 Returns:
625 True if it exists, else False
626 """
627 try:
628 os.kill(pid, 0)
629 except OSError:
630 return False
631 else:
632 return True
633
634
635def write_procs(procs, tmpdir=tempfile.gettempdir()):
636 """Write the list of running buildman processes
637
638 Args:
639 tmpdir (str): Temporary directory to use (for testing only)
640 """
641 running_fname = os.path.join(tmpdir, RUNNING_FNAME)
642 tools.write_file(running_fname, ' '.join([str(p) for p in procs]),
643 binary=False)
644
645 # Allow another user to access the file
646 os.chmod(running_fname, 0o666)
647
648def wait_for_process_limit(limit, tmpdir=tempfile.gettempdir(),
649 pid=os.getpid()):
650 """Wait until the number of buildman processes drops to the limit
651
652 This uses FileLock to protect a 'running' file, which contains a list of
653 PIDs of running buildman processes. The number of PIDs in the file indicates
654 the number of running processes.
655
656 When buildman starts up, it calls this function to wait until it is OK to
657 start the build.
658
659 On exit, no attempt is made to remove the PID from the file, since other
660 buildman processes will notice that the PID is no-longer valid, and ignore
661 it.
662
663 Two timeouts are provided:
664 LOCK_WAIT_S: length of time to wait for the lock; if this occurs, the
665 lock is busted / removed before trying again
666 RUN_WAIT_S: length of time to wait to be allowed to run; if this occurs,
667 the build starts, with the PID being added to the file.
668
669 Args:
670 limit (int): Maximum number of buildman processes, including this one;
671 must be > 0
672 tmpdir (str): Temporary directory to use (for testing only)
673 pid (int): Current process ID (for testing only)
674 """
675 from filelock import Timeout, FileLock
676
677 running_fname = os.path.join(tmpdir, RUNNING_FNAME)
678 lock_fname = os.path.join(tmpdir, LOCK_FNAME)
679 lock = FileLock(lock_fname)
680
681 # Allow another user to access the file
682 col = terminal.Color()
683 tprint('Waiting for other buildman processes...', newline=False,
684 colour=col.RED)
685
686 claimed = False
687 deadline = time.time() + RUN_WAIT_S
688 while True:
689 try:
690 with lock.acquire(timeout=LOCK_WAIT_S):
691 os.chmod(lock_fname, 0o666)
692 procs = read_procs(tmpdir)
693
694 # Drop PIDs which are not running
695 procs = list(filter(check_pid, procs))
696
697 # If we haven't hit the limit, add ourself
698 if len(procs) < limit:
699 tprint('done...', newline=False)
700 claimed = True
701 if time.time() >= deadline:
702 tprint('timeout...', newline=False)
703 claimed = True
704 if claimed:
705 write_procs(procs + [pid], tmpdir)
706 break
707
708 except Timeout:
709 tprint('failed to get lock: busting...', newline=False)
710 os.remove(lock_fname)
711
712 time.sleep(1)
713 tprint('starting build', newline=False)
714 print_clear()
Simon Glassf7524f32023-07-19 17:48:58 -0600715
Simon Glassa56ac992023-07-19 17:49:04 -0600716def do_buildman(args, toolchains=None, make_func=None, brds=None,
Simon Glassc1e1e1d2023-07-19 17:48:30 -0600717 clean_dir=False, test_thread_exceptions=False):
Simon Glassc05694f2013-04-03 11:07:16 +0000718 """The main control code for buildman
719
720 Args:
Simon Glassa56ac992023-07-19 17:49:04 -0600721 args: ArgumentParser object
Simon Glassc05694f2013-04-03 11:07:16 +0000722 args: Command line arguments (list of strings)
Simon Glassed098bb2014-09-05 19:00:13 -0600723 toolchains: Toolchains to use - this should be a Toolchains()
724 object. If None, then it will be created and scanned
725 make_func: Make function to use for the builder. This is called
726 to execute 'make'. If this is None, the normal function
727 will be used, which calls the 'make' tool with suitable
728 arguments. This setting is useful for tests.
Simon Glass5df45222022-07-11 19:04:00 -0600729 brds: Boards() object to use, containing a list of available
Simon Glasscbd36582014-09-05 19:00:16 -0600730 boards. If this is None it will be created and scanned.
Simon Glassa29b3ea2021-04-11 16:27:25 +1200731 clean_dir: Used for tests only, indicates that the existing output_dir
732 should be removed before starting the build
Simon Glass9bf9a722021-04-11 16:27:27 +1200733 test_thread_exceptions: Uses for tests only, True to make the threads
734 raise an exception instead of reporting their result. This simulates
735 a failure in the code somewhere
Simon Glassc05694f2013-04-03 11:07:16 +0000736 """
Simon Glassaf0e29f2023-07-19 17:48:31 -0600737 # Used so testing can obtain the builder: pylint: disable=W0603
738 global TEST_BUILDER
Simon Glassa10ebe12014-09-05 19:00:18 -0600739
Simon Glass761648b2022-01-29 14:14:11 -0700740 gitutil.setup()
Simon Glass9f1ba0f2016-07-27 20:33:02 -0600741 col = terminal.Color()
Simon Glassc05694f2013-04-03 11:07:16 +0000742
Simon Glassa56ac992023-07-19 17:49:04 -0600743 git_dir = os.path.join(args.git, '.git')
Simon Glassc05694f2013-04-03 11:07:16 +0000744
Simon Glassa56ac992023-07-19 17:49:04 -0600745 toolchains = get_toolchains(toolchains, col, args.override_toolchain,
746 args.fetch_arch, args.list_tool_chains,
747 args.verbose)
Simon Glass18a07252023-08-03 12:51:36 -0600748 if isinstance(toolchains, int):
749 return toolchains
750
Simon Glass3b300c52023-07-19 17:48:49 -0600751 output_dir = setup_output_dir(
Simon Glassa56ac992023-07-19 17:49:04 -0600752 args.output_dir, args.work_in_output, args.branch,
753 args.no_subdirs, col, clean_dir)
Simon Glass6029af12020-04-09 15:08:51 -0600754
Simon Glassc05694f2013-04-03 11:07:16 +0000755 # Work out what subset of the boards we are building
Simon Glass5df45222022-07-11 19:04:00 -0600756 if not brds:
Simon Glassa56ac992023-07-19 17:49:04 -0600757 brds = get_boards_obj(output_dir, args.regen_board_list,
Simon Glass66d4c882023-07-19 17:49:30 -0600758 args.maintainer_check, args.full_check,
Simon Glass89592fc2023-09-07 10:00:18 -0600759 args.threads, args.verbose and
760 not args.print_arch and not args.print_prefix)
Simon Glassabcc11d2023-07-19 17:48:41 -0600761 if isinstance(brds, int):
762 return brds
Simon Glass924c73a2014-08-28 09:43:41 -0600763
Simon Glass72f8bff2023-07-19 17:48:39 -0600764 selected, why_selected, board_warnings = determine_boards(
Simon Glassa56ac992023-07-19 17:49:04 -0600765 brds, args.terms, col, args.boards, args.exclude)
Simon Glassc05694f2013-04-03 11:07:16 +0000766
Simon Glassa56ac992023-07-19 17:49:04 -0600767 if args.print_prefix:
Simon Glasseaf1be22023-07-19 17:48:56 -0600768 show_toolchain_prefix(brds, toolchains)
Simon Glass48ac42e2019-12-05 15:59:14 -0700769 return 0
770
Simon Glassa8a0ce72023-07-19 17:49:28 -0600771 if args.print_arch:
772 show_arch(brds)
773 return 0
774
Simon Glassa56ac992023-07-19 17:49:04 -0600775 series = determine_series(selected, col, git_dir, args.count,
776 args.branch, args.work_in_output)
Simon Glassc05694f2013-04-03 11:07:16 +0000777
Simon Glassa56ac992023-07-19 17:49:04 -0600778 adjust_args(args, series, selected)
Simon Glassc05694f2013-04-03 11:07:16 +0000779
Simon Glass70274872023-07-19 17:48:47 -0600780 # For a dry run, just show our actions as a sanity check
Simon Glassa56ac992023-07-19 17:49:04 -0600781 if args.dry_run:
Simon Glass70274872023-07-19 17:48:47 -0600782 show_actions(series, why_selected, selected, output_dir, board_warnings,
Simon Glassa56ac992023-07-19 17:49:04 -0600783 args.step, args.threads, args.jobs,
784 args.verbose)
Simon Glass70274872023-07-19 17:48:47 -0600785 return 0
Simon Glassc05694f2013-04-03 11:07:16 +0000786
Simon Glassa56ac992023-07-19 17:49:04 -0600787 # Create a new builder with the selected args
Simon Glass2183b842023-07-19 17:48:33 -0600788 builder = Builder(toolchains, output_dir, git_dir,
Simon Glassa56ac992023-07-19 17:49:04 -0600789 args.threads, args.jobs, checkout=True,
790 show_unknown=args.show_unknown, step=args.step,
Tom Rini3f6f9b22024-07-05 14:34:07 -0600791 no_subdirs=args.no_subdirs, full_path=args.full_path,
792 verbose_build=args.verbose_build,
793 mrproper=args.mrproper,
794 fallback_mrproper=args.fallback_mrproper,
Simon Glassa56ac992023-07-19 17:49:04 -0600795 per_board_out_dir=args.per_board_out_dir,
796 config_only=args.config_only,
797 squash_config_y=not args.preserve_config_y,
798 warnings_as_errors=args.warnings_as_errors,
799 work_in_output=args.work_in_output,
Simon Glasse5650a82022-01-22 05:07:33 -0700800 test_thread_exceptions=test_thread_exceptions,
Simon Glassa56ac992023-07-19 17:49:04 -0600801 adjust_cfg=calc_adjust_cfg(args.adjust_cfg,
802 args.reproducible_builds),
803 allow_missing=get_allow_missing(args.allow_missing,
804 args.no_allow_missing,
805 len(selected), args.branch),
806 no_lto=args.no_lto,
807 reproducible_builds=args.reproducible_builds,
808 force_build = args.force_build,
809 force_build_failures = args.force_build_failures,
810 force_reconfig = args.force_reconfig, in_tree = args.in_tree,
811 force_config_on_failure=not args.quick, make_func=make_func)
Simon Glassc05694f2013-04-03 11:07:16 +0000812
Simon Glasscf91d312023-07-19 17:48:52 -0600813 TEST_BUILDER = builder
Simon Glassc05694f2013-04-03 11:07:16 +0000814
Simon Glassc229d322024-06-23 11:55:15 -0600815 if args.process_limit:
816 wait_for_process_limit(args.process_limit)
817
Simon Glass00401d92023-07-19 17:48:55 -0600818 return run_builder(builder, series.commits if series else None,
Simon Glassa56ac992023-07-19 17:49:04 -0600819 brds.get_selected_dict(), args)