blob: a98d1b4c06f3805d484d558b2517675a37721242 [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 Glassf0d9c102020-04-17 18:09:02 -06008import subprocess
Simon Glassc05694f2013-04-03 11:07:16 +00009import sys
10
Simon Glassf0d9c102020-04-17 18:09:02 -060011from buildman import board
12from buildman import bsettings
13from buildman import toolchain
14from buildman.builder import Builder
Simon Glassa997ea52020-04-17 18:09:04 -060015from patman import command
16from patman import gitutil
17from patman import patchstream
18from patman import terminal
19from patman.terminal import Print
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
Simon Glass2df44be2020-03-18 09:42:47 -060088def ShowToolchainPrefix(boards, toolchains):
Simon Glass48ac42e2019-12-05 15:59:14 -070089 """Show information about a the tool chain used by one or more boards
90
Simon Glass2df44be2020-03-18 09:42:47 -060091 The function checks that all boards use the same toolchain, then prints
92 the correct value for CROSS_COMPILE.
Simon Glass48ac42e2019-12-05 15:59:14 -070093
94 Args:
95 boards: Boards object containing selected boards
96 toolchains: Toolchains object containing available toolchains
Simon Glass48ac42e2019-12-05 15:59:14 -070097
98 Return:
99 None on success, string error message otherwise
100 """
101 boards = boards.GetSelectedDict()
102 tc_set = set()
103 for brd in boards.values():
104 tc_set.add(toolchains.Select(brd.arch))
105 if len(tc_set) != 1:
106 return 'Supplied boards must share one toolchain'
107 return False
108 tc = tc_set.pop()
Simon Glass2df44be2020-03-18 09:42:47 -0600109 print(tc.GetEnvArgs(toolchain.VAR_CROSS_COMPILE))
Simon Glass48ac42e2019-12-05 15:59:14 -0700110 return None
111
Simon Glassa10ebe12014-09-05 19:00:18 -0600112def DoBuildman(options, args, toolchains=None, make_func=None, boards=None,
Simon Glass9bf9a722021-04-11 16:27:27 +1200113 clean_dir=False, test_thread_exceptions=False):
Simon Glassc05694f2013-04-03 11:07:16 +0000114 """The main control code for buildman
115
116 Args:
117 options: Command line options object
118 args: Command line arguments (list of strings)
Simon Glassed098bb2014-09-05 19:00:13 -0600119 toolchains: Toolchains to use - this should be a Toolchains()
120 object. If None, then it will be created and scanned
121 make_func: Make function to use for the builder. This is called
122 to execute 'make'. If this is None, the normal function
123 will be used, which calls the 'make' tool with suitable
124 arguments. This setting is useful for tests.
Simon Glasscbd36582014-09-05 19:00:16 -0600125 board: Boards() object to use, containing a list of available
126 boards. If this is None it will be created and scanned.
Simon Glassa29b3ea2021-04-11 16:27:25 +1200127 clean_dir: Used for tests only, indicates that the existing output_dir
128 should be removed before starting the build
Simon Glass9bf9a722021-04-11 16:27:27 +1200129 test_thread_exceptions: Uses for tests only, True to make the threads
130 raise an exception instead of reporting their result. This simulates
131 a failure in the code somewhere
Simon Glassc05694f2013-04-03 11:07:16 +0000132 """
Simon Glassa10ebe12014-09-05 19:00:18 -0600133 global builder
134
Simon Glassca9b06e2014-09-05 19:00:11 -0600135 if options.full_help:
136 pager = os.getenv('PAGER')
137 if not pager:
138 pager = 'more'
Simon Glassc29101f2016-03-06 19:45:34 -0700139 fname = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])),
140 'README')
Simon Glassca9b06e2014-09-05 19:00:11 -0600141 command.Run(pager, fname)
142 return 0
143
Simon Glassc05694f2013-04-03 11:07:16 +0000144 gitutil.Setup()
Simon Glass9f1ba0f2016-07-27 20:33:02 -0600145 col = terminal.Color()
Simon Glassc05694f2013-04-03 11:07:16 +0000146
Simon Glassc05694f2013-04-03 11:07:16 +0000147 options.git_dir = os.path.join(options.git, '.git')
148
Simon Glassa3d9b4f2016-07-27 20:33:04 -0600149 no_toolchains = toolchains is None
150 if no_toolchains:
Simon Glassf77ca5b2019-01-07 16:44:20 -0700151 toolchains = toolchain.Toolchains(options.override_toolchain)
Simon Glassc05694f2013-04-03 11:07:16 +0000152
Simon Glass7e803e12014-12-01 17:34:06 -0700153 if options.fetch_arch:
154 if options.fetch_arch == 'list':
155 sorted_list = toolchains.ListArchs()
Simon Glassc78ed662019-10-31 07:42:53 -0600156 print(col.Color(col.BLUE, 'Available architectures: %s\n' %
157 ' '.join(sorted_list)))
Simon Glass7e803e12014-12-01 17:34:06 -0700158 return 0
159 else:
160 fetch_arch = options.fetch_arch
161 if fetch_arch == 'all':
162 fetch_arch = ','.join(toolchains.ListArchs())
Simon Glassc78ed662019-10-31 07:42:53 -0600163 print(col.Color(col.CYAN, '\nDownloading toolchains: %s' %
164 fetch_arch))
Simon Glass7e803e12014-12-01 17:34:06 -0700165 for arch in fetch_arch.split(','):
Simon Glassc78ed662019-10-31 07:42:53 -0600166 print()
Simon Glass7e803e12014-12-01 17:34:06 -0700167 ret = toolchains.FetchAndInstall(arch)
168 if ret:
169 return ret
170 return 0
171
Simon Glassa3d9b4f2016-07-27 20:33:04 -0600172 if no_toolchains:
173 toolchains.GetSettings()
Simon Glass74579fc2018-11-06 16:02:10 -0700174 toolchains.Scan(options.list_tool_chains and options.verbose)
Simon Glassa3d9b4f2016-07-27 20:33:04 -0600175 if options.list_tool_chains:
176 toolchains.List()
Simon Glassc78ed662019-10-31 07:42:53 -0600177 print()
Simon Glassa3d9b4f2016-07-27 20:33:04 -0600178 return 0
179
Simon Glass6029af12020-04-09 15:08:51 -0600180 if options.incremental:
181 print(col.Color(col.RED,
182 'Warning: -I has been removed. See documentation'))
Simon Glassd9c98632020-04-17 17:51:32 -0600183 if not options.output_dir:
184 if options.work_in_output:
185 sys.exit(col.Color(col.RED, '-w requires that you specify -o'))
186 options.output_dir = '..'
Simon Glass6029af12020-04-09 15:08:51 -0600187
Simon Glassc05694f2013-04-03 11:07:16 +0000188 # Work out what subset of the boards we are building
Simon Glasscbd36582014-09-05 19:00:16 -0600189 if not boards:
Tom Rini6ef6b3f2019-11-19 15:14:33 -0500190 if not os.path.exists(options.output_dir):
191 os.makedirs(options.output_dir)
Bin Meng0733e202019-10-28 07:24:59 -0700192 board_file = os.path.join(options.output_dir, 'boards.cfg')
Simon Glass952109b2020-07-19 09:59:49 -0600193 our_path = os.path.dirname(os.path.realpath(__file__))
194 genboardscfg = os.path.join(our_path, '../genboardscfg.py')
195 if not os.path.exists(genboardscfg):
196 genboardscfg = os.path.join(options.git, 'tools/genboardscfg.py')
Simon Glassaa26d472019-12-05 15:59:12 -0700197 status = subprocess.call([genboardscfg, '-q', '-o', board_file])
Simon Glasscbd36582014-09-05 19:00:16 -0600198 if status != 0:
Simon Glass952109b2020-07-19 09:59:49 -0600199 # Older versions don't support -q
200 status = subprocess.call([genboardscfg, '-o', board_file])
201 if status != 0:
202 sys.exit("Failed to generate boards.cfg")
Masahiro Yamadae9bc8d22014-07-30 14:08:22 +0900203
Simon Glasscbd36582014-09-05 19:00:16 -0600204 boards = board.Boards()
Bin Meng0733e202019-10-28 07:24:59 -0700205 boards.ReadBoards(board_file)
Simon Glass924c73a2014-08-28 09:43:41 -0600206
207 exclude = []
208 if options.exclude:
209 for arg in options.exclude:
210 exclude += arg.split(',')
211
Simon Glassd9eb9f02018-06-11 23:26:46 -0600212 if options.boards:
213 requested_boards = []
214 for b in options.boards:
215 requested_boards += b.split(',')
216 else:
217 requested_boards = None
218 why_selected, board_warnings = boards.SelectBoards(args, exclude,
219 requested_boards)
Simon Glassc05694f2013-04-03 11:07:16 +0000220 selected = boards.GetSelected()
221 if not len(selected):
Masahiro Yamada880828d2014-08-16 00:59:26 +0900222 sys.exit(col.Color(col.RED, 'No matching boards found'))
Simon Glassc05694f2013-04-03 11:07:16 +0000223
Simon Glass2df44be2020-03-18 09:42:47 -0600224 if options.print_prefix:
Simon Glassf4c00722020-04-17 17:51:31 -0600225 err = ShowToolchainPrefix(boards, toolchains)
Simon Glass48ac42e2019-12-05 15:59:14 -0700226 if err:
227 sys.exit(col.Color(col.RED, err))
228 return 0
229
Simon Glass9b550912019-12-05 15:59:13 -0700230 # Work out how many commits to build. We want to build everything on the
231 # branch. We also build the upstream commit as a control so we can see
232 # problems introduced by the first commit on the branch.
233 count = options.count
234 has_range = options.branch and '..' in options.branch
235 if count == -1:
236 if not options.branch:
237 count = 1
238 else:
239 if has_range:
240 count, msg = gitutil.CountCommitsInRange(options.git_dir,
241 options.branch)
242 else:
243 count, msg = gitutil.CountCommitsInBranch(options.git_dir,
244 options.branch)
245 if count is None:
246 sys.exit(col.Color(col.RED, msg))
247 elif count == 0:
248 sys.exit(col.Color(col.RED, "Range '%s' has no commits" %
249 options.branch))
250 if msg:
251 print(col.Color(col.YELLOW, msg))
252 count += 1 # Build upstream commit also
253
254 if not count:
255 str = ("No commits found to process in branch '%s': "
256 "set branch's upstream or use -c flag" % options.branch)
257 sys.exit(col.Color(col.RED, str))
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600258 if options.work_in_output:
259 if len(selected) != 1:
260 sys.exit(col.Color(col.RED,
261 '-w can only be used with a single board'))
262 if count != 1:
263 sys.exit(col.Color(col.RED,
264 '-w can only be used with a single commit'))
Simon Glass9b550912019-12-05 15:59:13 -0700265
Simon Glassc05694f2013-04-03 11:07:16 +0000266 # Read the metadata from the commits. First look at the upstream commit,
267 # then the ones in the branch. We would like to do something like
268 # upstream/master~..branch but that isn't possible if upstream/master is
269 # a merge commit (it will list all the commits that form part of the
270 # merge)
Simon Glass359b55a62014-09-05 19:00:23 -0600271 # Conflicting tags are not a problem for buildman, since it does not use
272 # them. For example, Series-version is not useful for buildman. On the
273 # other hand conflicting tags will cause an error. So allow later tags
274 # to overwrite earlier ones by setting allow_overwrite=True
Simon Glassd326ad72014-08-09 15:32:59 -0600275 if options.branch:
Simon Glass16a52882014-08-09 15:33:09 -0600276 if count == -1:
Simon Glass5eeef462014-12-01 17:33:57 -0700277 if has_range:
278 range_expr = options.branch
279 else:
280 range_expr = gitutil.GetRangeInBranch(options.git_dir,
281 options.branch)
Simon Glass16a52882014-08-09 15:33:09 -0600282 upstream_commit = gitutil.GetUpstream(options.git_dir,
283 options.branch)
Simon Glass93f61c02020-10-29 21:46:19 -0600284 series = patchstream.get_metadata_for_list(upstream_commit,
Simon Glass359b55a62014-09-05 19:00:23 -0600285 options.git_dir, 1, series=None, allow_overwrite=True)
Simon Glassd326ad72014-08-09 15:32:59 -0600286
Simon Glass93f61c02020-10-29 21:46:19 -0600287 series = patchstream.get_metadata_for_list(range_expr,
Simon Glass359b55a62014-09-05 19:00:23 -0600288 options.git_dir, None, series, allow_overwrite=True)
Simon Glass16a52882014-08-09 15:33:09 -0600289 else:
290 # Honour the count
Simon Glass93f61c02020-10-29 21:46:19 -0600291 series = patchstream.get_metadata_for_list(options.branch,
Simon Glass359b55a62014-09-05 19:00:23 -0600292 options.git_dir, count, series=None, allow_overwrite=True)
Simon Glassd326ad72014-08-09 15:32:59 -0600293 else:
294 series = None
Simon Glass6af145f2017-01-23 05:38:56 -0700295 if not options.dry_run:
296 options.verbose = True
297 if not options.summary:
298 options.show_errors = True
Simon Glassc05694f2013-04-03 11:07:16 +0000299
300 # By default we have one thread per CPU. But if there are not enough jobs
301 # we can have fewer threads and use a high '-j' value for make.
Simon Glassc635d892021-01-30 22:17:46 -0700302 if options.threads is None:
Simon Glassc05694f2013-04-03 11:07:16 +0000303 options.threads = min(multiprocessing.cpu_count(), len(selected))
304 if not options.jobs:
305 options.jobs = max(1, (multiprocessing.cpu_count() +
Simon Glassc78ed662019-10-31 07:42:53 -0600306 len(selected) - 1) // len(selected))
Simon Glassc05694f2013-04-03 11:07:16 +0000307
308 if not options.step:
309 options.step = len(series.commits) - 1
310
Masahiro Yamada1fe610d2014-07-22 11:19:09 +0900311 gnu_make = command.Output(os.path.join(options.git,
Simon Glassc55e0562016-07-25 18:59:00 -0600312 'scripts/show-gnu-make'), raise_on_error=False).rstrip()
Masahiro Yamada1fe610d2014-07-22 11:19:09 +0900313 if not gnu_make:
Masahiro Yamada880828d2014-08-16 00:59:26 +0900314 sys.exit('GNU Make not found')
Masahiro Yamada1fe610d2014-07-22 11:19:09 +0900315
Simon Glassdbc01c72014-12-01 17:33:52 -0700316 # Create a new builder with the selected options.
317 output_dir = options.output_dir
Simon Glassd326ad72014-08-09 15:32:59 -0600318 if options.branch:
Simon Glass4aeceb92014-09-05 19:00:22 -0600319 dirname = options.branch.replace('/', '_')
Simon Glasse87bde12014-12-01 17:33:55 -0700320 # As a special case allow the board directory to be placed in the
321 # output directory itself rather than any subdirectory.
322 if not options.no_subdirs:
323 output_dir = os.path.join(options.output_dir, dirname)
Lothar Waßmannce6df922018-04-08 05:14:11 -0600324 if clean_dir and os.path.exists(output_dir):
325 shutil.rmtree(output_dir)
Simon Glassc05694f2013-04-03 11:07:16 +0000326 builder = Builder(toolchains, output_dir, options.git_dir,
Masahiro Yamada1fe610d2014-07-22 11:19:09 +0900327 options.threads, options.jobs, gnu_make=gnu_make, checkout=True,
Simon Glasse87bde12014-12-01 17:33:55 -0700328 show_unknown=options.show_unknown, step=options.step,
Simon Glass655b6102014-12-01 17:34:07 -0700329 no_subdirs=options.no_subdirs, full_path=options.full_path,
Stephen Warren97c96902016-04-11 10:48:44 -0600330 verbose_build=options.verbose_build,
Simon Glass6029af12020-04-09 15:08:51 -0600331 mrproper=options.mrproper,
Simon Glass739e8512016-11-13 14:25:51 -0700332 per_board_out_dir=options.per_board_out_dir,
Simon Glasscde5c302016-11-13 14:25:53 -0700333 config_only=options.config_only,
Daniel Schwierzeck20e2ea92018-01-26 16:31:05 +0100334 squash_config_y=not options.preserve_config_y,
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600335 warnings_as_errors=options.warnings_as_errors,
Simon Glass9bf9a722021-04-11 16:27:27 +1200336 work_in_output=options.work_in_output,
337 test_thread_exceptions=test_thread_exceptions)
Simon Glassc05694f2013-04-03 11:07:16 +0000338 builder.force_config_on_failure = not options.quick
Simon Glassed098bb2014-09-05 19:00:13 -0600339 if make_func:
340 builder.do_make = make_func
Simon Glassc05694f2013-04-03 11:07:16 +0000341
342 # For a dry run, just show our actions as a sanity check
343 if options.dry_run:
Simon Glassd9eb9f02018-06-11 23:26:46 -0600344 ShowActions(series, why_selected, selected, builder, options,
345 board_warnings)
Simon Glassc05694f2013-04-03 11:07:16 +0000346 else:
347 builder.force_build = options.force_build
Simon Glass7041c392014-07-13 12:22:31 -0600348 builder.force_build_failures = options.force_build_failures
Simon Glassf3018b7a2014-07-14 17:51:02 -0600349 builder.force_reconfig = options.force_reconfig
Simon Glass38df2e22014-07-14 17:51:03 -0600350 builder.in_tree = options.in_tree
Simon Glassc05694f2013-04-03 11:07:16 +0000351
352 # Work out which boards to build
353 board_selected = boards.GetSelectedDict()
354
Simon Glassd326ad72014-08-09 15:32:59 -0600355 if series:
356 commits = series.commits
Simon Glassa10ebe12014-09-05 19:00:18 -0600357 # Number the commits for test purposes
358 for commit in range(len(commits)):
359 commits[commit].sequence = commit
Simon Glassd326ad72014-08-09 15:32:59 -0600360 else:
361 commits = None
362
Simon Glassed098bb2014-09-05 19:00:13 -0600363 Print(GetActionSummary(options.summary, commits, board_selected,
Simon Glass9ea93812020-04-09 15:08:52 -0600364 options))
Simon Glassc05694f2013-04-03 11:07:16 +0000365
Simon Glass232d8502014-09-14 20:23:16 -0600366 # We can't show function sizes without board details at present
367 if options.show_bloat:
368 options.show_detail = True
Simon Glass9ea93812020-04-09 15:08:52 -0600369 builder.SetDisplayOptions(
370 options.show_errors, options.show_sizes, options.show_detail,
371 options.show_bloat, options.list_error_boards, options.show_config,
Simon Glassf4ebfba2020-04-09 15:08:53 -0600372 options.show_environment, options.filter_dtb_warnings,
373 options.filter_migration_warnings)
Simon Glassc05694f2013-04-03 11:07:16 +0000374 if options.summary:
Simon Glasseb48bbc2014-08-09 15:33:02 -0600375 builder.ShowSummary(commits, board_selected)
Simon Glassc05694f2013-04-03 11:07:16 +0000376 else:
Simon Glass9bf9a722021-04-11 16:27:27 +1200377 fail, warned, excs = builder.BuildBoards(
378 commits, board_selected, options.keep_outputs, options.verbose)
379 if excs:
380 return 102
381 elif fail:
Simon Glasse4cd5062020-04-09 10:49:45 -0600382 return 100
Simon Glass35e7d382020-03-18 09:42:44 -0600383 elif warned and not options.ignore_warnings:
Simon Glasse4cd5062020-04-09 10:49:45 -0600384 return 101
Simon Glassc2f91072014-08-28 09:43:39 -0600385 return 0