blob: fd9664c85d881bff6625d2f48888c0e0a19ff8ec [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 Glass0ede00f2020-04-17 18:09:02 -06008import subprocess
Simon Glassfc3fe1c2013-04-03 11:07:16 +00009import sys
10
Simon Glass0ede00f2020-04-17 18:09:02 -060011from buildman import board
12from buildman import bsettings
13from buildman import toolchain
14from buildman.builder import Builder
Simon Glassbf776672020-04-17 18:09:04 -060015from patman import command
16from patman import gitutil
17from patman import patchstream
18from patman import terminal
Paul Barker5fe50f92021-09-08 12:38:01 +010019from patman import tools
Simon Glassbf776672020-04-17 18:09:04 -060020from patman.terminal import Print
Simon Glassfc3fe1c2013-04-03 11:07:16 +000021
22def GetPlural(count):
23 """Returns a plural 's' if count is not 1"""
24 return 's' if count != 1 else ''
25
Simon Glassfea58582014-08-09 15:32:59 -060026def GetActionSummary(is_summary, commits, selected, options):
Simon Glassfc3fe1c2013-04-03 11:07:16 +000027 """Return a string summarising the intended action.
28
29 Returns:
30 Summary string.
31 """
Simon Glassfea58582014-08-09 15:32:59 -060032 if commits:
33 count = len(commits)
Simon Glassc05aa032019-10-31 07:42:53 -060034 count = (count + options.step - 1) // options.step
Simon Glassfea58582014-08-09 15:32:59 -060035 commit_str = '%d commit%s' % (count, GetPlural(count))
36 else:
37 commit_str = 'current source'
38 str = '%s %s for %d boards' % (
39 'Summary of' if is_summary else 'Building', commit_str,
Simon Glassfc3fe1c2013-04-03 11:07:16 +000040 len(selected))
41 str += ' (%d thread%s, %d job%s per thread)' % (options.threads,
42 GetPlural(options.threads), options.jobs, GetPlural(options.jobs))
43 return str
44
Simon Glass06890362018-06-11 23:26:46 -060045def ShowActions(series, why_selected, boards_selected, builder, options,
46 board_warnings):
Simon Glassfc3fe1c2013-04-03 11:07:16 +000047 """Display a list of actions that we would take, if not a dry run.
48
49 Args:
50 series: Series object
51 why_selected: Dictionary where each key is a buildman argument
Simon Glass8d7523c2017-01-23 05:38:56 -070052 provided by the user, and the value is the list of boards
53 brought in by that argument. For example, 'arm' might bring
54 in 400 boards, so in this case the key would be 'arm' and
Simon Glassfc3fe1c2013-04-03 11:07:16 +000055 the value would be a list of board names.
56 boards_selected: Dict of selected boards, key is target name,
57 value is Board object
58 builder: The builder that will be used to build the commits
59 options: Command line options object
Simon Glass06890362018-06-11 23:26:46 -060060 board_warnings: List of warnings obtained from board selected
Simon Glassfc3fe1c2013-04-03 11:07:16 +000061 """
62 col = terminal.Color()
Simon Glassc05aa032019-10-31 07:42:53 -060063 print('Dry run, so not doing much. But I would do this:')
64 print()
Simon Glassfea58582014-08-09 15:32:59 -060065 if series:
66 commits = series.commits
67 else:
68 commits = None
Simon Glassc05aa032019-10-31 07:42:53 -060069 print(GetActionSummary(False, commits, boards_selected,
70 options))
71 print('Build directory: %s' % builder.base_dir)
Simon Glassfea58582014-08-09 15:32:59 -060072 if commits:
73 for upto in range(0, len(series.commits), options.step):
74 commit = series.commits[upto]
Simon Glassc05aa032019-10-31 07:42:53 -060075 print(' ', col.Color(col.YELLOW, commit.hash[:8], bright=False), end=' ')
76 print(commit.subject)
77 print()
Simon Glassfc3fe1c2013-04-03 11:07:16 +000078 for arg in why_selected:
79 if arg != 'all':
Simon Glassc05aa032019-10-31 07:42:53 -060080 print(arg, ': %d boards' % len(why_selected[arg]))
Simon Glass8d7523c2017-01-23 05:38:56 -070081 if options.verbose:
Simon Glassc05aa032019-10-31 07:42:53 -060082 print(' %s' % ' '.join(why_selected[arg]))
83 print(('Total boards to build for each commit: %d\n' %
84 len(why_selected['all'])))
Simon Glass06890362018-06-11 23:26:46 -060085 if board_warnings:
86 for warning in board_warnings:
Simon Glassc05aa032019-10-31 07:42:53 -060087 print(col.Color(col.YELLOW, warning))
Simon Glassfc3fe1c2013-04-03 11:07:16 +000088
Simon Glass4e9162d2020-03-18 09:42:47 -060089def ShowToolchainPrefix(boards, toolchains):
Simon Glass57cb9d52019-12-05 15:59:14 -070090 """Show information about a the tool chain used by one or more boards
91
Simon Glass4e9162d2020-03-18 09:42:47 -060092 The function checks that all boards use the same toolchain, then prints
93 the correct value for CROSS_COMPILE.
Simon Glass57cb9d52019-12-05 15:59:14 -070094
95 Args:
96 boards: Boards object containing selected boards
97 toolchains: Toolchains object containing available toolchains
Simon Glass57cb9d52019-12-05 15:59:14 -070098
99 Return:
100 None on success, string error message otherwise
101 """
102 boards = boards.GetSelectedDict()
103 tc_set = set()
104 for brd in boards.values():
105 tc_set.add(toolchains.Select(brd.arch))
106 if len(tc_set) != 1:
107 return 'Supplied boards must share one toolchain'
108 return False
109 tc = tc_set.pop()
Simon Glass4e9162d2020-03-18 09:42:47 -0600110 print(tc.GetEnvArgs(toolchain.VAR_CROSS_COMPILE))
Simon Glass57cb9d52019-12-05 15:59:14 -0700111 return None
112
Simon Glass883a3212014-09-05 19:00:18 -0600113def DoBuildman(options, args, toolchains=None, make_func=None, boards=None,
Simon Glass8116c782021-04-11 16:27:27 +1200114 clean_dir=False, test_thread_exceptions=False):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000115 """The main control code for buildman
116
117 Args:
118 options: Command line options object
119 args: Command line arguments (list of strings)
Simon Glassd4144e42014-09-05 19:00:13 -0600120 toolchains: Toolchains to use - this should be a Toolchains()
121 object. If None, then it will be created and scanned
122 make_func: Make function to use for the builder. This is called
123 to execute 'make'. If this is None, the normal function
124 will be used, which calls the 'make' tool with suitable
125 arguments. This setting is useful for tests.
Simon Glass823e60b2014-09-05 19:00:16 -0600126 board: Boards() object to use, containing a list of available
127 boards. If this is None it will be created and scanned.
Simon Glass24993312021-04-11 16:27:25 +1200128 clean_dir: Used for tests only, indicates that the existing output_dir
129 should be removed before starting the build
Simon Glass8116c782021-04-11 16:27:27 +1200130 test_thread_exceptions: Uses for tests only, True to make the threads
131 raise an exception instead of reporting their result. This simulates
132 a failure in the code somewhere
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000133 """
Simon Glass883a3212014-09-05 19:00:18 -0600134 global builder
135
Simon Glass48ba5852014-09-05 19:00:11 -0600136 if options.full_help:
Paul Barker5fe50f92021-09-08 12:38:01 +0100137 tools.PrintFullHelp(
138 os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])), 'README')
139 )
Simon Glass48ba5852014-09-05 19:00:11 -0600140 return 0
141
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000142 gitutil.Setup()
Simon Glass713bea32016-07-27 20:33:02 -0600143 col = terminal.Color()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000144
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000145 options.git_dir = os.path.join(options.git, '.git')
146
Simon Glass7e92e462016-07-27 20:33:04 -0600147 no_toolchains = toolchains is None
148 if no_toolchains:
Simon Glass00beb242019-01-07 16:44:20 -0700149 toolchains = toolchain.Toolchains(options.override_toolchain)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000150
Simon Glass827e37b2014-12-01 17:34:06 -0700151 if options.fetch_arch:
152 if options.fetch_arch == 'list':
153 sorted_list = toolchains.ListArchs()
Simon Glassc05aa032019-10-31 07:42:53 -0600154 print(col.Color(col.BLUE, 'Available architectures: %s\n' %
155 ' '.join(sorted_list)))
Simon Glass827e37b2014-12-01 17:34:06 -0700156 return 0
157 else:
158 fetch_arch = options.fetch_arch
159 if fetch_arch == 'all':
160 fetch_arch = ','.join(toolchains.ListArchs())
Simon Glassc05aa032019-10-31 07:42:53 -0600161 print(col.Color(col.CYAN, '\nDownloading toolchains: %s' %
162 fetch_arch))
Simon Glass827e37b2014-12-01 17:34:06 -0700163 for arch in fetch_arch.split(','):
Simon Glassc05aa032019-10-31 07:42:53 -0600164 print()
Simon Glass827e37b2014-12-01 17:34:06 -0700165 ret = toolchains.FetchAndInstall(arch)
166 if ret:
167 return ret
168 return 0
169
Simon Glass7e92e462016-07-27 20:33:04 -0600170 if no_toolchains:
171 toolchains.GetSettings()
Simon Glass40232c92018-11-06 16:02:10 -0700172 toolchains.Scan(options.list_tool_chains and options.verbose)
Simon Glass7e92e462016-07-27 20:33:04 -0600173 if options.list_tool_chains:
174 toolchains.List()
Simon Glassc05aa032019-10-31 07:42:53 -0600175 print()
Simon Glass7e92e462016-07-27 20:33:04 -0600176 return 0
177
Simon Glasseb70a2c2020-04-09 15:08:51 -0600178 if options.incremental:
179 print(col.Color(col.RED,
180 'Warning: -I has been removed. See documentation'))
Simon Glass88daaef2020-04-17 17:51:32 -0600181 if not options.output_dir:
182 if options.work_in_output:
183 sys.exit(col.Color(col.RED, '-w requires that you specify -o'))
184 options.output_dir = '..'
Simon Glasseb70a2c2020-04-09 15:08:51 -0600185
Simon Glass7c66ead2019-12-05 15:59:13 -0700186 # Work out what subset of the boards we are building
187 if not boards:
188 if not os.path.exists(options.output_dir):
189 os.makedirs(options.output_dir)
190 board_file = os.path.join(options.output_dir, 'boards.cfg')
Simon Glass5a910b92020-07-19 09:59:49 -0600191 our_path = os.path.dirname(os.path.realpath(__file__))
192 genboardscfg = os.path.join(our_path, '../genboardscfg.py')
193 if not os.path.exists(genboardscfg):
194 genboardscfg = os.path.join(options.git, 'tools/genboardscfg.py')
Simon Glass7c66ead2019-12-05 15:59:13 -0700195 status = subprocess.call([genboardscfg, '-q', '-o', board_file])
196 if status != 0:
Simon Glass5a910b92020-07-19 09:59:49 -0600197 # Older versions don't support -q
198 status = subprocess.call([genboardscfg, '-o', board_file])
199 if status != 0:
200 sys.exit("Failed to generate boards.cfg")
Simon Glass7c66ead2019-12-05 15:59:13 -0700201
202 boards = board.Boards()
203 boards.ReadBoards(board_file)
204
205 exclude = []
206 if options.exclude:
207 for arg in options.exclude:
208 exclude += arg.split(',')
209
210 if options.boards:
211 requested_boards = []
212 for b in options.boards:
213 requested_boards += b.split(',')
214 else:
215 requested_boards = None
216 why_selected, board_warnings = boards.SelectBoards(args, exclude,
217 requested_boards)
218 selected = boards.GetSelected()
219 if not len(selected):
220 sys.exit(col.Color(col.RED, 'No matching boards found'))
221
Simon Glass4e9162d2020-03-18 09:42:47 -0600222 if options.print_prefix:
Simon Glass97944d32020-04-17 17:51:31 -0600223 err = ShowToolchainPrefix(boards, toolchains)
Simon Glass57cb9d52019-12-05 15:59:14 -0700224 if err:
225 sys.exit(col.Color(col.RED, err))
226 return 0
227
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000228 # Work out how many commits to build. We want to build everything on the
229 # branch. We also build the upstream commit as a control so we can see
230 # problems introduced by the first commit on the branch.
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000231 count = options.count
Simon Glass5abab202014-12-01 17:33:57 -0700232 has_range = options.branch and '..' in options.branch
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000233 if count == -1:
234 if not options.branch:
Simon Glassfea58582014-08-09 15:32:59 -0600235 count = 1
236 else:
Simon Glass5abab202014-12-01 17:33:57 -0700237 if has_range:
238 count, msg = gitutil.CountCommitsInRange(options.git_dir,
239 options.branch)
240 else:
241 count, msg = gitutil.CountCommitsInBranch(options.git_dir,
242 options.branch)
Simon Glassfea58582014-08-09 15:32:59 -0600243 if count is None:
Simon Glass2a9e2c62014-12-01 17:33:54 -0700244 sys.exit(col.Color(col.RED, msg))
Simon Glass5abab202014-12-01 17:33:57 -0700245 elif count == 0:
246 sys.exit(col.Color(col.RED, "Range '%s' has no commits" %
247 options.branch))
Simon Glass2a9e2c62014-12-01 17:33:54 -0700248 if msg:
Simon Glassc05aa032019-10-31 07:42:53 -0600249 print(col.Color(col.YELLOW, msg))
Simon Glassfea58582014-08-09 15:32:59 -0600250 count += 1 # Build upstream commit also
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000251
252 if not count:
253 str = ("No commits found to process in branch '%s': "
254 "set branch's upstream or use -c flag" % options.branch)
Masahiro Yamada31e21412014-08-16 00:59:26 +0900255 sys.exit(col.Color(col.RED, str))
Simon Glassd829f122020-03-18 09:42:42 -0600256 if options.work_in_output:
257 if len(selected) != 1:
258 sys.exit(col.Color(col.RED,
259 '-w can only be used with a single board'))
260 if count != 1:
261 sys.exit(col.Color(col.RED,
262 '-w can only be used with a single commit'))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000263
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000264 # Read the metadata from the commits. First look at the upstream commit,
265 # then the ones in the branch. We would like to do something like
266 # upstream/master~..branch but that isn't possible if upstream/master is
267 # a merge commit (it will list all the commits that form part of the
268 # merge)
Simon Glass950a2312014-09-05 19:00:23 -0600269 # Conflicting tags are not a problem for buildman, since it does not use
270 # them. For example, Series-version is not useful for buildman. On the
271 # other hand conflicting tags will cause an error. So allow later tags
272 # to overwrite earlier ones by setting allow_overwrite=True
Simon Glassfea58582014-08-09 15:32:59 -0600273 if options.branch:
Simon Glass3b74ba52014-08-09 15:33:09 -0600274 if count == -1:
Simon Glass5abab202014-12-01 17:33:57 -0700275 if has_range:
276 range_expr = options.branch
277 else:
278 range_expr = gitutil.GetRangeInBranch(options.git_dir,
279 options.branch)
Simon Glass3b74ba52014-08-09 15:33:09 -0600280 upstream_commit = gitutil.GetUpstream(options.git_dir,
281 options.branch)
Simon Glassd93720e2020-10-29 21:46:19 -0600282 series = patchstream.get_metadata_for_list(upstream_commit,
Simon Glass950a2312014-09-05 19:00:23 -0600283 options.git_dir, 1, series=None, allow_overwrite=True)
Simon Glassfea58582014-08-09 15:32:59 -0600284
Simon Glassd93720e2020-10-29 21:46:19 -0600285 series = patchstream.get_metadata_for_list(range_expr,
Simon Glass950a2312014-09-05 19:00:23 -0600286 options.git_dir, None, series, allow_overwrite=True)
Simon Glass3b74ba52014-08-09 15:33:09 -0600287 else:
288 # Honour the count
Simon Glassd93720e2020-10-29 21:46:19 -0600289 series = patchstream.get_metadata_for_list(options.branch,
Simon Glass950a2312014-09-05 19:00:23 -0600290 options.git_dir, count, series=None, allow_overwrite=True)
Simon Glassfea58582014-08-09 15:32:59 -0600291 else:
292 series = None
Simon Glass8d7523c2017-01-23 05:38:56 -0700293 if not options.dry_run:
294 options.verbose = True
295 if not options.summary:
296 options.show_errors = True
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000297
298 # By default we have one thread per CPU. But if there are not enough jobs
299 # we can have fewer threads and use a high '-j' value for make.
Simon Glassb82492b2021-01-30 22:17:46 -0700300 if options.threads is None:
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000301 options.threads = min(multiprocessing.cpu_count(), len(selected))
302 if not options.jobs:
303 options.jobs = max(1, (multiprocessing.cpu_count() +
Simon Glassc05aa032019-10-31 07:42:53 -0600304 len(selected) - 1) // len(selected))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000305
306 if not options.step:
307 options.step = len(series.commits) - 1
308
Masahiro Yamada99796922014-07-22 11:19:09 +0900309 gnu_make = command.Output(os.path.join(options.git,
Simon Glass785f1542016-07-25 18:59:00 -0600310 'scripts/show-gnu-make'), raise_on_error=False).rstrip()
Masahiro Yamada99796922014-07-22 11:19:09 +0900311 if not gnu_make:
Masahiro Yamada31e21412014-08-16 00:59:26 +0900312 sys.exit('GNU Make not found')
Masahiro Yamada99796922014-07-22 11:19:09 +0900313
Simon Glass05c96b12014-12-01 17:33:52 -0700314 # Create a new builder with the selected options.
315 output_dir = options.output_dir
Simon Glassfea58582014-08-09 15:32:59 -0600316 if options.branch:
Simon Glassf7582ce2014-09-05 19:00:22 -0600317 dirname = options.branch.replace('/', '_')
Simon Glass5971ab52014-12-01 17:33:55 -0700318 # As a special case allow the board directory to be placed in the
319 # output directory itself rather than any subdirectory.
320 if not options.no_subdirs:
321 output_dir = os.path.join(options.output_dir, dirname)
Lothar Waßmann409fc022018-04-08 05:14:11 -0600322 if clean_dir and os.path.exists(output_dir):
323 shutil.rmtree(output_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000324 builder = Builder(toolchains, output_dir, options.git_dir,
Masahiro Yamada99796922014-07-22 11:19:09 +0900325 options.threads, options.jobs, gnu_make=gnu_make, checkout=True,
Simon Glass5971ab52014-12-01 17:33:55 -0700326 show_unknown=options.show_unknown, step=options.step,
Simon Glassd2ce6582014-12-01 17:34:07 -0700327 no_subdirs=options.no_subdirs, full_path=options.full_path,
Stephen Warrenf79f1e02016-04-11 10:48:44 -0600328 verbose_build=options.verbose_build,
Simon Glasseb70a2c2020-04-09 15:08:51 -0600329 mrproper=options.mrproper,
Simon Glassb50113f2016-11-13 14:25:51 -0700330 per_board_out_dir=options.per_board_out_dir,
Simon Glassb464f8e2016-11-13 14:25:53 -0700331 config_only=options.config_only,
Daniel Schwierzeck2371d1b2018-01-26 16:31:05 +0100332 squash_config_y=not options.preserve_config_y,
Simon Glassd829f122020-03-18 09:42:42 -0600333 warnings_as_errors=options.warnings_as_errors,
Simon Glass8116c782021-04-11 16:27:27 +1200334 work_in_output=options.work_in_output,
335 test_thread_exceptions=test_thread_exceptions)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000336 builder.force_config_on_failure = not options.quick
Simon Glassd4144e42014-09-05 19:00:13 -0600337 if make_func:
338 builder.do_make = make_func
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000339
340 # For a dry run, just show our actions as a sanity check
341 if options.dry_run:
Simon Glass06890362018-06-11 23:26:46 -0600342 ShowActions(series, why_selected, selected, builder, options,
343 board_warnings)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000344 else:
345 builder.force_build = options.force_build
Simon Glass4266dc22014-07-13 12:22:31 -0600346 builder.force_build_failures = options.force_build_failures
Simon Glass97e91522014-07-14 17:51:02 -0600347 builder.force_reconfig = options.force_reconfig
Simon Glass189a4962014-07-14 17:51:03 -0600348 builder.in_tree = options.in_tree
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000349
350 # Work out which boards to build
351 board_selected = boards.GetSelectedDict()
352
Simon Glassfea58582014-08-09 15:32:59 -0600353 if series:
354 commits = series.commits
Simon Glass883a3212014-09-05 19:00:18 -0600355 # Number the commits for test purposes
356 for commit in range(len(commits)):
357 commits[commit].sequence = commit
Simon Glassfea58582014-08-09 15:32:59 -0600358 else:
359 commits = None
360
Simon Glassd4144e42014-09-05 19:00:13 -0600361 Print(GetActionSummary(options.summary, commits, board_selected,
Simon Glass174592b2020-04-09 15:08:52 -0600362 options))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000363
Simon Glass7798e222014-09-14 20:23:16 -0600364 # We can't show function sizes without board details at present
365 if options.show_bloat:
366 options.show_detail = True
Simon Glass174592b2020-04-09 15:08:52 -0600367 builder.SetDisplayOptions(
368 options.show_errors, options.show_sizes, options.show_detail,
369 options.show_bloat, options.list_error_boards, options.show_config,
Simon Glass113a8a52020-04-09 15:08:53 -0600370 options.show_environment, options.filter_dtb_warnings,
371 options.filter_migration_warnings)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000372 if options.summary:
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600373 builder.ShowSummary(commits, board_selected)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000374 else:
Simon Glass8116c782021-04-11 16:27:27 +1200375 fail, warned, excs = builder.BuildBoards(
376 commits, board_selected, options.keep_outputs, options.verbose)
377 if excs:
378 return 102
379 elif fail:
Simon Glassb1e5e6d2020-04-09 10:49:45 -0600380 return 100
Simon Glass7beb43c2020-03-18 09:42:44 -0600381 elif warned and not options.ignore_warnings:
Simon Glassb1e5e6d2020-04-09 10:49:45 -0600382 return 101
Simon Glass2c3deb92014-08-28 09:43:39 -0600383 return 0