]> Git Repo - J-u-boot.git/blame - tools/buildman/control.py
buildman: Allow ignoring warnings in the return code
[J-u-boot.git] / tools / buildman / control.py
CommitLineData
83d290c5 1# SPDX-License-Identifier: GPL-2.0+
fc3fe1c2
SG
2# Copyright (c) 2013 The Chromium OS Authors.
3#
fc3fe1c2
SG
4
5import multiprocessing
6import os
883a321a 7import shutil
fc3fe1c2
SG
8import sys
9
10import board
11import bsettings
12from builder import Builder
13import gitutil
14import patchstream
15import terminal
d4144e45 16from terminal import Print
fc3fe1c2 17import toolchain
99796923 18import command
73f30b9b 19import subprocess
fc3fe1c2
SG
20
21def GetPlural(count):
22 """Returns a plural 's' if count is not 1"""
23 return 's' if count != 1 else ''
24
fea5858e 25def GetActionSummary(is_summary, commits, selected, options):
fc3fe1c2
SG
26 """Return a string summarising the intended action.
27
28 Returns:
29 Summary string.
30 """
fea5858e
SG
31 if commits:
32 count = len(commits)
c05aa036 33 count = (count + options.step - 1) // options.step
fea5858e
SG
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,
fc3fe1c2
SG
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
0689036a
SG
44def ShowActions(series, why_selected, boards_selected, builder, options,
45 board_warnings):
fc3fe1c2
SG
46 """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
8d7523c5
SG
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
fc3fe1c2
SG
54 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
0689036a 59 board_warnings: List of warnings obtained from board selected
fc3fe1c2
SG
60 """
61 col = terminal.Color()
c05aa036
SG
62 print('Dry run, so not doing much. But I would do this:')
63 print()
fea5858e
SG
64 if series:
65 commits = series.commits
66 else:
67 commits = None
c05aa036
SG
68 print(GetActionSummary(False, commits, boards_selected,
69 options))
70 print('Build directory: %s' % builder.base_dir)
fea5858e
SG
71 if commits:
72 for upto in range(0, len(series.commits), options.step):
73 commit = series.commits[upto]
c05aa036
SG
74 print(' ', col.Color(col.YELLOW, commit.hash[:8], bright=False), end=' ')
75 print(commit.subject)
76 print()
fc3fe1c2
SG
77 for arg in why_selected:
78 if arg != 'all':
c05aa036 79 print(arg, ': %d boards' % len(why_selected[arg]))
8d7523c5 80 if options.verbose:
c05aa036
SG
81 print(' %s' % ' '.join(why_selected[arg]))
82 print(('Total boards to build for each commit: %d\n' %
83 len(why_selected['all'])))
0689036a
SG
84 if board_warnings:
85 for warning in board_warnings:
c05aa036 86 print(col.Color(col.YELLOW, warning))
fc3fe1c2 87
409fc029
LW
88def CheckOutputDir(output_dir):
89 """Make sure that the output directory is not within the current directory
90
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.
94
95 Args:
96 output_dir: Output directory path to check
97 """
98 path = os.path.realpath(output_dir)
99 cwd_path = os.path.realpath('.')
100 while True:
101 if os.path.realpath(path) == cwd_path:
58804b8c 102 Print("Cannot use output directory '%s' since it is within the current directory '%s'" %
409fc029
LW
103 (path, cwd_path))
104 sys.exit(1)
105 parent = os.path.dirname(path)
106 if parent == path:
107 break
108 path = parent
109
57cb9d52
SG
110def ShowToolchainInfo(boards, toolchains, print_arch, print_prefix):
111 """Show information about a the tool chain used by one or more boards
112
113 The function checks that all boards use the same toolchain.
114
115 Args:
116 boards: Boards object containing selected boards
117 toolchains: Toolchains object containing available toolchains
118 print_arch: True to print ARCH value
119 print_prefix: True to print CROSS_COMPILE value
120
121 Return:
122 None on success, string error message otherwise
123 """
124 boards = boards.GetSelectedDict()
125 tc_set = set()
126 for brd in boards.values():
127 tc_set.add(toolchains.Select(brd.arch))
128 if len(tc_set) != 1:
129 return 'Supplied boards must share one toolchain'
130 return False
131 tc = tc_set.pop()
132 if print_arch:
133 print(tc.GetEnvArgs(toolchain.VAR_ARCH))
134 if print_prefix:
135 print(tc.GetEnvArgs(toolchain.VAR_CROSS_COMPILE))
136 return None
137
883a321a
SG
138def DoBuildman(options, args, toolchains=None, make_func=None, boards=None,
139 clean_dir=False):
fc3fe1c2
SG
140 """The main control code for buildman
141
142 Args:
143 options: Command line options object
144 args: Command line arguments (list of strings)
d4144e45
SG
145 toolchains: Toolchains to use - this should be a Toolchains()
146 object. If None, then it will be created and scanned
147 make_func: Make function to use for the builder. This is called
148 to execute 'make'. If this is None, the normal function
149 will be used, which calls the 'make' tool with suitable
150 arguments. This setting is useful for tests.
823e60b6
SG
151 board: Boards() object to use, containing a list of available
152 boards. If this is None it will be created and scanned.
fc3fe1c2 153 """
883a321a
SG
154 global builder
155
48ba5856
SG
156 if options.full_help:
157 pager = os.getenv('PAGER')
158 if not pager:
159 pager = 'more'
2bdeade0
SG
160 fname = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])),
161 'README')
48ba5856
SG
162 command.Run(pager, fname)
163 return 0
164
fc3fe1c2 165 gitutil.Setup()
713bea38 166 col = terminal.Color()
fc3fe1c2 167
fc3fe1c2
SG
168 options.git_dir = os.path.join(options.git, '.git')
169
7e92e46e
SG
170 no_toolchains = toolchains is None
171 if no_toolchains:
00beb248 172 toolchains = toolchain.Toolchains(options.override_toolchain)
fc3fe1c2 173
827e37b5
SG
174 if options.fetch_arch:
175 if options.fetch_arch == 'list':
176 sorted_list = toolchains.ListArchs()
c05aa036
SG
177 print(col.Color(col.BLUE, 'Available architectures: %s\n' %
178 ' '.join(sorted_list)))
827e37b5
SG
179 return 0
180 else:
181 fetch_arch = options.fetch_arch
182 if fetch_arch == 'all':
183 fetch_arch = ','.join(toolchains.ListArchs())
c05aa036
SG
184 print(col.Color(col.CYAN, '\nDownloading toolchains: %s' %
185 fetch_arch))
827e37b5 186 for arch in fetch_arch.split(','):
c05aa036 187 print()
827e37b5
SG
188 ret = toolchains.FetchAndInstall(arch)
189 if ret:
190 return ret
191 return 0
192
7e92e46e
SG
193 if no_toolchains:
194 toolchains.GetSettings()
40232c91 195 toolchains.Scan(options.list_tool_chains and options.verbose)
7e92e46e
SG
196 if options.list_tool_chains:
197 toolchains.List()
c05aa036 198 print()
7e92e46e
SG
199 return 0
200
fc3fe1c2 201 # Work out what subset of the boards we are building
823e60b6 202 if not boards:
70f68139
TR
203 if not os.path.exists(options.output_dir):
204 os.makedirs(options.output_dir)
bc750bca
BM
205 board_file = os.path.join(options.output_dir, 'boards.cfg')
206 genboardscfg = os.path.join(options.git, 'tools/genboardscfg.py')
6a3fc91e 207 status = subprocess.call([genboardscfg, '-q', '-o', board_file])
823e60b6 208 if status != 0:
bc750bca 209 sys.exit("Failed to generate boards.cfg")
823e60b6
SG
210
211 boards = board.Boards()
bc750bca 212 boards.ReadBoards(board_file)
3cf4ae6f
SG
213
214 exclude = []
215 if options.exclude:
216 for arg in options.exclude:
217 exclude += arg.split(',')
218
0689036a
SG
219 if options.boards:
220 requested_boards = []
221 for b in options.boards:
222 requested_boards += b.split(',')
223 else:
224 requested_boards = None
225 why_selected, board_warnings = boards.SelectBoards(args, exclude,
226 requested_boards)
fc3fe1c2
SG
227 selected = boards.GetSelected()
228 if not len(selected):
31e2141d 229 sys.exit(col.Color(col.RED, 'No matching boards found'))
fc3fe1c2 230
57cb9d52
SG
231 if options.print_arch or options.print_prefix:
232 err = ShowToolchainInfo(boards, toolchains, options.print_arch,
233 options.print_prefix)
234 if err:
235 sys.exit(col.Color(col.RED, err))
236 return 0
237
7c66ead4
SG
238 # Work out how many commits to build. We want to build everything on the
239 # branch. We also build the upstream commit as a control so we can see
240 # problems introduced by the first commit on the branch.
241 count = options.count
242 has_range = options.branch and '..' in options.branch
243 if count == -1:
244 if not options.branch:
245 count = 1
246 else:
247 if has_range:
248 count, msg = gitutil.CountCommitsInRange(options.git_dir,
249 options.branch)
250 else:
251 count, msg = gitutil.CountCommitsInBranch(options.git_dir,
252 options.branch)
253 if count is None:
254 sys.exit(col.Color(col.RED, msg))
255 elif count == 0:
256 sys.exit(col.Color(col.RED, "Range '%s' has no commits" %
257 options.branch))
258 if msg:
259 print(col.Color(col.YELLOW, msg))
260 count += 1 # Build upstream commit also
261
262 if not count:
263 str = ("No commits found to process in branch '%s': "
264 "set branch's upstream or use -c flag" % options.branch)
265 sys.exit(col.Color(col.RED, str))
d829f121
SG
266 if options.work_in_output:
267 if len(selected) != 1:
268 sys.exit(col.Color(col.RED,
269 '-w can only be used with a single board'))
270 if count != 1:
271 sys.exit(col.Color(col.RED,
272 '-w can only be used with a single commit'))
7c66ead4 273
fc3fe1c2
SG
274 # Read the metadata from the commits. First look at the upstream commit,
275 # then the ones in the branch. We would like to do something like
276 # upstream/master~..branch but that isn't possible if upstream/master is
277 # a merge commit (it will list all the commits that form part of the
278 # merge)
950a2313
SG
279 # Conflicting tags are not a problem for buildman, since it does not use
280 # them. For example, Series-version is not useful for buildman. On the
281 # other hand conflicting tags will cause an error. So allow later tags
282 # to overwrite earlier ones by setting allow_overwrite=True
fea5858e 283 if options.branch:
3b74ba5f 284 if count == -1:
5abab20d
SG
285 if has_range:
286 range_expr = options.branch
287 else:
288 range_expr = gitutil.GetRangeInBranch(options.git_dir,
289 options.branch)
3b74ba5f
SG
290 upstream_commit = gitutil.GetUpstream(options.git_dir,
291 options.branch)
292 series = patchstream.GetMetaDataForList(upstream_commit,
950a2313 293 options.git_dir, 1, series=None, allow_overwrite=True)
3b74ba5f 294
3b74ba5f 295 series = patchstream.GetMetaDataForList(range_expr,
950a2313 296 options.git_dir, None, series, allow_overwrite=True)
3b74ba5f
SG
297 else:
298 # Honour the count
299 series = patchstream.GetMetaDataForList(options.branch,
950a2313 300 options.git_dir, count, series=None, allow_overwrite=True)
fea5858e
SG
301 else:
302 series = None
8d7523c5
SG
303 if not options.dry_run:
304 options.verbose = True
305 if not options.summary:
306 options.show_errors = True
fc3fe1c2
SG
307
308 # By default we have one thread per CPU. But if there are not enough jobs
309 # we can have fewer threads and use a high '-j' value for make.
310 if not options.threads:
311 options.threads = min(multiprocessing.cpu_count(), len(selected))
312 if not options.jobs:
313 options.jobs = max(1, (multiprocessing.cpu_count() +
c05aa036 314 len(selected) - 1) // len(selected))
fc3fe1c2
SG
315
316 if not options.step:
317 options.step = len(series.commits) - 1
318
99796923 319 gnu_make = command.Output(os.path.join(options.git,
785f1548 320 'scripts/show-gnu-make'), raise_on_error=False).rstrip()
99796923 321 if not gnu_make:
31e2141d 322 sys.exit('GNU Make not found')
99796923 323
05c96b18
SG
324 # Create a new builder with the selected options.
325 output_dir = options.output_dir
fea5858e 326 if options.branch:
f7582ce8 327 dirname = options.branch.replace('/', '_')
5971ab5c
SG
328 # As a special case allow the board directory to be placed in the
329 # output directory itself rather than any subdirectory.
330 if not options.no_subdirs:
331 output_dir = os.path.join(options.output_dir, dirname)
409fc029
LW
332 if clean_dir and os.path.exists(output_dir):
333 shutil.rmtree(output_dir)
334 CheckOutputDir(output_dir)
fc3fe1c2 335 builder = Builder(toolchains, output_dir, options.git_dir,
99796923 336 options.threads, options.jobs, gnu_make=gnu_make, checkout=True,
5971ab5c 337 show_unknown=options.show_unknown, step=options.step,
d2ce658d 338 no_subdirs=options.no_subdirs, full_path=options.full_path,
f79f1e0c
SW
339 verbose_build=options.verbose_build,
340 incremental=options.incremental,
b50113f3 341 per_board_out_dir=options.per_board_out_dir,
b464f8e7 342 config_only=options.config_only,
2371d1bc 343 squash_config_y=not options.preserve_config_y,
d829f121
SG
344 warnings_as_errors=options.warnings_as_errors,
345 work_in_output=options.work_in_output)
fc3fe1c2 346 builder.force_config_on_failure = not options.quick
d4144e45
SG
347 if make_func:
348 builder.do_make = make_func
fc3fe1c2
SG
349
350 # For a dry run, just show our actions as a sanity check
351 if options.dry_run:
0689036a
SG
352 ShowActions(series, why_selected, selected, builder, options,
353 board_warnings)
fc3fe1c2
SG
354 else:
355 builder.force_build = options.force_build
4266dc28 356 builder.force_build_failures = options.force_build_failures
97e91526 357 builder.force_reconfig = options.force_reconfig
189a4968 358 builder.in_tree = options.in_tree
fc3fe1c2
SG
359
360 # Work out which boards to build
361 board_selected = boards.GetSelectedDict()
362
fea5858e
SG
363 if series:
364 commits = series.commits
883a321a
SG
365 # Number the commits for test purposes
366 for commit in range(len(commits)):
367 commits[commit].sequence = commit
fea5858e
SG
368 else:
369 commits = None
370
d4144e45
SG
371 Print(GetActionSummary(options.summary, commits, board_selected,
372 options))
fc3fe1c2 373
7798e228
SG
374 # We can't show function sizes without board details at present
375 if options.show_bloat:
376 options.show_detail = True
b2ea7ab2 377 builder.SetDisplayOptions(options.show_errors, options.show_sizes,
ed966657 378 options.show_detail, options.show_bloat,
843312dc 379 options.list_error_boards,
48ae4124
AK
380 options.show_config,
381 options.show_environment)
fc3fe1c2 382 if options.summary:
b2ea7ab2 383 builder.ShowSummary(commits, board_selected)
fc3fe1c2 384 else:
2c3deb97 385 fail, warned = builder.BuildBoards(commits, board_selected,
e5a0e5d8 386 options.keep_outputs, options.verbose)
2c3deb97
SG
387 if fail:
388 return 128
7beb43c9 389 elif warned and not options.ignore_warnings:
2c3deb97
SG
390 return 129
391 return 0
This page took 0.378526 seconds and 4 git commands to generate.