1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright (c) 2013 The Chromium OS Authors.
12 from builder import Builder
16 from terminal import Print
22 """Returns a plural 's' if count is not 1"""
23 return 's' if count != 1 else ''
25 def GetActionSummary(is_summary, commits, selected, options):
26 """Return a string summarising the intended action.
33 count = (count + options.step - 1) / options.step
34 commit_str = '%d commit%s' % (count, GetPlural(count))
36 commit_str = 'current source'
37 str = '%s %s for %d boards' % (
38 'Summary of' if is_summary else 'Building', commit_str,
40 str += ' (%d thread%s, %d job%s per thread)' % (options.threads,
41 GetPlural(options.threads), options.jobs, GetPlural(options.jobs))
44 def ShowActions(series, why_selected, boards_selected, builder, options):
45 """Display a list of actions that we would take, if not a dry run.
49 why_selected: Dictionary where each key is a buildman argument
50 provided by the user, and the value is the list of boards
51 brought in by that argument. For example, 'arm' might bring
52 in 400 boards, so in this case the key would be 'arm' and
53 the value would be a list of board names.
54 boards_selected: Dict of selected boards, key is target name,
56 builder: The builder that will be used to build the commits
57 options: Command line options object
59 col = terminal.Color()
60 print 'Dry run, so not doing much. But I would do this:'
63 commits = series.commits
66 print GetActionSummary(False, commits, boards_selected,
68 print 'Build directory: %s' % builder.base_dir
70 for upto in range(0, len(series.commits), options.step):
71 commit = series.commits[upto]
72 print ' ', col.Color(col.YELLOW, commit.hash[:8], bright=False),
75 for arg in why_selected:
77 print arg, ': %d boards' % len(why_selected[arg])
79 print ' %s' % ' '.join(why_selected[arg])
80 print ('Total boards to build for each commit: %d\n' %
81 len(why_selected['all']))
83 def CheckOutputDir(output_dir):
84 """Make sure that the output directory is not within the current directory
86 If we try to use an output directory which is within the current directory
87 (which is assumed to hold the U-Boot source) we may end up deleting the
88 U-Boot source code. Detect this and print an error in this case.
91 output_dir: Output directory path to check
93 path = os.path.realpath(output_dir)
94 cwd_path = os.path.realpath('.')
96 if os.path.realpath(path) == cwd_path:
97 Print("Cannot use output directory '%s' since it is within the current directtory '%s'" %
100 parent = os.path.dirname(path)
105 def DoBuildman(options, args, toolchains=None, make_func=None, boards=None,
107 """The main control code for buildman
110 options: Command line options object
111 args: Command line arguments (list of strings)
112 toolchains: Toolchains to use - this should be a Toolchains()
113 object. If None, then it will be created and scanned
114 make_func: Make function to use for the builder. This is called
115 to execute 'make'. If this is None, the normal function
116 will be used, which calls the 'make' tool with suitable
117 arguments. This setting is useful for tests.
118 board: Boards() object to use, containing a list of available
119 boards. If this is None it will be created and scanned.
123 if options.full_help:
124 pager = os.getenv('PAGER')
127 fname = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])),
129 command.Run(pager, fname)
133 col = terminal.Color()
135 options.git_dir = os.path.join(options.git, '.git')
137 no_toolchains = toolchains is None
139 toolchains = toolchain.Toolchains()
141 if options.fetch_arch:
142 if options.fetch_arch == 'list':
143 sorted_list = toolchains.ListArchs()
144 print col.Color(col.BLUE, 'Available architectures: %s\n' %
145 ' '.join(sorted_list))
148 fetch_arch = options.fetch_arch
149 if fetch_arch == 'all':
150 fetch_arch = ','.join(toolchains.ListArchs())
151 print col.Color(col.CYAN, '\nDownloading toolchains: %s' %
153 for arch in fetch_arch.split(','):
155 ret = toolchains.FetchAndInstall(arch)
161 toolchains.GetSettings()
162 toolchains.Scan(options.list_tool_chains)
163 if options.list_tool_chains:
168 # Work out how many commits to build. We want to build everything on the
169 # branch. We also build the upstream commit as a control so we can see
170 # problems introduced by the first commit on the branch.
171 count = options.count
172 has_range = options.branch and '..' in options.branch
174 if not options.branch:
178 count, msg = gitutil.CountCommitsInRange(options.git_dir,
181 count, msg = gitutil.CountCommitsInBranch(options.git_dir,
184 sys.exit(col.Color(col.RED, msg))
186 sys.exit(col.Color(col.RED, "Range '%s' has no commits" %
189 print col.Color(col.YELLOW, msg)
190 count += 1 # Build upstream commit also
193 str = ("No commits found to process in branch '%s': "
194 "set branch's upstream or use -c flag" % options.branch)
195 sys.exit(col.Color(col.RED, str))
197 # Work out what subset of the boards we are building
199 board_file = os.path.join(options.git, 'boards.cfg')
200 status = subprocess.call([os.path.join(options.git,
201 'tools/genboardscfg.py')])
203 sys.exit("Failed to generate boards.cfg")
205 boards = board.Boards()
206 boards.ReadBoards(os.path.join(options.git, 'boards.cfg'))
210 for arg in options.exclude:
211 exclude += arg.split(',')
213 why_selected = boards.SelectBoards(args, exclude)
214 selected = boards.GetSelected()
215 if not len(selected):
216 sys.exit(col.Color(col.RED, 'No matching boards found'))
218 # Read the metadata from the commits. First look at the upstream commit,
219 # then the ones in the branch. We would like to do something like
220 # upstream/master~..branch but that isn't possible if upstream/master is
221 # a merge commit (it will list all the commits that form part of the
223 # Conflicting tags are not a problem for buildman, since it does not use
224 # them. For example, Series-version is not useful for buildman. On the
225 # other hand conflicting tags will cause an error. So allow later tags
226 # to overwrite earlier ones by setting allow_overwrite=True
230 range_expr = options.branch
232 range_expr = gitutil.GetRangeInBranch(options.git_dir,
234 upstream_commit = gitutil.GetUpstream(options.git_dir,
236 series = patchstream.GetMetaDataForList(upstream_commit,
237 options.git_dir, 1, series=None, allow_overwrite=True)
239 series = patchstream.GetMetaDataForList(range_expr,
240 options.git_dir, None, series, allow_overwrite=True)
243 series = patchstream.GetMetaDataForList(options.branch,
244 options.git_dir, count, series=None, allow_overwrite=True)
247 if not options.dry_run:
248 options.verbose = True
249 if not options.summary:
250 options.show_errors = True
252 # By default we have one thread per CPU. But if there are not enough jobs
253 # we can have fewer threads and use a high '-j' value for make.
254 if not options.threads:
255 options.threads = min(multiprocessing.cpu_count(), len(selected))
257 options.jobs = max(1, (multiprocessing.cpu_count() +
258 len(selected) - 1) / len(selected))
261 options.step = len(series.commits) - 1
263 gnu_make = command.Output(os.path.join(options.git,
264 'scripts/show-gnu-make'), raise_on_error=False).rstrip()
266 sys.exit('GNU Make not found')
268 # Create a new builder with the selected options.
269 output_dir = options.output_dir
271 dirname = options.branch.replace('/', '_')
272 # As a special case allow the board directory to be placed in the
273 # output directory itself rather than any subdirectory.
274 if not options.no_subdirs:
275 output_dir = os.path.join(options.output_dir, dirname)
276 if clean_dir and os.path.exists(output_dir):
277 shutil.rmtree(output_dir)
278 CheckOutputDir(output_dir)
279 builder = Builder(toolchains, output_dir, options.git_dir,
280 options.threads, options.jobs, gnu_make=gnu_make, checkout=True,
281 show_unknown=options.show_unknown, step=options.step,
282 no_subdirs=options.no_subdirs, full_path=options.full_path,
283 verbose_build=options.verbose_build,
284 incremental=options.incremental,
285 per_board_out_dir=options.per_board_out_dir,
286 config_only=options.config_only,
287 squash_config_y=not options.preserve_config_y,
288 warnings_as_errors=options.warnings_as_errors)
289 builder.force_config_on_failure = not options.quick
291 builder.do_make = make_func
293 # For a dry run, just show our actions as a sanity check
295 ShowActions(series, why_selected, selected, builder, options)
297 builder.force_build = options.force_build
298 builder.force_build_failures = options.force_build_failures
299 builder.force_reconfig = options.force_reconfig
300 builder.in_tree = options.in_tree
302 # Work out which boards to build
303 board_selected = boards.GetSelectedDict()
306 commits = series.commits
307 # Number the commits for test purposes
308 for commit in range(len(commits)):
309 commits[commit].sequence = commit
313 Print(GetActionSummary(options.summary, commits, board_selected,
316 # We can't show function sizes without board details at present
317 if options.show_bloat:
318 options.show_detail = True
319 builder.SetDisplayOptions(options.show_errors, options.show_sizes,
320 options.show_detail, options.show_bloat,
321 options.list_error_boards,
324 builder.ShowSummary(commits, board_selected)
326 fail, warned = builder.BuildBoards(commits, board_selected,
327 options.keep_outputs, options.verbose)