]> git.ipfire.org Git - thirdparty/u-boot.git/blob - tools/buildman/control.py
SPDX: Convert all of our single license tags to Linux Kernel style
[thirdparty/u-boot.git] / tools / buildman / control.py
1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright (c) 2013 The Chromium OS Authors.
3 #
4
5 import multiprocessing
6 import os
7 import shutil
8 import sys
9
10 import board
11 import bsettings
12 from builder import Builder
13 import gitutil
14 import patchstream
15 import terminal
16 from terminal import Print
17 import toolchain
18 import command
19 import subprocess
20
21 def GetPlural(count):
22 """Returns a plural 's' if count is not 1"""
23 return 's' if count != 1 else ''
24
25 def GetActionSummary(is_summary, commits, selected, options):
26 """Return a string summarising the intended action.
27
28 Returns:
29 Summary string.
30 """
31 if commits:
32 count = len(commits)
33 count = (count + options.step - 1) / options.step
34 commit_str = '%d commit%s' % (count, GetPlural(count))
35 else:
36 commit_str = 'current source'
37 str = '%s %s for %d boards' % (
38 'Summary of' if is_summary else 'Building', commit_str,
39 len(selected))
40 str += ' (%d thread%s, %d job%s per thread)' % (options.threads,
41 GetPlural(options.threads), options.jobs, GetPlural(options.jobs))
42 return str
43
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.
46
47 Args:
48 series: Series object
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,
55 value is Board object
56 builder: The builder that will be used to build the commits
57 options: Command line options object
58 """
59 col = terminal.Color()
60 print 'Dry run, so not doing much. But I would do this:'
61 print
62 if series:
63 commits = series.commits
64 else:
65 commits = None
66 print GetActionSummary(False, commits, boards_selected,
67 options)
68 print 'Build directory: %s' % builder.base_dir
69 if commits:
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),
73 print commit.subject
74 print
75 for arg in why_selected:
76 if arg != 'all':
77 print arg, ': %d boards' % len(why_selected[arg])
78 if options.verbose:
79 print ' %s' % ' '.join(why_selected[arg])
80 print ('Total boards to build for each commit: %d\n' %
81 len(why_selected['all']))
82
83 def DoBuildman(options, args, toolchains=None, make_func=None, boards=None,
84 clean_dir=False):
85 """The main control code for buildman
86
87 Args:
88 options: Command line options object
89 args: Command line arguments (list of strings)
90 toolchains: Toolchains to use - this should be a Toolchains()
91 object. If None, then it will be created and scanned
92 make_func: Make function to use for the builder. This is called
93 to execute 'make'. If this is None, the normal function
94 will be used, which calls the 'make' tool with suitable
95 arguments. This setting is useful for tests.
96 board: Boards() object to use, containing a list of available
97 boards. If this is None it will be created and scanned.
98 """
99 global builder
100
101 if options.full_help:
102 pager = os.getenv('PAGER')
103 if not pager:
104 pager = 'more'
105 fname = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])),
106 'README')
107 command.Run(pager, fname)
108 return 0
109
110 gitutil.Setup()
111 col = terminal.Color()
112
113 options.git_dir = os.path.join(options.git, '.git')
114
115 no_toolchains = toolchains is None
116 if no_toolchains:
117 toolchains = toolchain.Toolchains()
118
119 if options.fetch_arch:
120 if options.fetch_arch == 'list':
121 sorted_list = toolchains.ListArchs()
122 print col.Color(col.BLUE, 'Available architectures: %s\n' %
123 ' '.join(sorted_list))
124 return 0
125 else:
126 fetch_arch = options.fetch_arch
127 if fetch_arch == 'all':
128 fetch_arch = ','.join(toolchains.ListArchs())
129 print col.Color(col.CYAN, '\nDownloading toolchains: %s' %
130 fetch_arch)
131 for arch in fetch_arch.split(','):
132 print
133 ret = toolchains.FetchAndInstall(arch)
134 if ret:
135 return ret
136 return 0
137
138 if no_toolchains:
139 toolchains.GetSettings()
140 toolchains.Scan(options.list_tool_chains)
141 if options.list_tool_chains:
142 toolchains.List()
143 print
144 return 0
145
146 # Work out how many commits to build. We want to build everything on the
147 # branch. We also build the upstream commit as a control so we can see
148 # problems introduced by the first commit on the branch.
149 count = options.count
150 has_range = options.branch and '..' in options.branch
151 if count == -1:
152 if not options.branch:
153 count = 1
154 else:
155 if has_range:
156 count, msg = gitutil.CountCommitsInRange(options.git_dir,
157 options.branch)
158 else:
159 count, msg = gitutil.CountCommitsInBranch(options.git_dir,
160 options.branch)
161 if count is None:
162 sys.exit(col.Color(col.RED, msg))
163 elif count == 0:
164 sys.exit(col.Color(col.RED, "Range '%s' has no commits" %
165 options.branch))
166 if msg:
167 print col.Color(col.YELLOW, msg)
168 count += 1 # Build upstream commit also
169
170 if not count:
171 str = ("No commits found to process in branch '%s': "
172 "set branch's upstream or use -c flag" % options.branch)
173 sys.exit(col.Color(col.RED, str))
174
175 # Work out what subset of the boards we are building
176 if not boards:
177 board_file = os.path.join(options.git, 'boards.cfg')
178 status = subprocess.call([os.path.join(options.git,
179 'tools/genboardscfg.py')])
180 if status != 0:
181 sys.exit("Failed to generate boards.cfg")
182
183 boards = board.Boards()
184 boards.ReadBoards(os.path.join(options.git, 'boards.cfg'))
185
186 exclude = []
187 if options.exclude:
188 for arg in options.exclude:
189 exclude += arg.split(',')
190
191 why_selected = boards.SelectBoards(args, exclude)
192 selected = boards.GetSelected()
193 if not len(selected):
194 sys.exit(col.Color(col.RED, 'No matching boards found'))
195
196 # Read the metadata from the commits. First look at the upstream commit,
197 # then the ones in the branch. We would like to do something like
198 # upstream/master~..branch but that isn't possible if upstream/master is
199 # a merge commit (it will list all the commits that form part of the
200 # merge)
201 # Conflicting tags are not a problem for buildman, since it does not use
202 # them. For example, Series-version is not useful for buildman. On the
203 # other hand conflicting tags will cause an error. So allow later tags
204 # to overwrite earlier ones by setting allow_overwrite=True
205 if options.branch:
206 if count == -1:
207 if has_range:
208 range_expr = options.branch
209 else:
210 range_expr = gitutil.GetRangeInBranch(options.git_dir,
211 options.branch)
212 upstream_commit = gitutil.GetUpstream(options.git_dir,
213 options.branch)
214 series = patchstream.GetMetaDataForList(upstream_commit,
215 options.git_dir, 1, series=None, allow_overwrite=True)
216
217 series = patchstream.GetMetaDataForList(range_expr,
218 options.git_dir, None, series, allow_overwrite=True)
219 else:
220 # Honour the count
221 series = patchstream.GetMetaDataForList(options.branch,
222 options.git_dir, count, series=None, allow_overwrite=True)
223 else:
224 series = None
225 if not options.dry_run:
226 options.verbose = True
227 if not options.summary:
228 options.show_errors = True
229
230 # By default we have one thread per CPU. But if there are not enough jobs
231 # we can have fewer threads and use a high '-j' value for make.
232 if not options.threads:
233 options.threads = min(multiprocessing.cpu_count(), len(selected))
234 if not options.jobs:
235 options.jobs = max(1, (multiprocessing.cpu_count() +
236 len(selected) - 1) / len(selected))
237
238 if not options.step:
239 options.step = len(series.commits) - 1
240
241 gnu_make = command.Output(os.path.join(options.git,
242 'scripts/show-gnu-make'), raise_on_error=False).rstrip()
243 if not gnu_make:
244 sys.exit('GNU Make not found')
245
246 # Create a new builder with the selected options.
247 output_dir = options.output_dir
248 if options.branch:
249 dirname = options.branch.replace('/', '_')
250 # As a special case allow the board directory to be placed in the
251 # output directory itself rather than any subdirectory.
252 if not options.no_subdirs:
253 output_dir = os.path.join(options.output_dir, dirname)
254 if (clean_dir and output_dir != options.output_dir and
255 os.path.exists(output_dir)):
256 shutil.rmtree(output_dir)
257 builder = Builder(toolchains, output_dir, options.git_dir,
258 options.threads, options.jobs, gnu_make=gnu_make, checkout=True,
259 show_unknown=options.show_unknown, step=options.step,
260 no_subdirs=options.no_subdirs, full_path=options.full_path,
261 verbose_build=options.verbose_build,
262 incremental=options.incremental,
263 per_board_out_dir=options.per_board_out_dir,
264 config_only=options.config_only,
265 squash_config_y=not options.preserve_config_y,
266 warnings_as_errors=options.warnings_as_errors)
267 builder.force_config_on_failure = not options.quick
268 if make_func:
269 builder.do_make = make_func
270
271 # For a dry run, just show our actions as a sanity check
272 if options.dry_run:
273 ShowActions(series, why_selected, selected, builder, options)
274 else:
275 builder.force_build = options.force_build
276 builder.force_build_failures = options.force_build_failures
277 builder.force_reconfig = options.force_reconfig
278 builder.in_tree = options.in_tree
279
280 # Work out which boards to build
281 board_selected = boards.GetSelectedDict()
282
283 if series:
284 commits = series.commits
285 # Number the commits for test purposes
286 for commit in range(len(commits)):
287 commits[commit].sequence = commit
288 else:
289 commits = None
290
291 Print(GetActionSummary(options.summary, commits, board_selected,
292 options))
293
294 # We can't show function sizes without board details at present
295 if options.show_bloat:
296 options.show_detail = True
297 builder.SetDisplayOptions(options.show_errors, options.show_sizes,
298 options.show_detail, options.show_bloat,
299 options.list_error_boards,
300 options.show_config)
301 if options.summary:
302 builder.ShowSummary(commits, board_selected)
303 else:
304 fail, warned = builder.BuildBoards(commits, board_selected,
305 options.keep_outputs, options.verbose)
306 if fail:
307 return 128
308 elif warned:
309 return 129
310 return 0