blob: 2c3ba8b77fb1d9cf304b5e5ca61a451fd6634c2c [file] [log] [blame]
Googlere00b8eb2019-07-08 16:37:07 -07001# Copyright (c) 2013 The Chromium OS Authors.
2#
Googler695f9d92023-09-11 15:38:29 +08003# SPDX-License-Identifier: GPL-2.0+
4#
Googlere00b8eb2019-07-08 16:37:07 -07005
6import multiprocessing
7import os
8import shutil
9import sys
10
11import board
12import bsettings
13from builder import Builder
14import gitutil
15import patchstream
16import terminal
17from terminal import Print
18import toolchain
19import command
20import subprocess
21
22def GetPlural(count):
23 """Returns a plural 's' if count is not 1"""
24 return 's' if count != 1 else ''
25
26def GetActionSummary(is_summary, commits, selected, options):
27 """Return a string summarising the intended action.
28
29 Returns:
30 Summary string.
31 """
32 if commits:
33 count = len(commits)
34 count = (count + options.step - 1) / options.step
35 commit_str = '%d commit%s' % (count, GetPlural(count))
36 else:
37 commit_str = 'current source'
38 str = '%s %s for %d boards' % (
39 'Summary of' if is_summary else 'Building', commit_str,
40 len(selected))
41 str += ' (%d thread%s, %d job%s per thread)' % (options.threads,
42 GetPlural(options.threads), options.jobs, GetPlural(options.jobs))
43 return str
44
Googler695f9d92023-09-11 15:38:29 +080045def ShowActions(series, why_selected, boards_selected, builder, options):
Googlere00b8eb2019-07-08 16:37:07 -070046 """Display a list of actions that we would take, if not a dry run.
47
48 Args:
49 series: Series object
50 why_selected: Dictionary where each key is a buildman argument
Googler695f9d92023-09-11 15:38:29 +080051 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
Googlere00b8eb2019-07-08 16:37:07 -070054 the value would be a list of board names.
55 boards_selected: Dict of selected boards, key is target name,
56 value is Board object
57 builder: The builder that will be used to build the commits
58 options: Command line options object
Googlere00b8eb2019-07-08 16:37:07 -070059 """
60 col = terminal.Color()
61 print 'Dry run, so not doing much. But I would do this:'
62 print
63 if series:
64 commits = series.commits
65 else:
66 commits = None
67 print GetActionSummary(False, commits, boards_selected,
68 options)
69 print 'Build directory: %s' % builder.base_dir
70 if commits:
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),
74 print commit.subject
75 print
76 for arg in why_selected:
77 if arg != 'all':
Googler695f9d92023-09-11 15:38:29 +080078 print arg, ': %d boards' % why_selected[arg]
Googlere00b8eb2019-07-08 16:37:07 -070079 print ('Total boards to build for each commit: %d\n' %
Googler695f9d92023-09-11 15:38:29 +080080 why_selected['all'])
Googlere00b8eb2019-07-08 16:37:07 -070081
82def DoBuildman(options, args, toolchains=None, make_func=None, boards=None,
83 clean_dir=False):
84 """The main control code for buildman
85
86 Args:
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.
97 """
98 global builder
99
100 if options.full_help:
101 pager = os.getenv('PAGER')
102 if not pager:
103 pager = 'more'
Googler695f9d92023-09-11 15:38:29 +0800104 fname = os.path.join(os.path.dirname(sys.argv[0]), 'README')
Googlere00b8eb2019-07-08 16:37:07 -0700105 command.Run(pager, fname)
106 return 0
107
108 gitutil.Setup()
Googlere00b8eb2019-07-08 16:37:07 -0700109
110 options.git_dir = os.path.join(options.git, '.git')
111
Googler695f9d92023-09-11 15:38:29 +0800112 if not toolchains:
Googlere00b8eb2019-07-08 16:37:07 -0700113 toolchains = toolchain.Toolchains()
Googlere00b8eb2019-07-08 16:37:07 -0700114 toolchains.GetSettings()
Googler695f9d92023-09-11 15:38:29 +0800115 toolchains.Scan(options.list_tool_chains)
Googlere00b8eb2019-07-08 16:37:07 -0700116 if options.list_tool_chains:
117 toolchains.List()
118 print
119 return 0
120
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.
Googler695f9d92023-09-11 15:38:29 +0800124 col = terminal.Color()
Googlere00b8eb2019-07-08 16:37:07 -0700125 count = options.count
Googlere00b8eb2019-07-08 16:37:07 -0700126 if count == -1:
127 if not options.branch:
128 count = 1
129 else:
Googler695f9d92023-09-11 15:38:29 +0800130 count = gitutil.CountCommitsInBranch(options.git_dir,
131 options.branch)
Googlere00b8eb2019-07-08 16:37:07 -0700132 if count is None:
Googler695f9d92023-09-11 15:38:29 +0800133 str = ("Branch '%s' not found or has no upstream" %
134 options.branch)
135 sys.exit(col.Color(col.RED, str))
Googlere00b8eb2019-07-08 16:37:07 -0700136 count += 1 # Build upstream commit also
137
138 if not count:
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))
142
143 # Work out what subset of the boards we are building
144 if not boards:
145 board_file = os.path.join(options.git, 'boards.cfg')
146 status = subprocess.call([os.path.join(options.git,
147 'tools/genboardscfg.py')])
148 if status != 0:
149 sys.exit("Failed to generate boards.cfg")
150
151 boards = board.Boards()
152 boards.ReadBoards(os.path.join(options.git, 'boards.cfg'))
153
154 exclude = []
155 if options.exclude:
156 for arg in options.exclude:
157 exclude += arg.split(',')
158
Googler695f9d92023-09-11 15:38:29 +0800159 why_selected = boards.SelectBoards(args, exclude)
Googlere00b8eb2019-07-08 16:37:07 -0700160 selected = boards.GetSelected()
161 if not len(selected):
162 sys.exit(col.Color(col.RED, 'No matching boards found'))
163
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
168 # merge)
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
173 if options.branch:
174 if count == -1:
Googler695f9d92023-09-11 15:38:29 +0800175 range_expr = gitutil.GetRangeInBranch(options.git_dir,
176 options.branch)
Googlere00b8eb2019-07-08 16:37:07 -0700177 upstream_commit = gitutil.GetUpstream(options.git_dir,
178 options.branch)
179 series = patchstream.GetMetaDataForList(upstream_commit,
180 options.git_dir, 1, series=None, allow_overwrite=True)
181
182 series = patchstream.GetMetaDataForList(range_expr,
183 options.git_dir, None, series, allow_overwrite=True)
184 else:
185 # Honour the count
186 series = patchstream.GetMetaDataForList(options.branch,
187 options.git_dir, count, series=None, allow_overwrite=True)
188 else:
189 series = None
Googler695f9d92023-09-11 15:38:29 +0800190 options.verbose = True
Googlere00b8eb2019-07-08 16:37:07 -0700191
192 # By default we have one thread per CPU. But if there are not enough jobs
193 # we can have fewer threads and use a high '-j' value for make.
194 if not options.threads:
195 options.threads = min(multiprocessing.cpu_count(), len(selected))
196 if not options.jobs:
197 options.jobs = max(1, (multiprocessing.cpu_count() +
198 len(selected) - 1) / len(selected))
199
200 if not options.step:
201 options.step = len(series.commits) - 1
202
203 gnu_make = command.Output(os.path.join(options.git,
Googler695f9d92023-09-11 15:38:29 +0800204 'scripts/show-gnu-make')).rstrip()
Googlere00b8eb2019-07-08 16:37:07 -0700205 if not gnu_make:
206 sys.exit('GNU Make not found')
207
Googler695f9d92023-09-11 15:38:29 +0800208 # Create a new builder with the selected options
Googlere00b8eb2019-07-08 16:37:07 -0700209 if options.branch:
210 dirname = options.branch.replace('/', '_')
Googler695f9d92023-09-11 15:38:29 +0800211 else:
212 dirname = 'current'
213 output_dir = os.path.join(options.output_dir, dirname)
214 if clean_dir and os.path.exists(output_dir):
215 shutil.rmtree(output_dir)
Googlere00b8eb2019-07-08 16:37:07 -0700216 builder = Builder(toolchains, output_dir, options.git_dir,
217 options.threads, options.jobs, gnu_make=gnu_make, checkout=True,
Googler695f9d92023-09-11 15:38:29 +0800218 show_unknown=options.show_unknown, step=options.step)
Googlere00b8eb2019-07-08 16:37:07 -0700219 builder.force_config_on_failure = not options.quick
220 if make_func:
221 builder.do_make = make_func
222
223 # For a dry run, just show our actions as a sanity check
224 if options.dry_run:
Googler695f9d92023-09-11 15:38:29 +0800225 ShowActions(series, why_selected, selected, builder, options)
Googlere00b8eb2019-07-08 16:37:07 -0700226 else:
227 builder.force_build = options.force_build
228 builder.force_build_failures = options.force_build_failures
229 builder.force_reconfig = options.force_reconfig
230 builder.in_tree = options.in_tree
231
232 # Work out which boards to build
233 board_selected = boards.GetSelectedDict()
234
235 if series:
236 commits = series.commits
237 # Number the commits for test purposes
238 for commit in range(len(commits)):
239 commits[commit].sequence = commit
240 else:
241 commits = None
242
243 Print(GetActionSummary(options.summary, commits, board_selected,
244 options))
245
246 # We can't show function sizes without board details at present
247 if options.show_bloat:
248 options.show_detail = True
249 builder.SetDisplayOptions(options.show_errors, options.show_sizes,
250 options.show_detail, options.show_bloat,
Googler695f9d92023-09-11 15:38:29 +0800251 options.list_error_boards)
Googlere00b8eb2019-07-08 16:37:07 -0700252 if options.summary:
253 builder.ShowSummary(commits, board_selected)
254 else:
255 fail, warned = builder.BuildBoards(commits, board_selected,
256 options.keep_outputs, options.verbose)
257 if fail:
258 return 128
259 elif warned:
260 return 129
261 return 0