blob: 5d80400f7ab380830c701b3f8082cacc39148759 [file] [log] [blame]
Tom Rini10e47792018-05-06 17:58:06 -04001# SPDX-License-Identifier: GPL-2.0+
Simon Glassc05694f2013-04-03 11:07:16 +00002# Copyright (c) 2013 The Chromium OS Authors.
3#
Simon Glassc05694f2013-04-03 11:07:16 +00004
5import multiprocessing
6import os
Simon Glassa10ebe12014-09-05 19:00:18 -06007import shutil
Simon Glassc05694f2013-04-03 11:07:16 +00008import sys
9
10import board
11import bsettings
12from builder import Builder
13import gitutil
14import patchstream
15import terminal
Simon Glassed098bb2014-09-05 19:00:13 -060016from terminal import Print
Simon Glassc05694f2013-04-03 11:07:16 +000017import toolchain
Masahiro Yamada1fe610d2014-07-22 11:19:09 +090018import command
Masahiro Yamadae9bc8d22014-07-30 14:08:22 +090019import subprocess
Simon Glassc05694f2013-04-03 11:07:16 +000020
21def GetPlural(count):
22 """Returns a plural 's' if count is not 1"""
23 return 's' if count != 1 else ''
24
Simon Glassd326ad72014-08-09 15:32:59 -060025def GetActionSummary(is_summary, commits, selected, options):
Simon Glassc05694f2013-04-03 11:07:16 +000026 """Return a string summarising the intended action.
27
28 Returns:
29 Summary string.
30 """
Simon Glassd326ad72014-08-09 15:32:59 -060031 if commits:
32 count = len(commits)
Simon Glassc78ed662019-10-31 07:42:53 -060033 count = (count + options.step - 1) // options.step
Simon Glassd326ad72014-08-09 15:32:59 -060034 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,
Simon Glassc05694f2013-04-03 11:07:16 +000039 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
Simon Glassd9eb9f02018-06-11 23:26:46 -060044def ShowActions(series, why_selected, boards_selected, builder, options,
45 board_warnings):
Simon Glassc05694f2013-04-03 11:07:16 +000046 """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
Simon Glass6af145f2017-01-23 05:38:56 -070051 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
Simon Glassc05694f2013-04-03 11:07:16 +000054 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
Simon Glassd9eb9f02018-06-11 23:26:46 -060059 board_warnings: List of warnings obtained from board selected
Simon Glassc05694f2013-04-03 11:07:16 +000060 """
61 col = terminal.Color()
Simon Glassc78ed662019-10-31 07:42:53 -060062 print('Dry run, so not doing much. But I would do this:')
63 print()
Simon Glassd326ad72014-08-09 15:32:59 -060064 if series:
65 commits = series.commits
66 else:
67 commits = None
Simon Glassc78ed662019-10-31 07:42:53 -060068 print(GetActionSummary(False, commits, boards_selected,
69 options))
70 print('Build directory: %s' % builder.base_dir)
Simon Glassd326ad72014-08-09 15:32:59 -060071 if commits:
72 for upto in range(0, len(series.commits), options.step):
73 commit = series.commits[upto]
Simon Glassc78ed662019-10-31 07:42:53 -060074 print(' ', col.Color(col.YELLOW, commit.hash[:8], bright=False), end=' ')
75 print(commit.subject)
76 print()
Simon Glassc05694f2013-04-03 11:07:16 +000077 for arg in why_selected:
78 if arg != 'all':
Simon Glassc78ed662019-10-31 07:42:53 -060079 print(arg, ': %d boards' % len(why_selected[arg]))
Simon Glass6af145f2017-01-23 05:38:56 -070080 if options.verbose:
Simon Glassc78ed662019-10-31 07:42:53 -060081 print(' %s' % ' '.join(why_selected[arg]))
82 print(('Total boards to build for each commit: %d\n' %
83 len(why_selected['all'])))
Simon Glassd9eb9f02018-06-11 23:26:46 -060084 if board_warnings:
85 for warning in board_warnings:
Simon Glassc78ed662019-10-31 07:42:53 -060086 print(col.Color(col.YELLOW, warning))
Simon Glassc05694f2013-04-03 11:07:16 +000087
Lothar Waßmannce6df922018-04-08 05:14:11 -060088def 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:
Chris Packhamdf4e8052019-01-18 20:40:29 +1300102 Print("Cannot use output directory '%s' since it is within the current directory '%s'" %
Lothar Waßmannce6df922018-04-08 05:14:11 -0600103 (path, cwd_path))
104 sys.exit(1)
105 parent = os.path.dirname(path)
106 if parent == path:
107 break
108 path = parent
109
Simon Glass48ac42e2019-12-05 15:59:14 -0700110def 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
Simon Glassa10ebe12014-09-05 19:00:18 -0600138def DoBuildman(options, args, toolchains=None, make_func=None, boards=None,
139 clean_dir=False):
Simon Glassc05694f2013-04-03 11:07:16 +0000140 """The main control code for buildman
141
142 Args:
143 options: Command line options object
144 args: Command line arguments (list of strings)
Simon Glassed098bb2014-09-05 19:00:13 -0600145 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.
Simon Glasscbd36582014-09-05 19:00:16 -0600151 board: Boards() object to use, containing a list of available
152 boards. If this is None it will be created and scanned.
Simon Glassc05694f2013-04-03 11:07:16 +0000153 """
Simon Glassa10ebe12014-09-05 19:00:18 -0600154 global builder
155
Simon Glassca9b06e2014-09-05 19:00:11 -0600156 if options.full_help:
157 pager = os.getenv('PAGER')
158 if not pager:
159 pager = 'more'
Simon Glassc29101f2016-03-06 19:45:34 -0700160 fname = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])),
161 'README')
Simon Glassca9b06e2014-09-05 19:00:11 -0600162 command.Run(pager, fname)
163 return 0
164
Simon Glassc05694f2013-04-03 11:07:16 +0000165 gitutil.Setup()
Simon Glass9f1ba0f2016-07-27 20:33:02 -0600166 col = terminal.Color()
Simon Glassc05694f2013-04-03 11:07:16 +0000167
Simon Glassc05694f2013-04-03 11:07:16 +0000168 options.git_dir = os.path.join(options.git, '.git')
169
Simon Glassa3d9b4f2016-07-27 20:33:04 -0600170 no_toolchains = toolchains is None
171 if no_toolchains:
Simon Glassf77ca5b2019-01-07 16:44:20 -0700172 toolchains = toolchain.Toolchains(options.override_toolchain)
Simon Glassc05694f2013-04-03 11:07:16 +0000173
Simon Glass7e803e12014-12-01 17:34:06 -0700174 if options.fetch_arch:
175 if options.fetch_arch == 'list':
176 sorted_list = toolchains.ListArchs()
Simon Glassc78ed662019-10-31 07:42:53 -0600177 print(col.Color(col.BLUE, 'Available architectures: %s\n' %
178 ' '.join(sorted_list)))
Simon Glass7e803e12014-12-01 17:34:06 -0700179 return 0
180 else:
181 fetch_arch = options.fetch_arch
182 if fetch_arch == 'all':
183 fetch_arch = ','.join(toolchains.ListArchs())
Simon Glassc78ed662019-10-31 07:42:53 -0600184 print(col.Color(col.CYAN, '\nDownloading toolchains: %s' %
185 fetch_arch))
Simon Glass7e803e12014-12-01 17:34:06 -0700186 for arch in fetch_arch.split(','):
Simon Glassc78ed662019-10-31 07:42:53 -0600187 print()
Simon Glass7e803e12014-12-01 17:34:06 -0700188 ret = toolchains.FetchAndInstall(arch)
189 if ret:
190 return ret
191 return 0
192
Simon Glassa3d9b4f2016-07-27 20:33:04 -0600193 if no_toolchains:
194 toolchains.GetSettings()
Simon Glass74579fc2018-11-06 16:02:10 -0700195 toolchains.Scan(options.list_tool_chains and options.verbose)
Simon Glassa3d9b4f2016-07-27 20:33:04 -0600196 if options.list_tool_chains:
197 toolchains.List()
Simon Glassc78ed662019-10-31 07:42:53 -0600198 print()
Simon Glassa3d9b4f2016-07-27 20:33:04 -0600199 return 0
200
Simon Glassc05694f2013-04-03 11:07:16 +0000201 # Work out what subset of the boards we are building
Simon Glasscbd36582014-09-05 19:00:16 -0600202 if not boards:
Tom Rini6ef6b3f2019-11-19 15:14:33 -0500203 if not os.path.exists(options.output_dir):
204 os.makedirs(options.output_dir)
Bin Meng0733e202019-10-28 07:24:59 -0700205 board_file = os.path.join(options.output_dir, 'boards.cfg')
206 genboardscfg = os.path.join(options.git, 'tools/genboardscfg.py')
Simon Glassaa26d472019-12-05 15:59:12 -0700207 status = subprocess.call([genboardscfg, '-q', '-o', board_file])
Simon Glasscbd36582014-09-05 19:00:16 -0600208 if status != 0:
Bin Meng0733e202019-10-28 07:24:59 -0700209 sys.exit("Failed to generate boards.cfg")
Masahiro Yamadae9bc8d22014-07-30 14:08:22 +0900210
Simon Glasscbd36582014-09-05 19:00:16 -0600211 boards = board.Boards()
Bin Meng0733e202019-10-28 07:24:59 -0700212 boards.ReadBoards(board_file)
Simon Glass924c73a2014-08-28 09:43:41 -0600213
214 exclude = []
215 if options.exclude:
216 for arg in options.exclude:
217 exclude += arg.split(',')
218
Simon Glassd9eb9f02018-06-11 23:26:46 -0600219 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)
Simon Glassc05694f2013-04-03 11:07:16 +0000227 selected = boards.GetSelected()
228 if not len(selected):
Masahiro Yamada880828d2014-08-16 00:59:26 +0900229 sys.exit(col.Color(col.RED, 'No matching boards found'))
Simon Glassc05694f2013-04-03 11:07:16 +0000230
Simon Glass48ac42e2019-12-05 15:59:14 -0700231 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
Simon Glass9b550912019-12-05 15:59:13 -0700238 # 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))
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600266 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'))
Simon Glass9b550912019-12-05 15:59:13 -0700273
Simon Glassc05694f2013-04-03 11:07:16 +0000274 # 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)
Simon Glass359b55a62014-09-05 19:00:23 -0600279 # 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
Simon Glassd326ad72014-08-09 15:32:59 -0600283 if options.branch:
Simon Glass16a52882014-08-09 15:33:09 -0600284 if count == -1:
Simon Glass5eeef462014-12-01 17:33:57 -0700285 if has_range:
286 range_expr = options.branch
287 else:
288 range_expr = gitutil.GetRangeInBranch(options.git_dir,
289 options.branch)
Simon Glass16a52882014-08-09 15:33:09 -0600290 upstream_commit = gitutil.GetUpstream(options.git_dir,
291 options.branch)
292 series = patchstream.GetMetaDataForList(upstream_commit,
Simon Glass359b55a62014-09-05 19:00:23 -0600293 options.git_dir, 1, series=None, allow_overwrite=True)
Simon Glassd326ad72014-08-09 15:32:59 -0600294
Simon Glass16a52882014-08-09 15:33:09 -0600295 series = patchstream.GetMetaDataForList(range_expr,
Simon Glass359b55a62014-09-05 19:00:23 -0600296 options.git_dir, None, series, allow_overwrite=True)
Simon Glass16a52882014-08-09 15:33:09 -0600297 else:
298 # Honour the count
299 series = patchstream.GetMetaDataForList(options.branch,
Simon Glass359b55a62014-09-05 19:00:23 -0600300 options.git_dir, count, series=None, allow_overwrite=True)
Simon Glassd326ad72014-08-09 15:32:59 -0600301 else:
302 series = None
Simon Glass6af145f2017-01-23 05:38:56 -0700303 if not options.dry_run:
304 options.verbose = True
305 if not options.summary:
306 options.show_errors = True
Simon Glassc05694f2013-04-03 11:07:16 +0000307
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() +
Simon Glassc78ed662019-10-31 07:42:53 -0600314 len(selected) - 1) // len(selected))
Simon Glassc05694f2013-04-03 11:07:16 +0000315
316 if not options.step:
317 options.step = len(series.commits) - 1
318
Masahiro Yamada1fe610d2014-07-22 11:19:09 +0900319 gnu_make = command.Output(os.path.join(options.git,
Simon Glassc55e0562016-07-25 18:59:00 -0600320 'scripts/show-gnu-make'), raise_on_error=False).rstrip()
Masahiro Yamada1fe610d2014-07-22 11:19:09 +0900321 if not gnu_make:
Masahiro Yamada880828d2014-08-16 00:59:26 +0900322 sys.exit('GNU Make not found')
Masahiro Yamada1fe610d2014-07-22 11:19:09 +0900323
Simon Glassdbc01c72014-12-01 17:33:52 -0700324 # Create a new builder with the selected options.
325 output_dir = options.output_dir
Simon Glassd326ad72014-08-09 15:32:59 -0600326 if options.branch:
Simon Glass4aeceb92014-09-05 19:00:22 -0600327 dirname = options.branch.replace('/', '_')
Simon Glasse87bde12014-12-01 17:33:55 -0700328 # 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)
Lothar Waßmannce6df922018-04-08 05:14:11 -0600332 if clean_dir and os.path.exists(output_dir):
333 shutil.rmtree(output_dir)
334 CheckOutputDir(output_dir)
Simon Glassc05694f2013-04-03 11:07:16 +0000335 builder = Builder(toolchains, output_dir, options.git_dir,
Masahiro Yamada1fe610d2014-07-22 11:19:09 +0900336 options.threads, options.jobs, gnu_make=gnu_make, checkout=True,
Simon Glasse87bde12014-12-01 17:33:55 -0700337 show_unknown=options.show_unknown, step=options.step,
Simon Glass655b6102014-12-01 17:34:07 -0700338 no_subdirs=options.no_subdirs, full_path=options.full_path,
Stephen Warren97c96902016-04-11 10:48:44 -0600339 verbose_build=options.verbose_build,
340 incremental=options.incremental,
Simon Glass739e8512016-11-13 14:25:51 -0700341 per_board_out_dir=options.per_board_out_dir,
Simon Glasscde5c302016-11-13 14:25:53 -0700342 config_only=options.config_only,
Daniel Schwierzeck20e2ea92018-01-26 16:31:05 +0100343 squash_config_y=not options.preserve_config_y,
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600344 warnings_as_errors=options.warnings_as_errors,
345 work_in_output=options.work_in_output)
Simon Glassc05694f2013-04-03 11:07:16 +0000346 builder.force_config_on_failure = not options.quick
Simon Glassed098bb2014-09-05 19:00:13 -0600347 if make_func:
348 builder.do_make = make_func
Simon Glassc05694f2013-04-03 11:07:16 +0000349
350 # For a dry run, just show our actions as a sanity check
351 if options.dry_run:
Simon Glassd9eb9f02018-06-11 23:26:46 -0600352 ShowActions(series, why_selected, selected, builder, options,
353 board_warnings)
Simon Glassc05694f2013-04-03 11:07:16 +0000354 else:
355 builder.force_build = options.force_build
Simon Glass7041c392014-07-13 12:22:31 -0600356 builder.force_build_failures = options.force_build_failures
Simon Glassf3018b7a2014-07-14 17:51:02 -0600357 builder.force_reconfig = options.force_reconfig
Simon Glass38df2e22014-07-14 17:51:03 -0600358 builder.in_tree = options.in_tree
Simon Glassc05694f2013-04-03 11:07:16 +0000359
360 # Work out which boards to build
361 board_selected = boards.GetSelectedDict()
362
Simon Glassd326ad72014-08-09 15:32:59 -0600363 if series:
364 commits = series.commits
Simon Glassa10ebe12014-09-05 19:00:18 -0600365 # Number the commits for test purposes
366 for commit in range(len(commits)):
367 commits[commit].sequence = commit
Simon Glassd326ad72014-08-09 15:32:59 -0600368 else:
369 commits = None
370
Simon Glassed098bb2014-09-05 19:00:13 -0600371 Print(GetActionSummary(options.summary, commits, board_selected,
372 options))
Simon Glassc05694f2013-04-03 11:07:16 +0000373
Simon Glass232d8502014-09-14 20:23:16 -0600374 # We can't show function sizes without board details at present
375 if options.show_bloat:
376 options.show_detail = True
Simon Glasseb48bbc2014-08-09 15:33:02 -0600377 builder.SetDisplayOptions(options.show_errors, options.show_sizes,
Simon Glass3394c9f2014-08-28 09:43:43 -0600378 options.show_detail, options.show_bloat,
Simon Glassdb17fb82015-02-05 22:06:15 -0700379 options.list_error_boards,
Alex Kiernan4059e302018-05-31 04:48:34 +0000380 options.show_config,
381 options.show_environment)
Simon Glassc05694f2013-04-03 11:07:16 +0000382 if options.summary:
Simon Glasseb48bbc2014-08-09 15:33:02 -0600383 builder.ShowSummary(commits, board_selected)
Simon Glassc05694f2013-04-03 11:07:16 +0000384 else:
Simon Glassc2f91072014-08-28 09:43:39 -0600385 fail, warned = builder.BuildBoards(commits, board_selected,
Simon Glass78e418e2014-08-09 15:33:03 -0600386 options.keep_outputs, options.verbose)
Simon Glassc2f91072014-08-28 09:43:39 -0600387 if fail:
388 return 128
389 elif warned:
390 return 129
391 return 0