1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright (c) 2013 The Chromium OS Authors.
11 from buildman import boards
12 from buildman import bsettings
13 from buildman import cfgutil
14 from buildman import toolchain
15 from buildman.builder import Builder
16 from patman import command
17 from patman import gitutil
18 from patman import patchstream
19 from patman import terminal
20 from patman import tools
21 from patman.terminal import tprint
24 """Returns a plural 's' if count is not 1"""
25 return 's' if count != 1 else ''
27 def GetActionSummary(is_summary, commits, selected, options):
28 """Return a string summarising the intended action.
35 count = (count + options.step - 1) // options.step
36 commit_str = '%d commit%s' % (count, GetPlural(count))
38 commit_str = 'current source'
39 str = '%s %s for %d boards' % (
40 'Summary of' if is_summary else 'Building', commit_str,
42 str += ' (%d thread%s, %d job%s per thread)' % (options.threads,
43 GetPlural(options.threads), options.jobs, GetPlural(options.jobs))
46 def ShowActions(series, why_selected, boards_selected, builder, options,
48 """Display a list of actions that we would take, if not a dry run.
52 why_selected: Dictionary where each key is a buildman argument
53 provided by the user, and the value is the list of boards
54 brought in by that argument. For example, 'arm' might bring
55 in 400 boards, so in this case the key would be 'arm' and
56 the value would be a list of board names.
57 boards_selected: Dict of selected boards, key is target name,
59 builder: The builder that will be used to build the commits
60 options: Command line options object
61 board_warnings: List of warnings obtained from board selected
63 col = terminal.Color()
64 print('Dry run, so not doing much. But I would do this:')
67 commits = series.commits
70 print(GetActionSummary(False, commits, boards_selected,
72 print('Build directory: %s' % builder.base_dir)
74 for upto in range(0, len(series.commits), options.step):
75 commit = series.commits[upto]
76 print(' ', col.build(col.YELLOW, commit.hash[:8], bright=False), end=' ')
79 for arg in why_selected:
81 print(arg, ': %d boards' % len(why_selected[arg]))
83 print(' %s' % ' '.join(why_selected[arg]))
84 print(('Total boards to build for each commit: %d\n' %
85 len(why_selected['all'])))
87 for warning in board_warnings:
88 print(col.build(col.YELLOW, warning))
90 def ShowToolchainPrefix(brds, toolchains):
91 """Show information about a the tool chain used by one or more boards
93 The function checks that all boards use the same toolchain, then prints
94 the correct value for CROSS_COMPILE.
97 boards: Boards object containing selected boards
98 toolchains: Toolchains object containing available toolchains
101 None on success, string error message otherwise
103 board_selected = brds.get_selected_dict()
105 for brd in board_selected.values():
106 tc_set.add(toolchains.Select(brd.arch))
108 return 'Supplied boards must share one toolchain'
111 print(tc.GetEnvArgs(toolchain.VAR_CROSS_COMPILE))
114 def DoBuildman(options, args, toolchains=None, make_func=None, brds=None,
115 clean_dir=False, test_thread_exceptions=False):
116 """The main control code for buildman
119 options: Command line options object
120 args: Command line arguments (list of strings)
121 toolchains: Toolchains to use - this should be a Toolchains()
122 object. If None, then it will be created and scanned
123 make_func: Make function to use for the builder. This is called
124 to execute 'make'. If this is None, the normal function
125 will be used, which calls the 'make' tool with suitable
126 arguments. This setting is useful for tests.
127 brds: Boards() object to use, containing a list of available
128 boards. If this is None it will be created and scanned.
129 clean_dir: Used for tests only, indicates that the existing output_dir
130 should be removed before starting the build
131 test_thread_exceptions: Uses for tests only, True to make the threads
132 raise an exception instead of reporting their result. This simulates
133 a failure in the code somewhere
137 if options.full_help:
138 tools.print_full_help(
139 os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])), 'README')
144 col = terminal.Color()
146 options.git_dir = os.path.join(options.git, '.git')
148 no_toolchains = toolchains is None
150 toolchains = toolchain.Toolchains(options.override_toolchain)
152 if options.fetch_arch:
153 if options.fetch_arch == 'list':
154 sorted_list = toolchains.ListArchs()
155 print(col.build(col.BLUE, 'Available architectures: %s\n' %
156 ' '.join(sorted_list)))
159 fetch_arch = options.fetch_arch
160 if fetch_arch == 'all':
161 fetch_arch = ','.join(toolchains.ListArchs())
162 print(col.build(col.CYAN, '\nDownloading toolchains: %s' %
164 for arch in fetch_arch.split(','):
166 ret = toolchains.FetchAndInstall(arch)
172 toolchains.GetSettings()
173 toolchains.Scan(options.list_tool_chains and options.verbose)
174 if options.list_tool_chains:
179 if not options.output_dir:
180 if options.work_in_output:
181 sys.exit(col.build(col.RED, '-w requires that you specify -o'))
182 options.output_dir = '..'
184 # Work out what subset of the boards we are building
186 if not os.path.exists(options.output_dir):
187 os.makedirs(options.output_dir)
188 board_file = os.path.join(options.output_dir, 'boards.cfg')
190 brds = boards.Boards()
191 brds.ensure_board_list(board_file,
192 options.threads or multiprocessing.cpu_count(),
193 force=options.regen_board_list,
194 quiet=not options.verbose)
195 if options.regen_board_list:
197 brds.read_boards(board_file)
201 for arg in options.exclude:
202 exclude += arg.split(',')
205 requested_boards = []
206 for b in options.boards:
207 requested_boards += b.split(',')
209 requested_boards = None
210 why_selected, board_warnings = brds.select_boards(args, exclude,
212 selected = brds.get_selected()
213 if not len(selected):
214 sys.exit(col.build(col.RED, 'No matching boards found'))
216 if options.print_prefix:
217 err = ShowToolchainPrefix(brds, toolchains)
219 sys.exit(col.build(col.RED, err))
222 # Work out how many commits to build. We want to build everything on the
223 # branch. We also build the upstream commit as a control so we can see
224 # problems introduced by the first commit on the branch.
225 count = options.count
226 has_range = options.branch and '..' in options.branch
228 if not options.branch:
232 count, msg = gitutil.count_commits_in_range(options.git_dir,
235 count, msg = gitutil.count_commits_in_branch(options.git_dir,
238 sys.exit(col.build(col.RED, msg))
240 sys.exit(col.build(col.RED, "Range '%s' has no commits" %
243 print(col.build(col.YELLOW, msg))
244 count += 1 # Build upstream commit also
247 str = ("No commits found to process in branch '%s': "
248 "set branch's upstream or use -c flag" % options.branch)
249 sys.exit(col.build(col.RED, str))
250 if options.work_in_output:
251 if len(selected) != 1:
252 sys.exit(col.build(col.RED,
253 '-w can only be used with a single board'))
255 sys.exit(col.build(col.RED,
256 '-w can only be used with a single commit'))
258 # Read the metadata from the commits. First look at the upstream commit,
259 # then the ones in the branch. We would like to do something like
260 # upstream/master~..branch but that isn't possible if upstream/master is
261 # a merge commit (it will list all the commits that form part of the
263 # Conflicting tags are not a problem for buildman, since it does not use
264 # them. For example, Series-version is not useful for buildman. On the
265 # other hand conflicting tags will cause an error. So allow later tags
266 # to overwrite earlier ones by setting allow_overwrite=True
270 range_expr = options.branch
272 range_expr = gitutil.get_range_in_branch(options.git_dir,
274 upstream_commit = gitutil.get_upstream(options.git_dir,
276 series = patchstream.get_metadata_for_list(upstream_commit,
277 options.git_dir, 1, series=None, allow_overwrite=True)
279 series = patchstream.get_metadata_for_list(range_expr,
280 options.git_dir, None, series, allow_overwrite=True)
283 series = patchstream.get_metadata_for_list(options.branch,
284 options.git_dir, count, series=None, allow_overwrite=True)
287 if not options.dry_run:
288 options.verbose = True
289 if not options.summary:
290 options.show_errors = True
292 # By default we have one thread per CPU. But if there are not enough jobs
293 # we can have fewer threads and use a high '-j' value for make.
294 if options.threads is None:
295 options.threads = min(multiprocessing.cpu_count(), len(selected))
297 options.jobs = max(1, (multiprocessing.cpu_count() +
298 len(selected) - 1) // len(selected))
301 options.step = len(series.commits) - 1
303 gnu_make = command.output(os.path.join(options.git,
304 'scripts/show-gnu-make'), raise_on_error=False).rstrip()
306 sys.exit('GNU Make not found')
308 # Create a new builder with the selected options.
309 output_dir = options.output_dir
311 dirname = options.branch.replace('/', '_')
312 # As a special case allow the board directory to be placed in the
313 # output directory itself rather than any subdirectory.
314 if not options.no_subdirs:
315 output_dir = os.path.join(options.output_dir, dirname)
316 if clean_dir and os.path.exists(output_dir):
317 shutil.rmtree(output_dir)
318 adjust_cfg = cfgutil.convert_list_to_dict(options.adjust_cfg)
320 builder = Builder(toolchains, output_dir, options.git_dir,
321 options.threads, options.jobs, gnu_make=gnu_make, checkout=True,
322 show_unknown=options.show_unknown, step=options.step,
323 no_subdirs=options.no_subdirs, full_path=options.full_path,
324 verbose_build=options.verbose_build,
325 mrproper=options.mrproper,
326 per_board_out_dir=options.per_board_out_dir,
327 config_only=options.config_only,
328 squash_config_y=not options.preserve_config_y,
329 warnings_as_errors=options.warnings_as_errors,
330 work_in_output=options.work_in_output,
331 test_thread_exceptions=test_thread_exceptions,
332 adjust_cfg=adjust_cfg)
333 builder.force_config_on_failure = not options.quick
335 builder.do_make = make_func
337 # For a dry run, just show our actions as a sanity check
339 ShowActions(series, why_selected, selected, builder, options,
342 builder.force_build = options.force_build
343 builder.force_build_failures = options.force_build_failures
344 builder.force_reconfig = options.force_reconfig
345 builder.in_tree = options.in_tree
347 # Work out which boards to build
348 board_selected = brds.get_selected_dict()
351 commits = series.commits
352 # Number the commits for test purposes
353 for commit in range(len(commits)):
354 commits[commit].sequence = commit
359 tprint(GetActionSummary(options.summary, commits, board_selected,
362 # We can't show function sizes without board details at present
363 if options.show_bloat:
364 options.show_detail = True
365 builder.SetDisplayOptions(
366 options.show_errors, options.show_sizes, options.show_detail,
367 options.show_bloat, options.list_error_boards, options.show_config,
368 options.show_environment, options.filter_dtb_warnings,
369 options.filter_migration_warnings, options.ide)
371 builder.ShowSummary(commits, board_selected)
373 fail, warned, excs = builder.BuildBoards(
374 commits, board_selected, options.keep_outputs, options.verbose)
379 elif warned and not options.ignore_warnings: