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(sys.argv[0]), 'README')
105 command.Run(pager, fname)
110 options.git_dir = os.path.join(options.git, '.git')
113 toolchains = toolchain.Toolchains()
114 toolchains.GetSettings()
115 toolchains.Scan(options.list_tool_chains)
116 if options.list_tool_chains:
121 if options.fetch_arch:
122 if options.fetch_arch == 'list':
123 sorted_list = toolchains.ListArchs()
124 print 'Available architectures: %s\n' % ' '.join(sorted_list)
127 fetch_arch = options.fetch_arch
128 if fetch_arch == 'all':
129 fetch_arch = ','.join(toolchains.ListArchs())
130 print 'Downloading toolchains: %s\n' % fetch_arch
131 for arch in fetch_arch.split(','):
132 ret = toolchains.FetchAndInstall(arch)
137 # Work out how many commits to build. We want to build everything on the
138 # branch. We also build the upstream commit as a control so we can see
139 # problems introduced by the first commit on the branch.
140 col = terminal.Color()
141 count = options.count
142 has_range = options.branch and '..' in options.branch
144 if not options.branch:
148 count, msg = gitutil.CountCommitsInRange(options.git_dir,
151 count, msg = gitutil.CountCommitsInBranch(options.git_dir,
154 sys.exit(col.Color(col.RED, msg))
156 sys.exit(col.Color(col.RED, "Range '%s' has no commits" %
159 print col.Color(col.YELLOW, msg)
160 count += 1 # Build upstream commit also
163 str = ("No commits found to process in branch '%s': "
164 "set branch's upstream or use -c flag" % options.branch)
165 sys.exit(col.Color(col.RED, str))
167 # Work out what subset of the boards we are building
169 board_file = os.path.join(options.git, 'boards.cfg')
170 status = subprocess.call([os.path.join(options.git,
171 'tools/genboardscfg.py')])
173 sys.exit("Failed to generate boards.cfg")
175 boards = board.Boards()
176 boards.ReadBoards(os.path.join(options.git, 'boards.cfg'))
180 for arg in options.exclude:
181 exclude += arg.split(',')
183 why_selected = boards.SelectBoards(args, exclude)
184 selected = boards.GetSelected()
185 if not len(selected):
186 sys.exit(col.Color(col.RED, 'No matching boards found'))
188 # Read the metadata from the commits. First look at the upstream commit,
189 # then the ones in the branch. We would like to do something like
190 # upstream/master~..branch but that isn't possible if upstream/master is
191 # a merge commit (it will list all the commits that form part of the
193 # Conflicting tags are not a problem for buildman, since it does not use
194 # them. For example, Series-version is not useful for buildman. On the
195 # other hand conflicting tags will cause an error. So allow later tags
196 # to overwrite earlier ones by setting allow_overwrite=True
200 range_expr = options.branch
202 range_expr = gitutil.GetRangeInBranch(options.git_dir,
204 upstream_commit = gitutil.GetUpstream(options.git_dir,
206 series = patchstream.GetMetaDataForList(upstream_commit,
207 options.git_dir, 1, series=None, allow_overwrite=True)
209 series = patchstream.GetMetaDataForList(range_expr,
210 options.git_dir, None, series, allow_overwrite=True)
213 series = patchstream.GetMetaDataForList(options.branch,
214 options.git_dir, count, series=None, allow_overwrite=True)
217 options.verbose = True
218 if not options.summary:
219 options.show_errors = True
221 # By default we have one thread per CPU. But if there are not enough jobs
222 # we can have fewer threads and use a high '-j' value for make.
223 if not options.threads:
224 options.threads = min(multiprocessing.cpu_count(), len(selected))
226 options.jobs = max(1, (multiprocessing.cpu_count() +
227 len(selected) - 1) / len(selected))
230 options.step = len(series.commits) - 1
232 gnu_make = command.Output(os.path.join(options.git,
233 'scripts/show-gnu-make')).rstrip()
235 sys.exit('GNU Make not found')
237 # Create a new builder with the selected options.
238 output_dir = options.output_dir
240 dirname = options.branch.replace('/', '_')
241 # As a special case allow the board directory to be placed in the
242 # output directory itself rather than any subdirectory.
243 if not options.no_subdirs:
244 output_dir = os.path.join(options.output_dir, dirname)
245 if (clean_dir and output_dir != options.output_dir and
246 os.path.exists(output_dir)):
247 shutil.rmtree(output_dir)
248 builder = Builder(toolchains, output_dir, options.git_dir,
249 options.threads, options.jobs, gnu_make=gnu_make, checkout=True,
250 show_unknown=options.show_unknown, step=options.step,
251 no_subdirs=options.no_subdirs, full_path=options.full_path,
252 verbose_build=options.verbose_build)
253 builder.force_config_on_failure = not options.quick
255 builder.do_make = make_func
257 # For a dry run, just show our actions as a sanity check
259 ShowActions(series, why_selected, selected, builder, options)
261 builder.force_build = options.force_build
262 builder.force_build_failures = options.force_build_failures
263 builder.force_reconfig = options.force_reconfig
264 builder.in_tree = options.in_tree
266 # Work out which boards to build
267 board_selected = boards.GetSelectedDict()
270 commits = series.commits
271 # Number the commits for test purposes
272 for commit in range(len(commits)):
273 commits[commit].sequence = commit
277 Print(GetActionSummary(options.summary, commits, board_selected,
280 # We can't show function sizes without board details at present
281 if options.show_bloat:
282 options.show_detail = True
283 builder.SetDisplayOptions(options.show_errors, options.show_sizes,
284 options.show_detail, options.show_bloat,
285 options.list_error_boards,
288 builder.ShowSummary(commits, board_selected)
290 fail, warned = builder.BuildBoards(commits, board_selected,
291 options.keep_outputs, options.verbose)