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 # Work out how many commits to build. We want to build everything on the
122 # branch. We also build the upstream commit as a control so we can see
123 # problems introduced by the first commit on the branch.
124 col = terminal.Color()
125 count = options.count
127 if not options.branch:
130 count, msg = gitutil.CountCommitsInBranch(options.git_dir,
133 sys.exit(col.Color(col.RED, msg))
135 print col.Color(col.YELLOW, msg)
136 count += 1 # Build upstream commit also
139 str = ("No commits found to process in branch '%s': "
140 "set branch's upstream or use -c flag" % options.branch)
141 sys.exit(col.Color(col.RED, str))
143 # Work out what subset of the boards we are building
145 board_file = os.path.join(options.git, 'boards.cfg')
146 status = subprocess.call([os.path.join(options.git,
147 'tools/genboardscfg.py')])
149 sys.exit("Failed to generate boards.cfg")
151 boards = board.Boards()
152 boards.ReadBoards(os.path.join(options.git, 'boards.cfg'))
156 for arg in options.exclude:
157 exclude += arg.split(',')
159 why_selected = boards.SelectBoards(args, exclude)
160 selected = boards.GetSelected()
161 if not len(selected):
162 sys.exit(col.Color(col.RED, 'No matching boards found'))
164 # Read the metadata from the commits. First look at the upstream commit,
165 # then the ones in the branch. We would like to do something like
166 # upstream/master~..branch but that isn't possible if upstream/master is
167 # a merge commit (it will list all the commits that form part of the
169 # Conflicting tags are not a problem for buildman, since it does not use
170 # them. For example, Series-version is not useful for buildman. On the
171 # other hand conflicting tags will cause an error. So allow later tags
172 # to overwrite earlier ones by setting allow_overwrite=True
175 range_expr = gitutil.GetRangeInBranch(options.git_dir,
177 upstream_commit = gitutil.GetUpstream(options.git_dir,
179 series = patchstream.GetMetaDataForList(upstream_commit,
180 options.git_dir, 1, series=None, allow_overwrite=True)
182 series = patchstream.GetMetaDataForList(range_expr,
183 options.git_dir, None, series, allow_overwrite=True)
186 series = patchstream.GetMetaDataForList(options.branch,
187 options.git_dir, count, series=None, allow_overwrite=True)
190 options.verbose = True
191 if not options.summary:
192 options.show_errors = True
194 # By default we have one thread per CPU. But if there are not enough jobs
195 # we can have fewer threads and use a high '-j' value for make.
196 if not options.threads:
197 options.threads = min(multiprocessing.cpu_count(), len(selected))
199 options.jobs = max(1, (multiprocessing.cpu_count() +
200 len(selected) - 1) / len(selected))
203 options.step = len(series.commits) - 1
205 gnu_make = command.Output(os.path.join(options.git,
206 'scripts/show-gnu-make')).rstrip()
208 sys.exit('GNU Make not found')
210 # Create a new builder with the selected options.
211 output_dir = options.output_dir
213 dirname = options.branch.replace('/', '_')
214 # As a special case allow the board directory to be placed in the
215 # output directory itself rather than any subdirectory.
216 if not options.no_subdirs:
217 output_dir = os.path.join(options.output_dir, dirname)
218 if (clean_dir and output_dir != options.output_dir and
219 os.path.exists(output_dir)):
220 shutil.rmtree(output_dir)
221 builder = Builder(toolchains, output_dir, options.git_dir,
222 options.threads, options.jobs, gnu_make=gnu_make, checkout=True,
223 show_unknown=options.show_unknown, step=options.step,
224 no_subdirs=options.no_subdirs)
225 builder.force_config_on_failure = not options.quick
227 builder.do_make = make_func
229 # For a dry run, just show our actions as a sanity check
231 ShowActions(series, why_selected, selected, builder, options)
233 builder.force_build = options.force_build
234 builder.force_build_failures = options.force_build_failures
235 builder.force_reconfig = options.force_reconfig
236 builder.in_tree = options.in_tree
238 # Work out which boards to build
239 board_selected = boards.GetSelectedDict()
242 commits = series.commits
243 # Number the commits for test purposes
244 for commit in range(len(commits)):
245 commits[commit].sequence = commit
249 Print(GetActionSummary(options.summary, commits, board_selected,
252 # We can't show function sizes without board details at present
253 if options.show_bloat:
254 options.show_detail = True
255 builder.SetDisplayOptions(options.show_errors, options.show_sizes,
256 options.show_detail, options.show_bloat,
257 options.list_error_boards)
259 builder.ShowSummary(commits, board_selected)
261 fail, warned = builder.BuildBoards(commits, board_selected,
262 options.keep_outputs, options.verbose)