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 DoBuildman(options, args, toolchains=None, make_func=None, boards=None,
112 """The main control code for buildman
115 options: Command line options object
116 args: Command line arguments (list of strings)
117 toolchains: Toolchains to use - this should be a Toolchains()
118 object. If None, then it will be created and scanned
119 make_func: Make function to use for the builder. This is called
120 to execute 'make'. If this is None, the normal function
121 will be used, which calls the 'make' tool with suitable
122 arguments. This setting is useful for tests.
123 board: Boards() object to use, containing a list of available
124 boards. If this is None it will be created and scanned.
128 if options.full_help:
129 pager = os.getenv('PAGER')
132 fname = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])),
134 command.Run(pager, fname)
138 col = terminal.Color()
140 options.git_dir = os.path.join(options.git, '.git')
142 no_toolchains = toolchains is None
144 toolchains = toolchain.Toolchains(options.override_toolchain)
146 if options.fetch_arch:
147 if options.fetch_arch == 'list':
148 sorted_list = toolchains.ListArchs()
149 print(col.Color(col.BLUE, 'Available architectures: %s\n' %
150 ' '.join(sorted_list)))
153 fetch_arch = options.fetch_arch
154 if fetch_arch == 'all':
155 fetch_arch = ','.join(toolchains.ListArchs())
156 print(col.Color(col.CYAN, '\nDownloading toolchains: %s' %
158 for arch in fetch_arch.split(','):
160 ret = toolchains.FetchAndInstall(arch)
166 toolchains.GetSettings()
167 toolchains.Scan(options.list_tool_chains and options.verbose)
168 if options.list_tool_chains:
173 # Work out what subset of the boards we are building
175 if not os.path.exists(options.output_dir):
176 os.makedirs(options.output_dir)
177 board_file = os.path.join(options.output_dir, 'boards.cfg')
178 genboardscfg = os.path.join(options.git, 'tools/genboardscfg.py')
179 status = subprocess.call([genboardscfg, '-q', '-o', board_file])
181 sys.exit("Failed to generate boards.cfg")
183 boards = board.Boards()
184 boards.ReadBoards(board_file)
188 for arg in options.exclude:
189 exclude += arg.split(',')
192 requested_boards = []
193 for b in options.boards:
194 requested_boards += b.split(',')
196 requested_boards = None
197 why_selected, board_warnings = boards.SelectBoards(args, exclude,
199 selected = boards.GetSelected()
200 if not len(selected):
201 sys.exit(col.Color(col.RED, 'No matching boards found'))
203 # Work out how many commits to build. We want to build everything on the
204 # branch. We also build the upstream commit as a control so we can see
205 # problems introduced by the first commit on the branch.
206 count = options.count
207 has_range = options.branch and '..' in options.branch
209 if not options.branch:
213 count, msg = gitutil.CountCommitsInRange(options.git_dir,
216 count, msg = gitutil.CountCommitsInBranch(options.git_dir,
219 sys.exit(col.Color(col.RED, msg))
221 sys.exit(col.Color(col.RED, "Range '%s' has no commits" %
224 print(col.Color(col.YELLOW, msg))
225 count += 1 # Build upstream commit also
228 str = ("No commits found to process in branch '%s': "
229 "set branch's upstream or use -c flag" % options.branch)
230 sys.exit(col.Color(col.RED, str))
232 # Read the metadata from the commits. First look at the upstream commit,
233 # then the ones in the branch. We would like to do something like
234 # upstream/master~..branch but that isn't possible if upstream/master is
235 # a merge commit (it will list all the commits that form part of the
237 # Conflicting tags are not a problem for buildman, since it does not use
238 # them. For example, Series-version is not useful for buildman. On the
239 # other hand conflicting tags will cause an error. So allow later tags
240 # to overwrite earlier ones by setting allow_overwrite=True
244 range_expr = options.branch
246 range_expr = gitutil.GetRangeInBranch(options.git_dir,
248 upstream_commit = gitutil.GetUpstream(options.git_dir,
250 series = patchstream.GetMetaDataForList(upstream_commit,
251 options.git_dir, 1, series=None, allow_overwrite=True)
253 series = patchstream.GetMetaDataForList(range_expr,
254 options.git_dir, None, series, allow_overwrite=True)
257 series = patchstream.GetMetaDataForList(options.branch,
258 options.git_dir, count, series=None, allow_overwrite=True)
261 if not options.dry_run:
262 options.verbose = True
263 if not options.summary:
264 options.show_errors = True
266 # By default we have one thread per CPU. But if there are not enough jobs
267 # we can have fewer threads and use a high '-j' value for make.
268 if not options.threads:
269 options.threads = min(multiprocessing.cpu_count(), len(selected))
271 options.jobs = max(1, (multiprocessing.cpu_count() +
272 len(selected) - 1) // len(selected))
275 options.step = len(series.commits) - 1
277 gnu_make = command.Output(os.path.join(options.git,
278 'scripts/show-gnu-make'), raise_on_error=False).rstrip()
280 sys.exit('GNU Make not found')
282 # Create a new builder with the selected options.
283 output_dir = options.output_dir
285 dirname = options.branch.replace('/', '_')
286 # As a special case allow the board directory to be placed in the
287 # output directory itself rather than any subdirectory.
288 if not options.no_subdirs:
289 output_dir = os.path.join(options.output_dir, dirname)
290 if clean_dir and os.path.exists(output_dir):
291 shutil.rmtree(output_dir)
292 CheckOutputDir(output_dir)
293 builder = Builder(toolchains, output_dir, options.git_dir,
294 options.threads, options.jobs, gnu_make=gnu_make, checkout=True,
295 show_unknown=options.show_unknown, step=options.step,
296 no_subdirs=options.no_subdirs, full_path=options.full_path,
297 verbose_build=options.verbose_build,
298 incremental=options.incremental,
299 per_board_out_dir=options.per_board_out_dir,
300 config_only=options.config_only,
301 squash_config_y=not options.preserve_config_y,
302 warnings_as_errors=options.warnings_as_errors)
303 builder.force_config_on_failure = not options.quick
305 builder.do_make = make_func
307 # For a dry run, just show our actions as a sanity check
309 ShowActions(series, why_selected, selected, builder, options,
312 builder.force_build = options.force_build
313 builder.force_build_failures = options.force_build_failures
314 builder.force_reconfig = options.force_reconfig
315 builder.in_tree = options.in_tree
317 # Work out which boards to build
318 board_selected = boards.GetSelectedDict()
321 commits = series.commits
322 # Number the commits for test purposes
323 for commit in range(len(commits)):
324 commits[commit].sequence = commit
328 Print(GetActionSummary(options.summary, commits, board_selected,
331 # We can't show function sizes without board details at present
332 if options.show_bloat:
333 options.show_detail = True
334 builder.SetDisplayOptions(options.show_errors, options.show_sizes,
335 options.show_detail, options.show_bloat,
336 options.list_error_boards,
338 options.show_environment)
340 builder.ShowSummary(commits, board_selected)
342 fail, warned = builder.BuildBoards(commits, board_selected,
343 options.keep_outputs, options.verbose)