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')
114 no_toolchains = toolchains is None
116 toolchains = toolchain.Toolchains()
118 if options.fetch_arch:
119 if options.fetch_arch == 'list':
120 sorted_list = toolchains.ListArchs()
121 print col.Color(col.BLUE, 'Available architectures: %s\n' %
122 ' '.join(sorted_list))
125 fetch_arch = options.fetch_arch
126 if fetch_arch == 'all':
127 fetch_arch = ','.join(toolchains.ListArchs())
128 print col.Color(col.CYAN, '\nDownloading toolchains: %s' %
130 for arch in fetch_arch.split(','):
132 ret = toolchains.FetchAndInstall(arch)
138 toolchains.GetSettings()
139 toolchains.Scan(options.list_tool_chains)
140 if options.list_tool_chains:
145 # Work out how many commits to build. We want to build everything on the
146 # branch. We also build the upstream commit as a control so we can see
147 # problems introduced by the first commit on the branch.
148 count = options.count
149 has_range = options.branch and '..' in options.branch
151 if not options.branch:
155 count, msg = gitutil.CountCommitsInRange(options.git_dir,
158 count, msg = gitutil.CountCommitsInBranch(options.git_dir,
161 sys.exit(col.Color(col.RED, msg))
163 sys.exit(col.Color(col.RED, "Range '%s' has no commits" %
166 print col.Color(col.YELLOW, msg)
167 count += 1 # Build upstream commit also
170 str = ("No commits found to process in branch '%s': "
171 "set branch's upstream or use -c flag" % options.branch)
172 sys.exit(col.Color(col.RED, str))
174 # Work out what subset of the boards we are building
176 board_file = os.path.join(options.git, 'boards.cfg')
177 status = subprocess.call([os.path.join(options.git,
178 'tools/genboardscfg.py')])
180 sys.exit("Failed to generate boards.cfg")
182 boards = board.Boards()
183 boards.ReadBoards(os.path.join(options.git, 'boards.cfg'))
187 for arg in options.exclude:
188 exclude += arg.split(',')
190 why_selected = boards.SelectBoards(args, exclude)
191 selected = boards.GetSelected()
192 if not len(selected):
193 sys.exit(col.Color(col.RED, 'No matching boards found'))
195 # Read the metadata from the commits. First look at the upstream commit,
196 # then the ones in the branch. We would like to do something like
197 # upstream/master~..branch but that isn't possible if upstream/master is
198 # a merge commit (it will list all the commits that form part of the
200 # Conflicting tags are not a problem for buildman, since it does not use
201 # them. For example, Series-version is not useful for buildman. On the
202 # other hand conflicting tags will cause an error. So allow later tags
203 # to overwrite earlier ones by setting allow_overwrite=True
207 range_expr = options.branch
209 range_expr = gitutil.GetRangeInBranch(options.git_dir,
211 upstream_commit = gitutil.GetUpstream(options.git_dir,
213 series = patchstream.GetMetaDataForList(upstream_commit,
214 options.git_dir, 1, series=None, allow_overwrite=True)
216 series = patchstream.GetMetaDataForList(range_expr,
217 options.git_dir, None, series, allow_overwrite=True)
220 series = patchstream.GetMetaDataForList(options.branch,
221 options.git_dir, count, series=None, allow_overwrite=True)
224 options.verbose = True
225 if not options.summary:
226 options.show_errors = True
228 # By default we have one thread per CPU. But if there are not enough jobs
229 # we can have fewer threads and use a high '-j' value for make.
230 if not options.threads:
231 options.threads = min(multiprocessing.cpu_count(), len(selected))
233 options.jobs = max(1, (multiprocessing.cpu_count() +
234 len(selected) - 1) / len(selected))
237 options.step = len(series.commits) - 1
239 gnu_make = command.Output(os.path.join(options.git,
240 'scripts/show-gnu-make')).rstrip()
242 sys.exit('GNU Make not found')
244 # Create a new builder with the selected options.
245 output_dir = options.output_dir
247 dirname = options.branch.replace('/', '_')
248 # As a special case allow the board directory to be placed in the
249 # output directory itself rather than any subdirectory.
250 if not options.no_subdirs:
251 output_dir = os.path.join(options.output_dir, dirname)
252 if (clean_dir and output_dir != options.output_dir and
253 os.path.exists(output_dir)):
254 shutil.rmtree(output_dir)
255 builder = Builder(toolchains, output_dir, options.git_dir,
256 options.threads, options.jobs, gnu_make=gnu_make, checkout=True,
257 show_unknown=options.show_unknown, step=options.step,
258 no_subdirs=options.no_subdirs, full_path=options.full_path,
259 verbose_build=options.verbose_build,
260 incremental=options.incremental,
261 per_board_out_dir=options.per_board_out_dir,)
262 builder.force_config_on_failure = not options.quick
264 builder.do_make = make_func
266 # For a dry run, just show our actions as a sanity check
268 ShowActions(series, why_selected, selected, builder, options)
270 builder.force_build = options.force_build
271 builder.force_build_failures = options.force_build_failures
272 builder.force_reconfig = options.force_reconfig
273 builder.in_tree = options.in_tree
275 # Work out which boards to build
276 board_selected = boards.GetSelectedDict()
279 commits = series.commits
280 # Number the commits for test purposes
281 for commit in range(len(commits)):
282 commits[commit].sequence = commit
286 Print(GetActionSummary(options.summary, commits, board_selected,
289 # We can't show function sizes without board details at present
290 if options.show_bloat:
291 options.show_detail = True
292 builder.SetDisplayOptions(options.show_errors, options.show_sizes,
293 options.show_detail, options.show_bloat,
294 options.list_error_boards,
297 builder.ShowSummary(commits, board_selected)
299 fail, warned = builder.BuildBoards(commits, board_selected,
300 options.keep_outputs, options.verbose)