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):
45 """Display a list of actions that we would take, if not a dry run.
49 why_selected: Dictionary where each key is a buildman argument
50 provided by the user, and the value is the list of boards
51 brought in by that argument. For example, 'arm' might bring
52 in 400 boards, so in this case the key would be 'arm' and
53 the value would be a list of board names.
54 boards_selected: Dict of selected boards, key is target name,
56 builder: The builder that will be used to build the commits
57 options: Command line options object
59 col = terminal.Color()
60 print 'Dry run, so not doing much. But I would do this:'
63 commits = series.commits
66 print GetActionSummary(False, commits, boards_selected,
68 print 'Build directory: %s' % builder.base_dir
70 for upto in range(0, len(series.commits), options.step):
71 commit = series.commits[upto]
72 print ' ', col.Color(col.YELLOW, commit.hash[:8], bright=False),
75 for arg in why_selected:
77 print arg, ': %d boards' % len(why_selected[arg])
79 print ' %s' % ' '.join(why_selected[arg])
80 print ('Total boards to build for each commit: %d\n' %
81 len(why_selected['all']))
83 def DoBuildman(options, args, toolchains=None, make_func=None, boards=None,
85 """The main control code for buildman
88 options: Command line options object
89 args: Command line arguments (list of strings)
90 toolchains: Toolchains to use - this should be a Toolchains()
91 object. If None, then it will be created and scanned
92 make_func: Make function to use for the builder. This is called
93 to execute 'make'. If this is None, the normal function
94 will be used, which calls the 'make' tool with suitable
95 arguments. This setting is useful for tests.
96 board: Boards() object to use, containing a list of available
97 boards. If this is None it will be created and scanned.
101 if options.full_help:
102 pager = os.getenv('PAGER')
105 fname = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])),
107 command.Run(pager, fname)
111 col = terminal.Color()
113 options.git_dir = os.path.join(options.git, '.git')
115 no_toolchains = toolchains is None
117 toolchains = toolchain.Toolchains()
119 if options.fetch_arch:
120 if options.fetch_arch == 'list':
121 sorted_list = toolchains.ListArchs()
122 print col.Color(col.BLUE, 'Available architectures: %s\n' %
123 ' '.join(sorted_list))
126 fetch_arch = options.fetch_arch
127 if fetch_arch == 'all':
128 fetch_arch = ','.join(toolchains.ListArchs())
129 print col.Color(col.CYAN, '\nDownloading toolchains: %s' %
131 for arch in fetch_arch.split(','):
133 ret = toolchains.FetchAndInstall(arch)
139 toolchains.GetSettings()
140 toolchains.Scan(options.list_tool_chains)
141 if options.list_tool_chains:
146 # Work out how many commits to build. We want to build everything on the
147 # branch. We also build the upstream commit as a control so we can see
148 # problems introduced by the first commit on the branch.
149 count = options.count
150 has_range = options.branch and '..' in options.branch
152 if not options.branch:
156 count, msg = gitutil.CountCommitsInRange(options.git_dir,
159 count, msg = gitutil.CountCommitsInBranch(options.git_dir,
162 sys.exit(col.Color(col.RED, msg))
164 sys.exit(col.Color(col.RED, "Range '%s' has no commits" %
167 print col.Color(col.YELLOW, msg)
168 count += 1 # Build upstream commit also
171 str = ("No commits found to process in branch '%s': "
172 "set branch's upstream or use -c flag" % options.branch)
173 sys.exit(col.Color(col.RED, str))
175 # Work out what subset of the boards we are building
177 board_file = os.path.join(options.git, 'boards.cfg')
178 status = subprocess.call([os.path.join(options.git,
179 'tools/genboardscfg.py')])
181 sys.exit("Failed to generate boards.cfg")
183 boards = board.Boards()
184 boards.ReadBoards(os.path.join(options.git, 'boards.cfg'))
188 for arg in options.exclude:
189 exclude += arg.split(',')
191 why_selected = boards.SelectBoards(args, exclude)
192 selected = boards.GetSelected()
193 if not len(selected):
194 sys.exit(col.Color(col.RED, 'No matching boards found'))
196 # Read the metadata from the commits. First look at the upstream commit,
197 # then the ones in the branch. We would like to do something like
198 # upstream/master~..branch but that isn't possible if upstream/master is
199 # a merge commit (it will list all the commits that form part of the
201 # Conflicting tags are not a problem for buildman, since it does not use
202 # them. For example, Series-version is not useful for buildman. On the
203 # other hand conflicting tags will cause an error. So allow later tags
204 # to overwrite earlier ones by setting allow_overwrite=True
208 range_expr = options.branch
210 range_expr = gitutil.GetRangeInBranch(options.git_dir,
212 upstream_commit = gitutil.GetUpstream(options.git_dir,
214 series = patchstream.GetMetaDataForList(upstream_commit,
215 options.git_dir, 1, series=None, allow_overwrite=True)
217 series = patchstream.GetMetaDataForList(range_expr,
218 options.git_dir, None, series, allow_overwrite=True)
221 series = patchstream.GetMetaDataForList(options.branch,
222 options.git_dir, count, series=None, allow_overwrite=True)
225 if not options.dry_run:
226 options.verbose = True
227 if not options.summary:
228 options.show_errors = True
230 # By default we have one thread per CPU. But if there are not enough jobs
231 # we can have fewer threads and use a high '-j' value for make.
232 if not options.threads:
233 options.threads = min(multiprocessing.cpu_count(), len(selected))
235 options.jobs = max(1, (multiprocessing.cpu_count() +
236 len(selected) - 1) / len(selected))
239 options.step = len(series.commits) - 1
241 gnu_make = command.Output(os.path.join(options.git,
242 'scripts/show-gnu-make'), raise_on_error=False).rstrip()
244 sys.exit('GNU Make not found')
246 # Create a new builder with the selected options.
247 output_dir = options.output_dir
249 dirname = options.branch.replace('/', '_')
250 # As a special case allow the board directory to be placed in the
251 # output directory itself rather than any subdirectory.
252 if not options.no_subdirs:
253 output_dir = os.path.join(options.output_dir, dirname)
254 if (clean_dir and output_dir != options.output_dir and
255 os.path.exists(output_dir)):
256 shutil.rmtree(output_dir)
257 builder = Builder(toolchains, output_dir, options.git_dir,
258 options.threads, options.jobs, gnu_make=gnu_make, checkout=True,
259 show_unknown=options.show_unknown, step=options.step,
260 no_subdirs=options.no_subdirs, full_path=options.full_path,
261 verbose_build=options.verbose_build,
262 incremental=options.incremental,
263 per_board_out_dir=options.per_board_out_dir,
264 config_only=options.config_only,
265 squash_config_y=not options.preserve_config_y,
266 warnings_as_errors=options.warnings_as_errors)
267 builder.force_config_on_failure = not options.quick
269 builder.do_make = make_func
271 # For a dry run, just show our actions as a sanity check
273 ShowActions(series, why_selected, selected, builder, options)
275 builder.force_build = options.force_build
276 builder.force_build_failures = options.force_build_failures
277 builder.force_reconfig = options.force_reconfig
278 builder.in_tree = options.in_tree
280 # Work out which boards to build
281 board_selected = boards.GetSelectedDict()
284 commits = series.commits
285 # Number the commits for test purposes
286 for commit in range(len(commits)):
287 commits[commit].sequence = commit
291 Print(GetActionSummary(options.summary, commits, board_selected,
294 # We can't show function sizes without board details at present
295 if options.show_bloat:
296 options.show_detail = True
297 builder.SetDisplayOptions(options.show_errors, options.show_sizes,
298 options.show_detail, options.show_bloat,
299 options.list_error_boards,
302 builder.ShowSummary(commits, board_selected)
304 fail, warned = builder.BuildBoards(commits, board_selected,
305 options.keep_outputs, options.verbose)