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,
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
59 board_warnings: List of warnings obtained from board selected
61 col = terminal.Color()
62 print 'Dry run, so not doing much. But I would do this:'
65 commits = series.commits
68 print GetActionSummary(False, commits, boards_selected,
70 print 'Build directory: %s' % builder.base_dir
72 for upto in range(0, len(series.commits), options.step):
73 commit = series.commits[upto]
74 print ' ', col.Color(col.YELLOW, commit.hash[:8], bright=False),
77 for arg in why_selected:
79 print arg, ': %d boards' % len(why_selected[arg])
81 print ' %s' % ' '.join(why_selected[arg])
82 print ('Total boards to build for each commit: %d\n' %
83 len(why_selected['all']))
85 for warning in board_warnings:
86 print col.Color(col.YELLOW, warning)
88 def CheckOutputDir(output_dir):
89 """Make sure that the output directory is not within the current directory
91 If we try to use an output directory which is within the current directory
92 (which is assumed to hold the U-Boot source) we may end up deleting the
93 U-Boot source code. Detect this and print an error in this case.
96 output_dir: Output directory path to check
98 path = os.path.realpath(output_dir)
99 cwd_path = os.path.realpath('.')
101 if os.path.realpath(path) == cwd_path:
102 Print("Cannot use output directory '%s' since it is within the current directory '%s'" %
105 parent = os.path.dirname(path)
110 def DoBuildman(options, args, toolchains=None, make_func=None, boards=None,
112 """The main control code for buildman
115 options: Command line options object
116 args: Command line arguments (list of strings)
117 toolchains: Toolchains to use - this should be a Toolchains()
118 object. If None, then it will be created and scanned
119 make_func: Make function to use for the builder. This is called
120 to execute 'make'. If this is None, the normal function
121 will be used, which calls the 'make' tool with suitable
122 arguments. This setting is useful for tests.
123 board: Boards() object to use, containing a list of available
124 boards. If this is None it will be created and scanned.
128 if options.full_help:
129 pager = os.getenv('PAGER')
132 fname = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])),
134 command.Run(pager, fname)
138 col = terminal.Color()
140 options.git_dir = os.path.join(options.git, '.git')
142 no_toolchains = toolchains is None
144 toolchains = toolchain.Toolchains(options.override_toolchain)
146 if options.fetch_arch:
147 if options.fetch_arch == 'list':
148 sorted_list = toolchains.ListArchs()
149 print col.Color(col.BLUE, 'Available architectures: %s\n' %
150 ' '.join(sorted_list))
153 fetch_arch = options.fetch_arch
154 if fetch_arch == 'all':
155 fetch_arch = ','.join(toolchains.ListArchs())
156 print col.Color(col.CYAN, '\nDownloading toolchains: %s' %
158 for arch in fetch_arch.split(','):
160 ret = toolchains.FetchAndInstall(arch)
166 toolchains.GetSettings()
167 toolchains.Scan(options.list_tool_chains and options.verbose)
168 if options.list_tool_chains:
173 # Work out how many commits to build. We want to build everything on the
174 # branch. We also build the upstream commit as a control so we can see
175 # problems introduced by the first commit on the branch.
176 count = options.count
177 has_range = options.branch and '..' in options.branch
179 if not options.branch:
183 count, msg = gitutil.CountCommitsInRange(options.git_dir,
186 count, msg = gitutil.CountCommitsInBranch(options.git_dir,
189 sys.exit(col.Color(col.RED, msg))
191 sys.exit(col.Color(col.RED, "Range '%s' has no commits" %
194 print col.Color(col.YELLOW, msg)
195 count += 1 # Build upstream commit also
198 str = ("No commits found to process in branch '%s': "
199 "set branch's upstream or use -c flag" % options.branch)
200 sys.exit(col.Color(col.RED, str))
202 # Work out what subset of the boards we are building
204 board_file = os.path.join(options.git, 'boards.cfg')
205 status = subprocess.call([os.path.join(options.git,
206 'tools/genboardscfg.py')])
208 sys.exit("Failed to generate boards.cfg")
210 boards = board.Boards()
211 boards.ReadBoards(os.path.join(options.git, 'boards.cfg'))
215 for arg in options.exclude:
216 exclude += arg.split(',')
220 requested_boards = []
221 for b in options.boards:
222 requested_boards += b.split(',')
224 requested_boards = None
225 why_selected, board_warnings = boards.SelectBoards(args, exclude,
227 selected = boards.GetSelected()
228 if not len(selected):
229 sys.exit(col.Color(col.RED, 'No matching boards found'))
231 # Read the metadata from the commits. First look at the upstream commit,
232 # then the ones in the branch. We would like to do something like
233 # upstream/master~..branch but that isn't possible if upstream/master is
234 # a merge commit (it will list all the commits that form part of the
236 # Conflicting tags are not a problem for buildman, since it does not use
237 # them. For example, Series-version is not useful for buildman. On the
238 # other hand conflicting tags will cause an error. So allow later tags
239 # to overwrite earlier ones by setting allow_overwrite=True
243 range_expr = options.branch
245 range_expr = gitutil.GetRangeInBranch(options.git_dir,
247 upstream_commit = gitutil.GetUpstream(options.git_dir,
249 series = patchstream.GetMetaDataForList(upstream_commit,
250 options.git_dir, 1, series=None, allow_overwrite=True)
252 series = patchstream.GetMetaDataForList(range_expr,
253 options.git_dir, None, series, allow_overwrite=True)
256 series = patchstream.GetMetaDataForList(options.branch,
257 options.git_dir, count, series=None, allow_overwrite=True)
260 if not options.dry_run:
261 options.verbose = True
262 if not options.summary:
263 options.show_errors = True
265 # By default we have one thread per CPU. But if there are not enough jobs
266 # we can have fewer threads and use a high '-j' value for make.
267 if not options.threads:
268 options.threads = min(multiprocessing.cpu_count(), len(selected))
270 options.jobs = max(1, (multiprocessing.cpu_count() +
271 len(selected) - 1) / len(selected))
274 options.step = len(series.commits) - 1
276 gnu_make = command.Output(os.path.join(options.git,
277 'scripts/show-gnu-make'), raise_on_error=False).rstrip()
279 sys.exit('GNU Make not found')
281 # Create a new builder with the selected options.
282 output_dir = options.output_dir
284 dirname = options.branch.replace('/', '_')
285 # As a special case allow the board directory to be placed in the
286 # output directory itself rather than any subdirectory.
287 if not options.no_subdirs:
288 output_dir = os.path.join(options.output_dir, dirname)
289 if clean_dir and os.path.exists(output_dir):
290 shutil.rmtree(output_dir)
291 CheckOutputDir(output_dir)
292 builder = Builder(toolchains, output_dir, options.git_dir,
293 options.threads, options.jobs, gnu_make=gnu_make, checkout=True,
294 show_unknown=options.show_unknown, step=options.step,
295 no_subdirs=options.no_subdirs, full_path=options.full_path,
296 verbose_build=options.verbose_build,
297 incremental=options.incremental,
298 per_board_out_dir=options.per_board_out_dir,
299 config_only=options.config_only,
300 squash_config_y=not options.preserve_config_y,
301 warnings_as_errors=options.warnings_as_errors)
302 builder.force_config_on_failure = not options.quick
304 builder.do_make = make_func
306 # For a dry run, just show our actions as a sanity check
308 ShowActions(series, why_selected, selected, builder, options,
311 builder.force_build = options.force_build
312 builder.force_build_failures = options.force_build_failures
313 builder.force_reconfig = options.force_reconfig
314 builder.in_tree = options.in_tree
316 # Work out which boards to build
317 board_selected = boards.GetSelectedDict()
320 commits = series.commits
321 # Number the commits for test purposes
322 for commit in range(len(commits)):
323 commits[commit].sequence = commit
327 Print(GetActionSummary(options.summary, commits, board_selected,
330 # We can't show function sizes without board details at present
331 if options.show_bloat:
332 options.show_detail = True
333 builder.SetDisplayOptions(options.show_errors, options.show_sizes,
334 options.show_detail, options.show_bloat,
335 options.list_error_boards,
337 options.show_environment)
339 builder.ShowSummary(commits, board_selected)
341 fail, warned = builder.BuildBoards(commits, board_selected,
342 options.keep_outputs, options.verbose)