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 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
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' % len(why_selected[arg])
80 print ' %s' % ' '.join(why_selected[arg])
81 print ('Total boards to build for each commit: %d\n' %
82 len(why_selected['all']))
84 def DoBuildman(options, args, toolchains=None, make_func=None, boards=None,
86 """The main control code for buildman
89 options: Command line options object
90 args: Command line arguments (list of strings)
91 toolchains: Toolchains to use - this should be a Toolchains()
92 object. If None, then it will be created and scanned
93 make_func: Make function to use for the builder. This is called
94 to execute 'make'. If this is None, the normal function
95 will be used, which calls the 'make' tool with suitable
96 arguments. This setting is useful for tests.
97 board: Boards() object to use, containing a list of available
98 boards. If this is None it will be created and scanned.
102 if options.full_help:
103 pager = os.getenv('PAGER')
106 fname = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])),
108 command.Run(pager, fname)
112 col = terminal.Color()
114 options.git_dir = os.path.join(options.git, '.git')
116 no_toolchains = toolchains is None
118 toolchains = toolchain.Toolchains()
120 if options.fetch_arch:
121 if options.fetch_arch == 'list':
122 sorted_list = toolchains.ListArchs()
123 print col.Color(col.BLUE, 'Available architectures: %s\n' %
124 ' '.join(sorted_list))
127 fetch_arch = options.fetch_arch
128 if fetch_arch == 'all':
129 fetch_arch = ','.join(toolchains.ListArchs())
130 print col.Color(col.CYAN, '\nDownloading toolchains: %s' %
132 for arch in fetch_arch.split(','):
134 ret = toolchains.FetchAndInstall(arch)
140 toolchains.GetSettings()
141 toolchains.Scan(options.list_tool_chains)
142 if options.list_tool_chains:
147 # Work out how many commits to build. We want to build everything on the
148 # branch. We also build the upstream commit as a control so we can see
149 # problems introduced by the first commit on the branch.
150 count = options.count
151 has_range = options.branch and '..' in options.branch
153 if not options.branch:
157 count, msg = gitutil.CountCommitsInRange(options.git_dir,
160 count, msg = gitutil.CountCommitsInBranch(options.git_dir,
163 sys.exit(col.Color(col.RED, msg))
165 sys.exit(col.Color(col.RED, "Range '%s' has no commits" %
168 print col.Color(col.YELLOW, msg)
169 count += 1 # Build upstream commit also
172 str = ("No commits found to process in branch '%s': "
173 "set branch's upstream or use -c flag" % options.branch)
174 sys.exit(col.Color(col.RED, str))
176 # Work out what subset of the boards we are building
178 board_file = os.path.join(options.git, 'boards.cfg')
179 status = subprocess.call([os.path.join(options.git,
180 'tools/genboardscfg.py')])
182 sys.exit("Failed to generate boards.cfg")
184 boards = board.Boards()
185 boards.ReadBoards(os.path.join(options.git, 'boards.cfg'))
189 for arg in options.exclude:
190 exclude += arg.split(',')
192 why_selected = boards.SelectBoards(args, exclude)
193 selected = boards.GetSelected()
194 if not len(selected):
195 sys.exit(col.Color(col.RED, 'No matching boards found'))
197 # Read the metadata from the commits. First look at the upstream commit,
198 # then the ones in the branch. We would like to do something like
199 # upstream/master~..branch but that isn't possible if upstream/master is
200 # a merge commit (it will list all the commits that form part of the
202 # Conflicting tags are not a problem for buildman, since it does not use
203 # them. For example, Series-version is not useful for buildman. On the
204 # other hand conflicting tags will cause an error. So allow later tags
205 # to overwrite earlier ones by setting allow_overwrite=True
209 range_expr = options.branch
211 range_expr = gitutil.GetRangeInBranch(options.git_dir,
213 upstream_commit = gitutil.GetUpstream(options.git_dir,
215 series = patchstream.GetMetaDataForList(upstream_commit,
216 options.git_dir, 1, series=None, allow_overwrite=True)
218 series = patchstream.GetMetaDataForList(range_expr,
219 options.git_dir, None, series, allow_overwrite=True)
222 series = patchstream.GetMetaDataForList(options.branch,
223 options.git_dir, count, series=None, allow_overwrite=True)
226 if not options.dry_run:
227 options.verbose = True
228 if not options.summary:
229 options.show_errors = True
231 # By default we have one thread per CPU. But if there are not enough jobs
232 # we can have fewer threads and use a high '-j' value for make.
233 if not options.threads:
234 options.threads = min(multiprocessing.cpu_count(), len(selected))
236 options.jobs = max(1, (multiprocessing.cpu_count() +
237 len(selected) - 1) / len(selected))
240 options.step = len(series.commits) - 1
242 gnu_make = command.Output(os.path.join(options.git,
243 'scripts/show-gnu-make'), raise_on_error=False).rstrip()
245 sys.exit('GNU Make not found')
247 # Create a new builder with the selected options.
248 output_dir = options.output_dir
250 dirname = options.branch.replace('/', '_')
251 # As a special case allow the board directory to be placed in the
252 # output directory itself rather than any subdirectory.
253 if not options.no_subdirs:
254 output_dir = os.path.join(options.output_dir, dirname)
255 if (clean_dir and output_dir != options.output_dir and
256 os.path.exists(output_dir)):
257 shutil.rmtree(output_dir)
258 builder = Builder(toolchains, output_dir, options.git_dir,
259 options.threads, options.jobs, gnu_make=gnu_make, checkout=True,
260 show_unknown=options.show_unknown, step=options.step,
261 no_subdirs=options.no_subdirs, full_path=options.full_path,
262 verbose_build=options.verbose_build,
263 incremental=options.incremental,
264 per_board_out_dir=options.per_board_out_dir,
265 config_only=options.config_only,
266 squash_config_y=not options.preserve_config_y)
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)