1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright (c) 2013 The Chromium OS Authors.
11 from buildman import board
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 Print
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.Color(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.Color(col.YELLOW, warning))
90 def ShowToolchainPrefix(boards, 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 boards = boards.GetSelectedDict()
105 for brd in boards.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, boards=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 board: 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:
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.Color(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.Color(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 options.incremental:
180 print(col.Color(col.RED,
181 'Warning: -I has been removed. See documentation'))
182 if not options.output_dir:
183 if options.work_in_output:
184 sys.exit(col.Color(col.RED, '-w requires that you specify -o'))
185 options.output_dir = '..'
187 # Work out what subset of the boards we are building
189 if not os.path.exists(options.output_dir):
190 os.makedirs(options.output_dir)
191 board_file = os.path.join(options.output_dir, 'boards.cfg')
192 our_path = os.path.dirname(os.path.realpath(__file__))
193 genboardscfg = os.path.join(our_path, '../genboardscfg.py')
194 if not os.path.exists(genboardscfg):
195 genboardscfg = os.path.join(options.git, 'tools/genboardscfg.py')
196 status = subprocess.call([genboardscfg, '-q', '-o', board_file])
198 # Older versions don't support -q
199 status = subprocess.call([genboardscfg, '-o', board_file])
201 sys.exit("Failed to generate boards.cfg")
203 boards = board.Boards()
204 boards.ReadBoards(board_file)
208 for arg in options.exclude:
209 exclude += arg.split(',')
212 requested_boards = []
213 for b in options.boards:
214 requested_boards += b.split(',')
216 requested_boards = None
217 why_selected, board_warnings = boards.SelectBoards(args, exclude,
219 selected = boards.GetSelected()
220 if not len(selected):
221 sys.exit(col.Color(col.RED, 'No matching boards found'))
223 if options.print_prefix:
224 err = ShowToolchainPrefix(boards, toolchains)
226 sys.exit(col.Color(col.RED, err))
229 # Work out how many commits to build. We want to build everything on the
230 # branch. We also build the upstream commit as a control so we can see
231 # problems introduced by the first commit on the branch.
232 count = options.count
233 has_range = options.branch and '..' in options.branch
235 if not options.branch:
239 count, msg = gitutil.CountCommitsInRange(options.git_dir,
242 count, msg = gitutil.CountCommitsInBranch(options.git_dir,
245 sys.exit(col.Color(col.RED, msg))
247 sys.exit(col.Color(col.RED, "Range '%s' has no commits" %
250 print(col.Color(col.YELLOW, msg))
251 count += 1 # Build upstream commit also
254 str = ("No commits found to process in branch '%s': "
255 "set branch's upstream or use -c flag" % options.branch)
256 sys.exit(col.Color(col.RED, str))
257 if options.work_in_output:
258 if len(selected) != 1:
259 sys.exit(col.Color(col.RED,
260 '-w can only be used with a single board'))
262 sys.exit(col.Color(col.RED,
263 '-w can only be used with a single commit'))
265 # Read the metadata from the commits. First look at the upstream commit,
266 # then the ones in the branch. We would like to do something like
267 # upstream/master~..branch but that isn't possible if upstream/master is
268 # a merge commit (it will list all the commits that form part of the
270 # Conflicting tags are not a problem for buildman, since it does not use
271 # them. For example, Series-version is not useful for buildman. On the
272 # other hand conflicting tags will cause an error. So allow later tags
273 # to overwrite earlier ones by setting allow_overwrite=True
277 range_expr = options.branch
279 range_expr = gitutil.GetRangeInBranch(options.git_dir,
281 upstream_commit = gitutil.GetUpstream(options.git_dir,
283 series = patchstream.get_metadata_for_list(upstream_commit,
284 options.git_dir, 1, series=None, allow_overwrite=True)
286 series = patchstream.get_metadata_for_list(range_expr,
287 options.git_dir, None, series, allow_overwrite=True)
290 series = patchstream.get_metadata_for_list(options.branch,
291 options.git_dir, count, series=None, allow_overwrite=True)
294 if not options.dry_run:
295 options.verbose = True
296 if not options.summary:
297 options.show_errors = True
299 # By default we have one thread per CPU. But if there are not enough jobs
300 # we can have fewer threads and use a high '-j' value for make.
301 if options.threads is None:
302 options.threads = min(multiprocessing.cpu_count(), len(selected))
304 options.jobs = max(1, (multiprocessing.cpu_count() +
305 len(selected) - 1) // len(selected))
308 options.step = len(series.commits) - 1
310 gnu_make = command.Output(os.path.join(options.git,
311 'scripts/show-gnu-make'), raise_on_error=False).rstrip()
313 sys.exit('GNU Make not found')
315 # Create a new builder with the selected options.
316 output_dir = options.output_dir
318 dirname = options.branch.replace('/', '_')
319 # As a special case allow the board directory to be placed in the
320 # output directory itself rather than any subdirectory.
321 if not options.no_subdirs:
322 output_dir = os.path.join(options.output_dir, dirname)
323 if clean_dir and os.path.exists(output_dir):
324 shutil.rmtree(output_dir)
325 adjust_cfg = cfgutil.convert_list_to_dict(options.adjust_cfg)
327 builder = Builder(toolchains, output_dir, options.git_dir,
328 options.threads, options.jobs, gnu_make=gnu_make, checkout=True,
329 show_unknown=options.show_unknown, step=options.step,
330 no_subdirs=options.no_subdirs, full_path=options.full_path,
331 verbose_build=options.verbose_build,
332 mrproper=options.mrproper,
333 per_board_out_dir=options.per_board_out_dir,
334 config_only=options.config_only,
335 squash_config_y=not options.preserve_config_y,
336 warnings_as_errors=options.warnings_as_errors,
337 work_in_output=options.work_in_output,
338 test_thread_exceptions=test_thread_exceptions,
339 adjust_cfg=adjust_cfg)
340 builder.force_config_on_failure = not options.quick
342 builder.do_make = make_func
344 # For a dry run, just show our actions as a sanity check
346 ShowActions(series, why_selected, selected, builder, options,
349 builder.force_build = options.force_build
350 builder.force_build_failures = options.force_build_failures
351 builder.force_reconfig = options.force_reconfig
352 builder.in_tree = options.in_tree
354 # Work out which boards to build
355 board_selected = boards.GetSelectedDict()
358 commits = series.commits
359 # Number the commits for test purposes
360 for commit in range(len(commits)):
361 commits[commit].sequence = commit
365 Print(GetActionSummary(options.summary, commits, board_selected,
368 # We can't show function sizes without board details at present
369 if options.show_bloat:
370 options.show_detail = True
371 builder.SetDisplayOptions(
372 options.show_errors, options.show_sizes, options.show_detail,
373 options.show_bloat, options.list_error_boards, options.show_config,
374 options.show_environment, options.filter_dtb_warnings,
375 options.filter_migration_warnings)
377 builder.ShowSummary(commits, board_selected)
379 fail, warned, excs = builder.BuildBoards(
380 commits, board_selected, options.keep_outputs, options.verbose)
385 elif warned and not options.ignore_warnings: