1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright (c) 2013 The Chromium OS Authors.
12 from builder import Builder
16 from terminal import Print
22 """Returns a plural 's' if count is not 1"""
23 return 's' if count != 1 else ''
25 def GetActionSummary(is_summary, commits, selected, options):
26 """Return a string summarising the intended action.
33 count = (count + options.step - 1) // options.step
34 commit_str = '%d commit%s' % (count, GetPlural(count))
36 commit_str = 'current source'
37 str = '%s %s for %d boards' % (
38 'Summary of' if is_summary else 'Building', commit_str,
40 str += ' (%d thread%s, %d job%s per thread)' % (options.threads,
41 GetPlural(options.threads), options.jobs, GetPlural(options.jobs))
44 def ShowActions(series, why_selected, boards_selected, builder, options,
46 """Display a list of actions that we would take, if not a dry run.
50 why_selected: Dictionary where each key is a buildman argument
51 provided by the user, and the value is the list of boards
52 brought in by that argument. For example, 'arm' might bring
53 in 400 boards, so in this case the key would be 'arm' and
54 the value would be a list of board names.
55 boards_selected: Dict of selected boards, key is target name,
57 builder: The builder that will be used to build the commits
58 options: Command line options object
59 board_warnings: List of warnings obtained from board selected
61 col = terminal.Color()
62 print('Dry run, so not doing much. But I would do this:')
65 commits = series.commits
68 print(GetActionSummary(False, commits, boards_selected,
70 print('Build directory: %s' % builder.base_dir)
72 for upto in range(0, len(series.commits), options.step):
73 commit = series.commits[upto]
74 print(' ', col.Color(col.YELLOW, commit.hash[:8], bright=False), end=' ')
77 for arg in why_selected:
79 print(arg, ': %d boards' % len(why_selected[arg]))
81 print(' %s' % ' '.join(why_selected[arg]))
82 print(('Total boards to build for each commit: %d\n' %
83 len(why_selected['all'])))
85 for warning in board_warnings:
86 print(col.Color(col.YELLOW, warning))
88 def CheckOutputDir(output_dir):
89 """Make sure that the output directory is not within the current directory
91 If we try to use an output directory which is within the current directory
92 (which is assumed to hold the U-Boot source) we may end up deleting the
93 U-Boot source code. Detect this and print an error in this case.
96 output_dir: Output directory path to check
98 path = os.path.realpath(output_dir)
99 cwd_path = os.path.realpath('.')
101 if os.path.realpath(path) == cwd_path:
102 Print("Cannot use output directory '%s' since it is within the current directory '%s'" %
105 parent = os.path.dirname(path)
110 def ShowToolchainInfo(boards, toolchains, print_arch, print_prefix):
111 """Show information about a the tool chain used by one or more boards
113 The function checks that all boards use the same toolchain.
116 boards: Boards object containing selected boards
117 toolchains: Toolchains object containing available toolchains
118 print_arch: True to print ARCH value
119 print_prefix: True to print CROSS_COMPILE value
122 None on success, string error message otherwise
124 boards = boards.GetSelectedDict()
126 for brd in boards.values():
127 tc_set.add(toolchains.Select(brd.arch))
129 return 'Supplied boards must share one toolchain'
133 print(tc.GetEnvArgs(toolchain.VAR_ARCH))
135 print(tc.GetEnvArgs(toolchain.VAR_CROSS_COMPILE))
138 def DoBuildman(options, args, toolchains=None, make_func=None, boards=None,
140 """The main control code for buildman
143 options: Command line options object
144 args: Command line arguments (list of strings)
145 toolchains: Toolchains to use - this should be a Toolchains()
146 object. If None, then it will be created and scanned
147 make_func: Make function to use for the builder. This is called
148 to execute 'make'. If this is None, the normal function
149 will be used, which calls the 'make' tool with suitable
150 arguments. This setting is useful for tests.
151 board: Boards() object to use, containing a list of available
152 boards. If this is None it will be created and scanned.
156 if options.full_help:
157 pager = os.getenv('PAGER')
160 fname = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])),
162 command.Run(pager, fname)
166 col = terminal.Color()
168 options.git_dir = os.path.join(options.git, '.git')
170 no_toolchains = toolchains is None
172 toolchains = toolchain.Toolchains(options.override_toolchain)
174 if options.fetch_arch:
175 if options.fetch_arch == 'list':
176 sorted_list = toolchains.ListArchs()
177 print(col.Color(col.BLUE, 'Available architectures: %s\n' %
178 ' '.join(sorted_list)))
181 fetch_arch = options.fetch_arch
182 if fetch_arch == 'all':
183 fetch_arch = ','.join(toolchains.ListArchs())
184 print(col.Color(col.CYAN, '\nDownloading toolchains: %s' %
186 for arch in fetch_arch.split(','):
188 ret = toolchains.FetchAndInstall(arch)
194 toolchains.GetSettings()
195 toolchains.Scan(options.list_tool_chains and options.verbose)
196 if options.list_tool_chains:
201 # Work out what subset of the boards we are building
203 if not os.path.exists(options.output_dir):
204 os.makedirs(options.output_dir)
205 board_file = os.path.join(options.output_dir, 'boards.cfg')
206 genboardscfg = os.path.join(options.git, 'tools/genboardscfg.py')
207 status = subprocess.call([genboardscfg, '-q', '-o', board_file])
209 sys.exit("Failed to generate boards.cfg")
211 boards = board.Boards()
212 boards.ReadBoards(board_file)
216 for arg in options.exclude:
217 exclude += arg.split(',')
220 requested_boards = []
221 for b in options.boards:
222 requested_boards += b.split(',')
224 requested_boards = None
225 why_selected, board_warnings = boards.SelectBoards(args, exclude,
227 selected = boards.GetSelected()
228 if not len(selected):
229 sys.exit(col.Color(col.RED, 'No matching boards found'))
231 if options.print_arch or options.print_prefix:
232 err = ShowToolchainInfo(boards, toolchains, options.print_arch,
233 options.print_prefix)
235 sys.exit(col.Color(col.RED, err))
238 # Work out how many commits to build. We want to build everything on the
239 # branch. We also build the upstream commit as a control so we can see
240 # problems introduced by the first commit on the branch.
241 count = options.count
242 has_range = options.branch and '..' in options.branch
244 if not options.branch:
248 count, msg = gitutil.CountCommitsInRange(options.git_dir,
251 count, msg = gitutil.CountCommitsInBranch(options.git_dir,
254 sys.exit(col.Color(col.RED, msg))
256 sys.exit(col.Color(col.RED, "Range '%s' has no commits" %
259 print(col.Color(col.YELLOW, msg))
260 count += 1 # Build upstream commit also
263 str = ("No commits found to process in branch '%s': "
264 "set branch's upstream or use -c flag" % options.branch)
265 sys.exit(col.Color(col.RED, str))
266 if options.work_in_output:
267 if len(selected) != 1:
268 sys.exit(col.Color(col.RED,
269 '-w can only be used with a single board'))
271 sys.exit(col.Color(col.RED,
272 '-w can only be used with a single commit'))
274 # Read the metadata from the commits. First look at the upstream commit,
275 # then the ones in the branch. We would like to do something like
276 # upstream/master~..branch but that isn't possible if upstream/master is
277 # a merge commit (it will list all the commits that form part of the
279 # Conflicting tags are not a problem for buildman, since it does not use
280 # them. For example, Series-version is not useful for buildman. On the
281 # other hand conflicting tags will cause an error. So allow later tags
282 # to overwrite earlier ones by setting allow_overwrite=True
286 range_expr = options.branch
288 range_expr = gitutil.GetRangeInBranch(options.git_dir,
290 upstream_commit = gitutil.GetUpstream(options.git_dir,
292 series = patchstream.GetMetaDataForList(upstream_commit,
293 options.git_dir, 1, series=None, allow_overwrite=True)
295 series = patchstream.GetMetaDataForList(range_expr,
296 options.git_dir, None, series, allow_overwrite=True)
299 series = patchstream.GetMetaDataForList(options.branch,
300 options.git_dir, count, series=None, allow_overwrite=True)
303 if not options.dry_run:
304 options.verbose = True
305 if not options.summary:
306 options.show_errors = True
308 # By default we have one thread per CPU. But if there are not enough jobs
309 # we can have fewer threads and use a high '-j' value for make.
310 if not options.threads:
311 options.threads = min(multiprocessing.cpu_count(), len(selected))
313 options.jobs = max(1, (multiprocessing.cpu_count() +
314 len(selected) - 1) // len(selected))
317 options.step = len(series.commits) - 1
319 gnu_make = command.Output(os.path.join(options.git,
320 'scripts/show-gnu-make'), raise_on_error=False).rstrip()
322 sys.exit('GNU Make not found')
324 # Create a new builder with the selected options.
325 output_dir = options.output_dir
327 dirname = options.branch.replace('/', '_')
328 # As a special case allow the board directory to be placed in the
329 # output directory itself rather than any subdirectory.
330 if not options.no_subdirs:
331 output_dir = os.path.join(options.output_dir, dirname)
332 if clean_dir and os.path.exists(output_dir):
333 shutil.rmtree(output_dir)
334 CheckOutputDir(output_dir)
335 builder = Builder(toolchains, output_dir, options.git_dir,
336 options.threads, options.jobs, gnu_make=gnu_make, checkout=True,
337 show_unknown=options.show_unknown, step=options.step,
338 no_subdirs=options.no_subdirs, full_path=options.full_path,
339 verbose_build=options.verbose_build,
340 incremental=options.incremental,
341 per_board_out_dir=options.per_board_out_dir,
342 config_only=options.config_only,
343 squash_config_y=not options.preserve_config_y,
344 warnings_as_errors=options.warnings_as_errors,
345 work_in_output=options.work_in_output)
346 builder.force_config_on_failure = not options.quick
348 builder.do_make = make_func
350 # For a dry run, just show our actions as a sanity check
352 ShowActions(series, why_selected, selected, builder, options,
355 builder.force_build = options.force_build
356 builder.force_build_failures = options.force_build_failures
357 builder.force_reconfig = options.force_reconfig
358 builder.in_tree = options.in_tree
360 # Work out which boards to build
361 board_selected = boards.GetSelectedDict()
364 commits = series.commits
365 # Number the commits for test purposes
366 for commit in range(len(commits)):
367 commits[commit].sequence = commit
371 Print(GetActionSummary(options.summary, commits, board_selected,
374 # We can't show function sizes without board details at present
375 if options.show_bloat:
376 options.show_detail = True
377 builder.SetDisplayOptions(options.show_errors, options.show_sizes,
378 options.show_detail, options.show_bloat,
379 options.list_error_boards,
381 options.show_environment)
383 builder.ShowSummary(commits, board_selected)
385 fail, warned = builder.BuildBoards(commits, board_selected,
386 options.keep_outputs, options.verbose)
389 elif warned and not options.ignore_warnings: