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 ShowToolchainPrefix(boards, toolchains):
89 """Show information about a the tool chain used by one or more boards
91 The function checks that all boards use the same toolchain, then prints
92 the correct value for CROSS_COMPILE.
95 boards: Boards object containing selected boards
96 toolchains: Toolchains object containing available toolchains
99 None on success, string error message otherwise
101 boards = boards.GetSelectedDict()
103 for brd in boards.values():
104 tc_set.add(toolchains.Select(brd.arch))
106 return 'Supplied boards must share one toolchain'
109 print(tc.GetEnvArgs(toolchain.VAR_CROSS_COMPILE))
112 def DoBuildman(options, args, toolchains=None, make_func=None, boards=None,
114 """The main control code for buildman
117 options: Command line options object
118 args: Command line arguments (list of strings)
119 toolchains: Toolchains to use - this should be a Toolchains()
120 object. If None, then it will be created and scanned
121 make_func: Make function to use for the builder. This is called
122 to execute 'make'. If this is None, the normal function
123 will be used, which calls the 'make' tool with suitable
124 arguments. This setting is useful for tests.
125 board: Boards() object to use, containing a list of available
126 boards. If this is None it will be created and scanned.
130 if options.full_help:
131 pager = os.getenv('PAGER')
134 fname = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])),
136 command.Run(pager, fname)
140 col = terminal.Color()
142 options.git_dir = os.path.join(options.git, '.git')
144 no_toolchains = toolchains is None
146 toolchains = toolchain.Toolchains(options.override_toolchain)
148 if options.fetch_arch:
149 if options.fetch_arch == 'list':
150 sorted_list = toolchains.ListArchs()
151 print(col.Color(col.BLUE, 'Available architectures: %s\n' %
152 ' '.join(sorted_list)))
155 fetch_arch = options.fetch_arch
156 if fetch_arch == 'all':
157 fetch_arch = ','.join(toolchains.ListArchs())
158 print(col.Color(col.CYAN, '\nDownloading toolchains: %s' %
160 for arch in fetch_arch.split(','):
162 ret = toolchains.FetchAndInstall(arch)
168 toolchains.GetSettings()
169 toolchains.Scan(options.list_tool_chains and options.verbose)
170 if options.list_tool_chains:
175 if options.incremental:
176 print(col.Color(col.RED,
177 'Warning: -I has been removed. See documentation'))
179 # Work out what subset of the boards we are building
181 if not os.path.exists(options.output_dir):
182 os.makedirs(options.output_dir)
183 board_file = os.path.join(options.output_dir, 'boards.cfg')
184 genboardscfg = os.path.join(options.git, 'tools/genboardscfg.py')
185 status = subprocess.call([genboardscfg, '-q', '-o', board_file])
187 sys.exit("Failed to generate boards.cfg")
189 boards = board.Boards()
190 boards.ReadBoards(board_file)
194 for arg in options.exclude:
195 exclude += arg.split(',')
198 requested_boards = []
199 for b in options.boards:
200 requested_boards += b.split(',')
202 requested_boards = None
203 why_selected, board_warnings = boards.SelectBoards(args, exclude,
205 selected = boards.GetSelected()
206 if not len(selected):
207 sys.exit(col.Color(col.RED, 'No matching boards found'))
209 if options.print_prefix:
210 err = ShowToolchainInfo(boards, toolchains)
212 sys.exit(col.Color(col.RED, err))
215 # Work out how many commits to build. We want to build everything on the
216 # branch. We also build the upstream commit as a control so we can see
217 # problems introduced by the first commit on the branch.
218 count = options.count
219 has_range = options.branch and '..' in options.branch
221 if not options.branch:
225 count, msg = gitutil.CountCommitsInRange(options.git_dir,
228 count, msg = gitutil.CountCommitsInBranch(options.git_dir,
231 sys.exit(col.Color(col.RED, msg))
233 sys.exit(col.Color(col.RED, "Range '%s' has no commits" %
236 print(col.Color(col.YELLOW, msg))
237 count += 1 # Build upstream commit also
240 str = ("No commits found to process in branch '%s': "
241 "set branch's upstream or use -c flag" % options.branch)
242 sys.exit(col.Color(col.RED, str))
243 if options.work_in_output:
244 if len(selected) != 1:
245 sys.exit(col.Color(col.RED,
246 '-w can only be used with a single board'))
248 sys.exit(col.Color(col.RED,
249 '-w can only be used with a single commit'))
251 # Read the metadata from the commits. First look at the upstream commit,
252 # then the ones in the branch. We would like to do something like
253 # upstream/master~..branch but that isn't possible if upstream/master is
254 # a merge commit (it will list all the commits that form part of the
256 # Conflicting tags are not a problem for buildman, since it does not use
257 # them. For example, Series-version is not useful for buildman. On the
258 # other hand conflicting tags will cause an error. So allow later tags
259 # to overwrite earlier ones by setting allow_overwrite=True
263 range_expr = options.branch
265 range_expr = gitutil.GetRangeInBranch(options.git_dir,
267 upstream_commit = gitutil.GetUpstream(options.git_dir,
269 series = patchstream.GetMetaDataForList(upstream_commit,
270 options.git_dir, 1, series=None, allow_overwrite=True)
272 series = patchstream.GetMetaDataForList(range_expr,
273 options.git_dir, None, series, allow_overwrite=True)
276 series = patchstream.GetMetaDataForList(options.branch,
277 options.git_dir, count, series=None, allow_overwrite=True)
280 if not options.dry_run:
281 options.verbose = True
282 if not options.summary:
283 options.show_errors = True
285 # By default we have one thread per CPU. But if there are not enough jobs
286 # we can have fewer threads and use a high '-j' value for make.
287 if not options.threads:
288 options.threads = min(multiprocessing.cpu_count(), len(selected))
290 options.jobs = max(1, (multiprocessing.cpu_count() +
291 len(selected) - 1) // len(selected))
294 options.step = len(series.commits) - 1
296 gnu_make = command.Output(os.path.join(options.git,
297 'scripts/show-gnu-make'), raise_on_error=False).rstrip()
299 sys.exit('GNU Make not found')
301 # Create a new builder with the selected options.
302 output_dir = options.output_dir
304 dirname = options.branch.replace('/', '_')
305 # As a special case allow the board directory to be placed in the
306 # output directory itself rather than any subdirectory.
307 if not options.no_subdirs:
308 output_dir = os.path.join(options.output_dir, dirname)
309 if clean_dir and os.path.exists(output_dir):
310 shutil.rmtree(output_dir)
311 builder = Builder(toolchains, output_dir, options.git_dir,
312 options.threads, options.jobs, gnu_make=gnu_make, checkout=True,
313 show_unknown=options.show_unknown, step=options.step,
314 no_subdirs=options.no_subdirs, full_path=options.full_path,
315 verbose_build=options.verbose_build,
316 mrproper=options.mrproper,
317 per_board_out_dir=options.per_board_out_dir,
318 config_only=options.config_only,
319 squash_config_y=not options.preserve_config_y,
320 warnings_as_errors=options.warnings_as_errors,
321 work_in_output=options.work_in_output)
322 builder.force_config_on_failure = not options.quick
324 builder.do_make = make_func
326 # For a dry run, just show our actions as a sanity check
328 ShowActions(series, why_selected, selected, builder, options,
331 builder.force_build = options.force_build
332 builder.force_build_failures = options.force_build_failures
333 builder.force_reconfig = options.force_reconfig
334 builder.in_tree = options.in_tree
336 # Work out which boards to build
337 board_selected = boards.GetSelectedDict()
340 commits = series.commits
341 # Number the commits for test purposes
342 for commit in range(len(commits)):
343 commits[commit].sequence = commit
347 Print(GetActionSummary(options.summary, commits, board_selected,
350 # We can't show function sizes without board details at present
351 if options.show_bloat:
352 options.show_detail = True
353 builder.SetDisplayOptions(
354 options.show_errors, options.show_sizes, options.show_detail,
355 options.show_bloat, options.list_error_boards, options.show_config,
356 options.show_environment, options.filter_dtb_warnings,
357 options.filter_migration_warnings)
359 builder.ShowSummary(commits, board_selected)
361 fail, warned = builder.BuildBoards(commits, board_selected,
362 options.keep_outputs, options.verbose)
365 elif warned and not options.ignore_warnings: