blob: ded43602505cfffe34f2c1fce91ece0359c6b689 [file] [log] [blame]
Tom Rini83d290c2018-05-06 17:58:06 -04001# SPDX-License-Identifier: GPL-2.0+
Simon Glassfc3fe1c2013-04-03 11:07:16 +00002# Copyright (c) 2013 The Chromium OS Authors.
3#
Simon Glassfc3fe1c2013-04-03 11:07:16 +00004
5import multiprocessing
6import os
Simon Glass883a3212014-09-05 19:00:18 -06007import shutil
Simon Glassfc3fe1c2013-04-03 11:07:16 +00008import sys
9
10import board
11import bsettings
12from builder import Builder
13import gitutil
14import patchstream
15import terminal
Simon Glassd4144e42014-09-05 19:00:13 -060016from terminal import Print
Simon Glassfc3fe1c2013-04-03 11:07:16 +000017import toolchain
Masahiro Yamada99796922014-07-22 11:19:09 +090018import command
Masahiro Yamada73f30b92014-07-30 14:08:22 +090019import subprocess
Simon Glassfc3fe1c2013-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 Glassfea58582014-08-09 15:32:59 -060025def GetActionSummary(is_summary, commits, selected, options):
Simon Glassfc3fe1c2013-04-03 11:07:16 +000026 """Return a string summarising the intended action.
27
28 Returns:
29 Summary string.
30 """
Simon Glassfea58582014-08-09 15:32:59 -060031 if commits:
32 count = len(commits)
Simon Glassc05aa032019-10-31 07:42:53 -060033 count = (count + options.step - 1) // options.step
Simon Glassfea58582014-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 Glassfc3fe1c2013-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 Glass06890362018-06-11 23:26:46 -060044def ShowActions(series, why_selected, boards_selected, builder, options,
45 board_warnings):
Simon Glassfc3fe1c2013-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 Glass8d7523c2017-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 Glassfc3fe1c2013-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 Glass06890362018-06-11 23:26:46 -060059 board_warnings: List of warnings obtained from board selected
Simon Glassfc3fe1c2013-04-03 11:07:16 +000060 """
61 col = terminal.Color()
Simon Glassc05aa032019-10-31 07:42:53 -060062 print('Dry run, so not doing much. But I would do this:')
63 print()
Simon Glassfea58582014-08-09 15:32:59 -060064 if series:
65 commits = series.commits
66 else:
67 commits = None
Simon Glassc05aa032019-10-31 07:42:53 -060068 print(GetActionSummary(False, commits, boards_selected,
69 options))
70 print('Build directory: %s' % builder.base_dir)
Simon Glassfea58582014-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 Glassc05aa032019-10-31 07:42:53 -060074 print(' ', col.Color(col.YELLOW, commit.hash[:8], bright=False), end=' ')
75 print(commit.subject)
76 print()
Simon Glassfc3fe1c2013-04-03 11:07:16 +000077 for arg in why_selected:
78 if arg != 'all':
Simon Glassc05aa032019-10-31 07:42:53 -060079 print(arg, ': %d boards' % len(why_selected[arg]))
Simon Glass8d7523c2017-01-23 05:38:56 -070080 if options.verbose:
Simon Glassc05aa032019-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 Glass06890362018-06-11 23:26:46 -060084 if board_warnings:
85 for warning in board_warnings:
Simon Glassc05aa032019-10-31 07:42:53 -060086 print(col.Color(col.YELLOW, warning))
Simon Glassfc3fe1c2013-04-03 11:07:16 +000087
Lothar Waßmann409fc022018-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 Packham58804b82019-01-18 20:40:29 +1300102 Print("Cannot use output directory '%s' since it is within the current directory '%s'" %
Lothar Waßmann409fc022018-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 Glass57cb9d52019-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 Glass883a3212014-09-05 19:00:18 -0600138def DoBuildman(options, args, toolchains=None, make_func=None, boards=None,
139 clean_dir=False):
Simon Glassfc3fe1c2013-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 Glassd4144e42014-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 Glass823e60b2014-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 Glassfc3fe1c2013-04-03 11:07:16 +0000153 """
Simon Glass883a3212014-09-05 19:00:18 -0600154 global builder
155
Simon Glass48ba5852014-09-05 19:00:11 -0600156 if options.full_help:
157 pager = os.getenv('PAGER')
158 if not pager:
159 pager = 'more'
Simon Glass2bdeade2016-03-06 19:45:34 -0700160 fname = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])),
161 'README')
Simon Glass48ba5852014-09-05 19:00:11 -0600162 command.Run(pager, fname)
163 return 0
164
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000165 gitutil.Setup()
Simon Glass713bea32016-07-27 20:33:02 -0600166 col = terminal.Color()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000167
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000168 options.git_dir = os.path.join(options.git, '.git')
169
Simon Glass7e92e462016-07-27 20:33:04 -0600170 no_toolchains = toolchains is None
171 if no_toolchains:
Simon Glass00beb242019-01-07 16:44:20 -0700172 toolchains = toolchain.Toolchains(options.override_toolchain)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000173
Simon Glass827e37b2014-12-01 17:34:06 -0700174 if options.fetch_arch:
175 if options.fetch_arch == 'list':
176 sorted_list = toolchains.ListArchs()
Simon Glassc05aa032019-10-31 07:42:53 -0600177 print(col.Color(col.BLUE, 'Available architectures: %s\n' %
178 ' '.join(sorted_list)))
Simon Glass827e37b2014-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 Glassc05aa032019-10-31 07:42:53 -0600184 print(col.Color(col.CYAN, '\nDownloading toolchains: %s' %
185 fetch_arch))
Simon Glass827e37b2014-12-01 17:34:06 -0700186 for arch in fetch_arch.split(','):
Simon Glassc05aa032019-10-31 07:42:53 -0600187 print()
Simon Glass827e37b2014-12-01 17:34:06 -0700188 ret = toolchains.FetchAndInstall(arch)
189 if ret:
190 return ret
191 return 0
192
Simon Glass7e92e462016-07-27 20:33:04 -0600193 if no_toolchains:
194 toolchains.GetSettings()
Simon Glass40232c92018-11-06 16:02:10 -0700195 toolchains.Scan(options.list_tool_chains and options.verbose)
Simon Glass7e92e462016-07-27 20:33:04 -0600196 if options.list_tool_chains:
197 toolchains.List()
Simon Glassc05aa032019-10-31 07:42:53 -0600198 print()
Simon Glass7e92e462016-07-27 20:33:04 -0600199 return 0
200
Simon Glass7c66ead2019-12-05 15:59:13 -0700201 # Work out what subset of the boards we are building
202 if not boards:
203 if not os.path.exists(options.output_dir):
204 os.makedirs(options.output_dir)
205 board_file = os.path.join(options.output_dir, 'boards.cfg')
206 genboardscfg = os.path.join(options.git, 'tools/genboardscfg.py')
207 status = subprocess.call([genboardscfg, '-q', '-o', board_file])
208 if status != 0:
209 sys.exit("Failed to generate boards.cfg")
210
211 boards = board.Boards()
212 boards.ReadBoards(board_file)
213
214 exclude = []
215 if options.exclude:
216 for arg in options.exclude:
217 exclude += arg.split(',')
218
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)
227 selected = boards.GetSelected()
228 if not len(selected):
229 sys.exit(col.Color(col.RED, 'No matching boards found'))
230
Simon Glass57cb9d52019-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 Glassfc3fe1c2013-04-03 11:07:16 +0000238 # 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.
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000241 count = options.count
Simon Glass5abab202014-12-01 17:33:57 -0700242 has_range = options.branch and '..' in options.branch
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000243 if count == -1:
244 if not options.branch:
Simon Glassfea58582014-08-09 15:32:59 -0600245 count = 1
246 else:
Simon Glass5abab202014-12-01 17:33:57 -0700247 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)
Simon Glassfea58582014-08-09 15:32:59 -0600253 if count is None:
Simon Glass2a9e2c62014-12-01 17:33:54 -0700254 sys.exit(col.Color(col.RED, msg))
Simon Glass5abab202014-12-01 17:33:57 -0700255 elif count == 0:
256 sys.exit(col.Color(col.RED, "Range '%s' has no commits" %
257 options.branch))
Simon Glass2a9e2c62014-12-01 17:33:54 -0700258 if msg:
Simon Glassc05aa032019-10-31 07:42:53 -0600259 print(col.Color(col.YELLOW, msg))
Simon Glassfea58582014-08-09 15:32:59 -0600260 count += 1 # Build upstream commit also
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000261
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)
Masahiro Yamada31e21412014-08-16 00:59:26 +0900265 sys.exit(col.Color(col.RED, str))
Simon Glassd829f122020-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 Glassfc3fe1c2013-04-03 11:07:16 +0000273
Simon Glassfc3fe1c2013-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 Glass950a2312014-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 Glassfea58582014-08-09 15:32:59 -0600283 if options.branch:
Simon Glass3b74ba52014-08-09 15:33:09 -0600284 if count == -1:
Simon Glass5abab202014-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 Glass3b74ba52014-08-09 15:33:09 -0600290 upstream_commit = gitutil.GetUpstream(options.git_dir,
291 options.branch)
292 series = patchstream.GetMetaDataForList(upstream_commit,
Simon Glass950a2312014-09-05 19:00:23 -0600293 options.git_dir, 1, series=None, allow_overwrite=True)
Simon Glassfea58582014-08-09 15:32:59 -0600294
Simon Glass3b74ba52014-08-09 15:33:09 -0600295 series = patchstream.GetMetaDataForList(range_expr,
Simon Glass950a2312014-09-05 19:00:23 -0600296 options.git_dir, None, series, allow_overwrite=True)
Simon Glass3b74ba52014-08-09 15:33:09 -0600297 else:
298 # Honour the count
299 series = patchstream.GetMetaDataForList(options.branch,
Simon Glass950a2312014-09-05 19:00:23 -0600300 options.git_dir, count, series=None, allow_overwrite=True)
Simon Glassfea58582014-08-09 15:32:59 -0600301 else:
302 series = None
Simon Glass8d7523c2017-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 Glassfc3fe1c2013-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 Glassc05aa032019-10-31 07:42:53 -0600314 len(selected) - 1) // len(selected))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000315
316 if not options.step:
317 options.step = len(series.commits) - 1
318
Masahiro Yamada99796922014-07-22 11:19:09 +0900319 gnu_make = command.Output(os.path.join(options.git,
Simon Glass785f1542016-07-25 18:59:00 -0600320 'scripts/show-gnu-make'), raise_on_error=False).rstrip()
Masahiro Yamada99796922014-07-22 11:19:09 +0900321 if not gnu_make:
Masahiro Yamada31e21412014-08-16 00:59:26 +0900322 sys.exit('GNU Make not found')
Masahiro Yamada99796922014-07-22 11:19:09 +0900323
Simon Glass05c96b12014-12-01 17:33:52 -0700324 # Create a new builder with the selected options.
325 output_dir = options.output_dir
Simon Glassfea58582014-08-09 15:32:59 -0600326 if options.branch:
Simon Glassf7582ce2014-09-05 19:00:22 -0600327 dirname = options.branch.replace('/', '_')
Simon Glass5971ab52014-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ßmann409fc022018-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 Glassfc3fe1c2013-04-03 11:07:16 +0000335 builder = Builder(toolchains, output_dir, options.git_dir,
Masahiro Yamada99796922014-07-22 11:19:09 +0900336 options.threads, options.jobs, gnu_make=gnu_make, checkout=True,
Simon Glass5971ab52014-12-01 17:33:55 -0700337 show_unknown=options.show_unknown, step=options.step,
Simon Glassd2ce6582014-12-01 17:34:07 -0700338 no_subdirs=options.no_subdirs, full_path=options.full_path,
Stephen Warrenf79f1e02016-04-11 10:48:44 -0600339 verbose_build=options.verbose_build,
340 incremental=options.incremental,
Simon Glassb50113f2016-11-13 14:25:51 -0700341 per_board_out_dir=options.per_board_out_dir,
Simon Glassb464f8e2016-11-13 14:25:53 -0700342 config_only=options.config_only,
Daniel Schwierzeck2371d1b2018-01-26 16:31:05 +0100343 squash_config_y=not options.preserve_config_y,
Simon Glassd829f122020-03-18 09:42:42 -0600344 warnings_as_errors=options.warnings_as_errors,
345 work_in_output=options.work_in_output)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000346 builder.force_config_on_failure = not options.quick
Simon Glassd4144e42014-09-05 19:00:13 -0600347 if make_func:
348 builder.do_make = make_func
Simon Glassfc3fe1c2013-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 Glass06890362018-06-11 23:26:46 -0600352 ShowActions(series, why_selected, selected, builder, options,
353 board_warnings)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000354 else:
355 builder.force_build = options.force_build
Simon Glass4266dc22014-07-13 12:22:31 -0600356 builder.force_build_failures = options.force_build_failures
Simon Glass97e91522014-07-14 17:51:02 -0600357 builder.force_reconfig = options.force_reconfig
Simon Glass189a4962014-07-14 17:51:03 -0600358 builder.in_tree = options.in_tree
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000359
360 # Work out which boards to build
361 board_selected = boards.GetSelectedDict()
362
Simon Glassfea58582014-08-09 15:32:59 -0600363 if series:
364 commits = series.commits
Simon Glass883a3212014-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 Glassfea58582014-08-09 15:32:59 -0600368 else:
369 commits = None
370
Simon Glassd4144e42014-09-05 19:00:13 -0600371 Print(GetActionSummary(options.summary, commits, board_selected,
372 options))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000373
Simon Glass7798e222014-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 Glassb2ea7ab2014-08-09 15:33:02 -0600377 builder.SetDisplayOptions(options.show_errors, options.show_sizes,
Simon Glassed966652014-08-28 09:43:43 -0600378 options.show_detail, options.show_bloat,
Simon Glass843312d2015-02-05 22:06:15 -0700379 options.list_error_boards,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000380 options.show_config,
381 options.show_environment)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000382 if options.summary:
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600383 builder.ShowSummary(commits, board_selected)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000384 else:
Simon Glass2c3deb92014-08-28 09:43:39 -0600385 fail, warned = builder.BuildBoards(commits, board_selected,
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600386 options.keep_outputs, options.verbose)
Simon Glass2c3deb92014-08-28 09:43:39 -0600387 if fail:
388 return 128
Simon Glass7beb43c2020-03-18 09:42:44 -0600389 elif warned and not options.ignore_warnings:
Simon Glass2c3deb92014-08-28 09:43:39 -0600390 return 129
391 return 0