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 how many commits to build. We want to build everything on the
174 # branch. We also build the upstream commit as a control so we can see
175 # problems introduced by the first commit on the branch.
176 count = options.count
177 has_range = options.branch and '..' in options.branch
179 if not options.branch:
183 count, msg = gitutil.CountCommitsInRange(options.git_dir,
186 count, msg = gitutil.CountCommitsInBranch(options.git_dir,
189 sys.exit(col.Color(col.RED, msg))
191 sys.exit(col.Color(col.RED, "Range '%s' has no commits" %
194 print(col.Color(col.YELLOW, msg))
195 count += 1 # Build upstream commit also
198 str = ("No commits found to process in branch '%s': "
199 "set branch's upstream or use -c flag" % options.branch)
200 sys.exit(col.Color(col.RED, str))
202 # Work out what subset of the boards we are building
204 if not os.path.exists(options.output_dir):
205 os.makedirs(options.output_dir)
206 board_file = os.path.join(options.output_dir, 'boards.cfg')
207 genboardscfg = os.path.join(options.git, 'tools/genboardscfg.py')
208 status = subprocess.call([genboardscfg, '-q', '-o', board_file])
210 sys.exit("Failed to generate boards.cfg")
212 boards = board.Boards()
213 boards.ReadBoards(board_file)
217 for arg in options.exclude:
218 exclude += arg.split(',')
222 requested_boards = []
223 for b in options.boards:
224 requested_boards += b.split(',')
226 requested_boards = None
227 why_selected, board_warnings = boards.SelectBoards(args, exclude,
229 selected = boards.GetSelected()
230 if not len(selected):
231 sys.exit(col.Color(col.RED, 'No matching boards found'))
233 # Read the metadata from the commits. First look at the upstream commit,
234 # then the ones in the branch. We would like to do something like
235 # upstream/master~..branch but that isn't possible if upstream/master is
236 # a merge commit (it will list all the commits that form part of the
238 # Conflicting tags are not a problem for buildman, since it does not use
239 # them. For example, Series-version is not useful for buildman. On the
240 # other hand conflicting tags will cause an error. So allow later tags
241 # to overwrite earlier ones by setting allow_overwrite=True
245 range_expr = options.branch
247 range_expr = gitutil.GetRangeInBranch(options.git_dir,
249 upstream_commit = gitutil.GetUpstream(options.git_dir,
251 series = patchstream.GetMetaDataForList(upstream_commit,
252 options.git_dir, 1, series=None, allow_overwrite=True)
254 series = patchstream.GetMetaDataForList(range_expr,
255 options.git_dir, None, series, allow_overwrite=True)
258 series = patchstream.GetMetaDataForList(options.branch,
259 options.git_dir, count, series=None, allow_overwrite=True)
262 if not options.dry_run:
263 options.verbose = True
264 if not options.summary:
265 options.show_errors = True
267 # By default we have one thread per CPU. But if there are not enough jobs
268 # we can have fewer threads and use a high '-j' value for make.
269 if not options.threads:
270 options.threads = min(multiprocessing.cpu_count(), len(selected))
272 options.jobs = max(1, (multiprocessing.cpu_count() +
273 len(selected) - 1) // len(selected))
276 options.step = len(series.commits) - 1
278 gnu_make = command.Output(os.path.join(options.git,
279 'scripts/show-gnu-make'), raise_on_error=False).rstrip()
281 sys.exit('GNU Make not found')
283 # Create a new builder with the selected options.
284 output_dir = options.output_dir
286 dirname = options.branch.replace('/', '_')
287 # As a special case allow the board directory to be placed in the
288 # output directory itself rather than any subdirectory.
289 if not options.no_subdirs:
290 output_dir = os.path.join(options.output_dir, dirname)
291 if clean_dir and os.path.exists(output_dir):
292 shutil.rmtree(output_dir)
293 CheckOutputDir(output_dir)
294 builder = Builder(toolchains, output_dir, options.git_dir,
295 options.threads, options.jobs, gnu_make=gnu_make, checkout=True,
296 show_unknown=options.show_unknown, step=options.step,
297 no_subdirs=options.no_subdirs, full_path=options.full_path,
298 verbose_build=options.verbose_build,
299 incremental=options.incremental,
300 per_board_out_dir=options.per_board_out_dir,
301 config_only=options.config_only,
302 squash_config_y=not options.preserve_config_y,
303 warnings_as_errors=options.warnings_as_errors)
304 builder.force_config_on_failure = not options.quick
306 builder.do_make = make_func
308 # For a dry run, just show our actions as a sanity check
310 ShowActions(series, why_selected, selected, builder, options,
313 builder.force_build = options.force_build
314 builder.force_build_failures = options.force_build_failures
315 builder.force_reconfig = options.force_reconfig
316 builder.in_tree = options.in_tree
318 # Work out which boards to build
319 board_selected = boards.GetSelectedDict()
322 commits = series.commits
323 # Number the commits for test purposes
324 for commit in range(len(commits)):
325 commits[commit].sequence = commit
329 Print(GetActionSummary(options.summary, commits, board_selected,
332 # We can't show function sizes without board details at present
333 if options.show_bloat:
334 options.show_detail = True
335 builder.SetDisplayOptions(options.show_errors, options.show_sizes,
336 options.show_detail, options.show_bloat,
337 options.list_error_boards,
339 options.show_environment)
341 builder.ShowSummary(commits, board_selected)
343 fail, warned = builder.BuildBoards(commits, board_selected,
344 options.keep_outputs, options.verbose)