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)
111 options.git_dir = os.path.join(options.git, '.git')
114 toolchains = toolchain.Toolchains()
115 toolchains.GetSettings()
116 toolchains.Scan(options.list_tool_chains)
117 if options.list_tool_chains:
122 if options.fetch_arch:
123 if options.fetch_arch == 'list':
124 sorted_list = toolchains.ListArchs()
125 print 'Available architectures: %s\n' % ' '.join(sorted_list)
128 fetch_arch = options.fetch_arch
129 if fetch_arch == 'all':
130 fetch_arch = ','.join(toolchains.ListArchs())
131 print 'Downloading toolchains: %s\n' % fetch_arch
132 for arch in fetch_arch.split(','):
133 ret = toolchains.FetchAndInstall(arch)
138 # Work out how many commits to build. We want to build everything on the
139 # branch. We also build the upstream commit as a control so we can see
140 # problems introduced by the first commit on the branch.
141 col = terminal.Color()
142 count = options.count
143 has_range = options.branch and '..' in options.branch
145 if not options.branch:
149 count, msg = gitutil.CountCommitsInRange(options.git_dir,
152 count, msg = gitutil.CountCommitsInBranch(options.git_dir,
155 sys.exit(col.Color(col.RED, msg))
157 sys.exit(col.Color(col.RED, "Range '%s' has no commits" %
160 print col.Color(col.YELLOW, msg)
161 count += 1 # Build upstream commit also
164 str = ("No commits found to process in branch '%s': "
165 "set branch's upstream or use -c flag" % options.branch)
166 sys.exit(col.Color(col.RED, str))
168 # Work out what subset of the boards we are building
170 board_file = os.path.join(options.git, 'boards.cfg')
171 status = subprocess.call([os.path.join(options.git,
172 'tools/genboardscfg.py')])
174 sys.exit("Failed to generate boards.cfg")
176 boards = board.Boards()
177 boards.ReadBoards(os.path.join(options.git, 'boards.cfg'))
181 for arg in options.exclude:
182 exclude += arg.split(',')
184 why_selected = boards.SelectBoards(args, exclude)
185 selected = boards.GetSelected()
186 if not len(selected):
187 sys.exit(col.Color(col.RED, 'No matching boards found'))
189 # Read the metadata from the commits. First look at the upstream commit,
190 # then the ones in the branch. We would like to do something like
191 # upstream/master~..branch but that isn't possible if upstream/master is
192 # a merge commit (it will list all the commits that form part of the
194 # Conflicting tags are not a problem for buildman, since it does not use
195 # them. For example, Series-version is not useful for buildman. On the
196 # other hand conflicting tags will cause an error. So allow later tags
197 # to overwrite earlier ones by setting allow_overwrite=True
201 range_expr = options.branch
203 range_expr = gitutil.GetRangeInBranch(options.git_dir,
205 upstream_commit = gitutil.GetUpstream(options.git_dir,
207 series = patchstream.GetMetaDataForList(upstream_commit,
208 options.git_dir, 1, series=None, allow_overwrite=True)
210 series = patchstream.GetMetaDataForList(range_expr,
211 options.git_dir, None, series, allow_overwrite=True)
214 series = patchstream.GetMetaDataForList(options.branch,
215 options.git_dir, count, series=None, allow_overwrite=True)
218 options.verbose = True
219 if not options.summary:
220 options.show_errors = True
222 # By default we have one thread per CPU. But if there are not enough jobs
223 # we can have fewer threads and use a high '-j' value for make.
224 if not options.threads:
225 options.threads = min(multiprocessing.cpu_count(), len(selected))
227 options.jobs = max(1, (multiprocessing.cpu_count() +
228 len(selected) - 1) / len(selected))
231 options.step = len(series.commits) - 1
233 gnu_make = command.Output(os.path.join(options.git,
234 'scripts/show-gnu-make')).rstrip()
236 sys.exit('GNU Make not found')
238 # Create a new builder with the selected options.
239 output_dir = options.output_dir
241 dirname = options.branch.replace('/', '_')
242 # As a special case allow the board directory to be placed in the
243 # output directory itself rather than any subdirectory.
244 if not options.no_subdirs:
245 output_dir = os.path.join(options.output_dir, dirname)
246 if (clean_dir and output_dir != options.output_dir and
247 os.path.exists(output_dir)):
248 shutil.rmtree(output_dir)
249 builder = Builder(toolchains, output_dir, options.git_dir,
250 options.threads, options.jobs, gnu_make=gnu_make, checkout=True,
251 show_unknown=options.show_unknown, step=options.step,
252 no_subdirs=options.no_subdirs, full_path=options.full_path,
253 verbose_build=options.verbose_build)
254 builder.force_config_on_failure = not options.quick
256 builder.do_make = make_func
258 # For a dry run, just show our actions as a sanity check
260 ShowActions(series, why_selected, selected, builder, options)
262 builder.force_build = options.force_build
263 builder.force_build_failures = options.force_build_failures
264 builder.force_reconfig = options.force_reconfig
265 builder.in_tree = options.in_tree
267 # Work out which boards to build
268 board_selected = boards.GetSelectedDict()
271 commits = series.commits
272 # Number the commits for test purposes
273 for commit in range(len(commits)):
274 commits[commit].sequence = commit
278 Print(GetActionSummary(options.summary, commits, board_selected,
281 # We can't show function sizes without board details at present
282 if options.show_bloat:
283 options.show_detail = True
284 builder.SetDisplayOptions(options.show_errors, options.show_sizes,
285 options.show_detail, options.show_bloat,
286 options.list_error_boards,
289 builder.ShowSummary(commits, board_selected)
291 fail, warned = builder.BuildBoards(commits, board_selected,
292 options.keep_outputs, options.verbose)