1 # Copyright (c) 2013 The Chromium OS Authors.
3 # SPDX-License-Identifier: GPL-2.0+
13 from builder import Builder
17 from terminal import Print
23 """Returns a plural 's' if count is not 1"""
24 return 's' if count != 1 else ''
26 def GetActionSummary(is_summary, commits, selected, options):
27 """Return a string summarising the intended action.
34 count = (count + options.step - 1) / options.step
35 commit_str = '%d commit%s' % (count, GetPlural(count))
37 commit_str = 'current source'
38 str = '%s %s for %d boards' % (
39 'Summary of' if is_summary else 'Building', commit_str,
41 str += ' (%d thread%s, %d job%s per thread)' % (options.threads,
42 GetPlural(options.threads), options.jobs, GetPlural(options.jobs))
45 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 boards brought
52 in by that argument. For example, 'arm' might bring in
53 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
60 col = terminal.Color()
61 print 'Dry run, so not doing much. But I would do this:'
64 commits = series.commits
67 print GetActionSummary(False, commits, boards_selected,
69 print 'Build directory: %s' % builder.base_dir
71 for upto in range(0, len(series.commits), options.step):
72 commit = series.commits[upto]
73 print ' ', col.Color(col.YELLOW, commit.hash[:8], bright=False),
76 for arg in why_selected:
78 print arg, ': %d boards' % why_selected[arg]
79 print ('Total boards to build for each commit: %d\n' %
82 def DoBuildman(options, args, toolchains=None, make_func=None, boards=None,
84 """The main control code for buildman
87 options: Command line options object
88 args: Command line arguments (list of strings)
89 toolchains: Toolchains to use - this should be a Toolchains()
90 object. If None, then it will be created and scanned
91 make_func: Make function to use for the builder. This is called
92 to execute 'make'. If this is None, the normal function
93 will be used, which calls the 'make' tool with suitable
94 arguments. This setting is useful for tests.
95 board: Boards() object to use, containing a list of available
96 boards. If this is None it will be created and scanned.
100 if options.full_help:
101 pager = os.getenv('PAGER')
104 fname = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])),
106 command.Run(pager, fname)
110 col = terminal.Color()
112 options.git_dir = os.path.join(options.git, '.git')
115 toolchains = toolchain.Toolchains()
116 toolchains.GetSettings()
117 toolchains.Scan(options.list_tool_chains)
118 if options.list_tool_chains:
123 if options.fetch_arch:
124 if options.fetch_arch == 'list':
125 sorted_list = toolchains.ListArchs()
126 print col.Color(col.BLUE, 'Available architectures: %s\n' %
127 ' '.join(sorted_list))
130 fetch_arch = options.fetch_arch
131 if fetch_arch == 'all':
132 fetch_arch = ','.join(toolchains.ListArchs())
133 print col.Color(col.CYAN, '\nDownloading toolchains: %s' %
135 for arch in fetch_arch.split(','):
137 ret = toolchains.FetchAndInstall(arch)
142 # Work out how many commits to build. We want to build everything on the
143 # branch. We also build the upstream commit as a control so we can see
144 # problems introduced by the first commit on the branch.
145 count = options.count
146 has_range = options.branch and '..' in options.branch
148 if not options.branch:
152 count, msg = gitutil.CountCommitsInRange(options.git_dir,
155 count, msg = gitutil.CountCommitsInBranch(options.git_dir,
158 sys.exit(col.Color(col.RED, msg))
160 sys.exit(col.Color(col.RED, "Range '%s' has no commits" %
163 print col.Color(col.YELLOW, msg)
164 count += 1 # Build upstream commit also
167 str = ("No commits found to process in branch '%s': "
168 "set branch's upstream or use -c flag" % options.branch)
169 sys.exit(col.Color(col.RED, str))
171 # Work out what subset of the boards we are building
173 board_file = os.path.join(options.git, 'boards.cfg')
174 status = subprocess.call([os.path.join(options.git,
175 'tools/genboardscfg.py')])
177 sys.exit("Failed to generate boards.cfg")
179 boards = board.Boards()
180 boards.ReadBoards(os.path.join(options.git, 'boards.cfg'))
184 for arg in options.exclude:
185 exclude += arg.split(',')
187 why_selected = boards.SelectBoards(args, exclude)
188 selected = boards.GetSelected()
189 if not len(selected):
190 sys.exit(col.Color(col.RED, 'No matching boards found'))
192 # Read the metadata from the commits. First look at the upstream commit,
193 # then the ones in the branch. We would like to do something like
194 # upstream/master~..branch but that isn't possible if upstream/master is
195 # a merge commit (it will list all the commits that form part of the
197 # Conflicting tags are not a problem for buildman, since it does not use
198 # them. For example, Series-version is not useful for buildman. On the
199 # other hand conflicting tags will cause an error. So allow later tags
200 # to overwrite earlier ones by setting allow_overwrite=True
204 range_expr = options.branch
206 range_expr = gitutil.GetRangeInBranch(options.git_dir,
208 upstream_commit = gitutil.GetUpstream(options.git_dir,
210 series = patchstream.GetMetaDataForList(upstream_commit,
211 options.git_dir, 1, series=None, allow_overwrite=True)
213 series = patchstream.GetMetaDataForList(range_expr,
214 options.git_dir, None, series, allow_overwrite=True)
217 series = patchstream.GetMetaDataForList(options.branch,
218 options.git_dir, count, series=None, allow_overwrite=True)
221 options.verbose = True
222 if not options.summary:
223 options.show_errors = True
225 # By default we have one thread per CPU. But if there are not enough jobs
226 # we can have fewer threads and use a high '-j' value for make.
227 if not options.threads:
228 options.threads = min(multiprocessing.cpu_count(), len(selected))
230 options.jobs = max(1, (multiprocessing.cpu_count() +
231 len(selected) - 1) / len(selected))
234 options.step = len(series.commits) - 1
236 gnu_make = command.Output(os.path.join(options.git,
237 'scripts/show-gnu-make')).rstrip()
239 sys.exit('GNU Make not found')
241 # Create a new builder with the selected options.
242 output_dir = options.output_dir
244 dirname = options.branch.replace('/', '_')
245 # As a special case allow the board directory to be placed in the
246 # output directory itself rather than any subdirectory.
247 if not options.no_subdirs:
248 output_dir = os.path.join(options.output_dir, dirname)
249 if (clean_dir and output_dir != options.output_dir and
250 os.path.exists(output_dir)):
251 shutil.rmtree(output_dir)
252 builder = Builder(toolchains, output_dir, options.git_dir,
253 options.threads, options.jobs, gnu_make=gnu_make, checkout=True,
254 show_unknown=options.show_unknown, step=options.step,
255 no_subdirs=options.no_subdirs, full_path=options.full_path,
256 verbose_build=options.verbose_build,
257 incremental=options.incremental,
258 per_board_out_dir=options.per_board_out_dir,)
259 builder.force_config_on_failure = not options.quick
261 builder.do_make = make_func
263 # For a dry run, just show our actions as a sanity check
265 ShowActions(series, why_selected, selected, builder, options)
267 builder.force_build = options.force_build
268 builder.force_build_failures = options.force_build_failures
269 builder.force_reconfig = options.force_reconfig
270 builder.in_tree = options.in_tree
272 # Work out which boards to build
273 board_selected = boards.GetSelectedDict()
276 commits = series.commits
277 # Number the commits for test purposes
278 for commit in range(len(commits)):
279 commits[commit].sequence = commit
283 Print(GetActionSummary(options.summary, commits, board_selected,
286 # We can't show function sizes without board details at present
287 if options.show_bloat:
288 options.show_detail = True
289 builder.SetDisplayOptions(options.show_errors, options.show_sizes,
290 options.show_detail, options.show_bloat,
291 options.list_error_boards,
294 builder.ShowSummary(commits, board_selected)
296 fail, warned = builder.BuildBoards(commits, board_selected,
297 options.keep_outputs, options.verbose)