blob: 5fcfba7ca367556f5a8087300d5132203d5ddd53 [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
19from patman.terminal import Print
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
Simon Glass4e9162d2020-03-18 09:42:47 -060088def ShowToolchainPrefix(boards, toolchains):
Simon Glass57cb9d52019-12-05 15:59:14 -070089 """Show information about a the tool chain used by one or more boards
90
Simon Glass4e9162d2020-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 Glass57cb9d52019-12-05 15:59:14 -070093
94 Args:
95 boards: Boards object containing selected boards
96 toolchains: Toolchains object containing available toolchains
Simon Glass57cb9d52019-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 Glass4e9162d2020-03-18 09:42:47 -0600109 print(tc.GetEnvArgs(toolchain.VAR_CROSS_COMPILE))
Simon Glass57cb9d52019-12-05 15:59:14 -0700110 return None
111
Simon Glass883a3212014-09-05 19:00:18 -0600112def DoBuildman(options, args, toolchains=None, make_func=None, boards=None,
113 clean_dir=False):
Simon Glassfc3fe1c2013-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 Glassd4144e42014-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 Glass823e60b2014-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 Glass24993312021-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 Glassfc3fe1c2013-04-03 11:07:16 +0000129 """
Simon Glass883a3212014-09-05 19:00:18 -0600130 global builder
131
Simon Glass48ba5852014-09-05 19:00:11 -0600132 if options.full_help:
133 pager = os.getenv('PAGER')
134 if not pager:
135 pager = 'more'
Simon Glass2bdeade2016-03-06 19:45:34 -0700136 fname = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])),
137 'README')
Simon Glass48ba5852014-09-05 19:00:11 -0600138 command.Run(pager, fname)
139 return 0
140
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000141 gitutil.Setup()
Simon Glass713bea32016-07-27 20:33:02 -0600142 col = terminal.Color()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000143
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000144 options.git_dir = os.path.join(options.git, '.git')
145
Simon Glass7e92e462016-07-27 20:33:04 -0600146 no_toolchains = toolchains is None
147 if no_toolchains:
Simon Glass00beb242019-01-07 16:44:20 -0700148 toolchains = toolchain.Toolchains(options.override_toolchain)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000149
Simon Glass827e37b2014-12-01 17:34:06 -0700150 if options.fetch_arch:
151 if options.fetch_arch == 'list':
152 sorted_list = toolchains.ListArchs()
Simon Glassc05aa032019-10-31 07:42:53 -0600153 print(col.Color(col.BLUE, 'Available architectures: %s\n' %
154 ' '.join(sorted_list)))
Simon Glass827e37b2014-12-01 17:34:06 -0700155 return 0
156 else:
157 fetch_arch = options.fetch_arch
158 if fetch_arch == 'all':
159 fetch_arch = ','.join(toolchains.ListArchs())
Simon Glassc05aa032019-10-31 07:42:53 -0600160 print(col.Color(col.CYAN, '\nDownloading toolchains: %s' %
161 fetch_arch))
Simon Glass827e37b2014-12-01 17:34:06 -0700162 for arch in fetch_arch.split(','):
Simon Glassc05aa032019-10-31 07:42:53 -0600163 print()
Simon Glass827e37b2014-12-01 17:34:06 -0700164 ret = toolchains.FetchAndInstall(arch)
165 if ret:
166 return ret
167 return 0
168
Simon Glass7e92e462016-07-27 20:33:04 -0600169 if no_toolchains:
170 toolchains.GetSettings()
Simon Glass40232c92018-11-06 16:02:10 -0700171 toolchains.Scan(options.list_tool_chains and options.verbose)
Simon Glass7e92e462016-07-27 20:33:04 -0600172 if options.list_tool_chains:
173 toolchains.List()
Simon Glassc05aa032019-10-31 07:42:53 -0600174 print()
Simon Glass7e92e462016-07-27 20:33:04 -0600175 return 0
176
Simon Glasseb70a2c2020-04-09 15:08:51 -0600177 if options.incremental:
178 print(col.Color(col.RED,
179 'Warning: -I has been removed. See documentation'))
Simon Glass88daaef2020-04-17 17:51:32 -0600180 if not options.output_dir:
181 if options.work_in_output:
182 sys.exit(col.Color(col.RED, '-w requires that you specify -o'))
183 options.output_dir = '..'
Simon Glasseb70a2c2020-04-09 15:08:51 -0600184
Simon Glass7c66ead2019-12-05 15:59:13 -0700185 # Work out what subset of the boards we are building
186 if not boards:
187 if not os.path.exists(options.output_dir):
188 os.makedirs(options.output_dir)
189 board_file = os.path.join(options.output_dir, 'boards.cfg')
Simon Glass5a910b92020-07-19 09:59:49 -0600190 our_path = os.path.dirname(os.path.realpath(__file__))
191 genboardscfg = os.path.join(our_path, '../genboardscfg.py')
192 if not os.path.exists(genboardscfg):
193 genboardscfg = os.path.join(options.git, 'tools/genboardscfg.py')
Simon Glass7c66ead2019-12-05 15:59:13 -0700194 status = subprocess.call([genboardscfg, '-q', '-o', board_file])
195 if status != 0:
Simon Glass5a910b92020-07-19 09:59:49 -0600196 # Older versions don't support -q
197 status = subprocess.call([genboardscfg, '-o', board_file])
198 if status != 0:
199 sys.exit("Failed to generate boards.cfg")
Simon Glass7c66ead2019-12-05 15:59:13 -0700200
201 boards = board.Boards()
202 boards.ReadBoards(board_file)
203
204 exclude = []
205 if options.exclude:
206 for arg in options.exclude:
207 exclude += arg.split(',')
208
209 if options.boards:
210 requested_boards = []
211 for b in options.boards:
212 requested_boards += b.split(',')
213 else:
214 requested_boards = None
215 why_selected, board_warnings = boards.SelectBoards(args, exclude,
216 requested_boards)
217 selected = boards.GetSelected()
218 if not len(selected):
219 sys.exit(col.Color(col.RED, 'No matching boards found'))
220
Simon Glass4e9162d2020-03-18 09:42:47 -0600221 if options.print_prefix:
Simon Glass97944d32020-04-17 17:51:31 -0600222 err = ShowToolchainPrefix(boards, toolchains)
Simon Glass57cb9d52019-12-05 15:59:14 -0700223 if err:
224 sys.exit(col.Color(col.RED, err))
225 return 0
226
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000227 # Work out how many commits to build. We want to build everything on the
228 # branch. We also build the upstream commit as a control so we can see
229 # problems introduced by the first commit on the branch.
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000230 count = options.count
Simon Glass5abab202014-12-01 17:33:57 -0700231 has_range = options.branch and '..' in options.branch
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000232 if count == -1:
233 if not options.branch:
Simon Glassfea58582014-08-09 15:32:59 -0600234 count = 1
235 else:
Simon Glass5abab202014-12-01 17:33:57 -0700236 if has_range:
237 count, msg = gitutil.CountCommitsInRange(options.git_dir,
238 options.branch)
239 else:
240 count, msg = gitutil.CountCommitsInBranch(options.git_dir,
241 options.branch)
Simon Glassfea58582014-08-09 15:32:59 -0600242 if count is None:
Simon Glass2a9e2c62014-12-01 17:33:54 -0700243 sys.exit(col.Color(col.RED, msg))
Simon Glass5abab202014-12-01 17:33:57 -0700244 elif count == 0:
245 sys.exit(col.Color(col.RED, "Range '%s' has no commits" %
246 options.branch))
Simon Glass2a9e2c62014-12-01 17:33:54 -0700247 if msg:
Simon Glassc05aa032019-10-31 07:42:53 -0600248 print(col.Color(col.YELLOW, msg))
Simon Glassfea58582014-08-09 15:32:59 -0600249 count += 1 # Build upstream commit also
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000250
251 if not count:
252 str = ("No commits found to process in branch '%s': "
253 "set branch's upstream or use -c flag" % options.branch)
Masahiro Yamada31e21412014-08-16 00:59:26 +0900254 sys.exit(col.Color(col.RED, str))
Simon Glassd829f122020-03-18 09:42:42 -0600255 if options.work_in_output:
256 if len(selected) != 1:
257 sys.exit(col.Color(col.RED,
258 '-w can only be used with a single board'))
259 if count != 1:
260 sys.exit(col.Color(col.RED,
261 '-w can only be used with a single commit'))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000262
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000263 # Read the metadata from the commits. First look at the upstream commit,
264 # then the ones in the branch. We would like to do something like
265 # upstream/master~..branch but that isn't possible if upstream/master is
266 # a merge commit (it will list all the commits that form part of the
267 # merge)
Simon Glass950a2312014-09-05 19:00:23 -0600268 # Conflicting tags are not a problem for buildman, since it does not use
269 # them. For example, Series-version is not useful for buildman. On the
270 # other hand conflicting tags will cause an error. So allow later tags
271 # to overwrite earlier ones by setting allow_overwrite=True
Simon Glassfea58582014-08-09 15:32:59 -0600272 if options.branch:
Simon Glass3b74ba52014-08-09 15:33:09 -0600273 if count == -1:
Simon Glass5abab202014-12-01 17:33:57 -0700274 if has_range:
275 range_expr = options.branch
276 else:
277 range_expr = gitutil.GetRangeInBranch(options.git_dir,
278 options.branch)
Simon Glass3b74ba52014-08-09 15:33:09 -0600279 upstream_commit = gitutil.GetUpstream(options.git_dir,
280 options.branch)
Simon Glassd93720e2020-10-29 21:46:19 -0600281 series = patchstream.get_metadata_for_list(upstream_commit,
Simon Glass950a2312014-09-05 19:00:23 -0600282 options.git_dir, 1, series=None, allow_overwrite=True)
Simon Glassfea58582014-08-09 15:32:59 -0600283
Simon Glassd93720e2020-10-29 21:46:19 -0600284 series = patchstream.get_metadata_for_list(range_expr,
Simon Glass950a2312014-09-05 19:00:23 -0600285 options.git_dir, None, series, allow_overwrite=True)
Simon Glass3b74ba52014-08-09 15:33:09 -0600286 else:
287 # Honour the count
Simon Glassd93720e2020-10-29 21:46:19 -0600288 series = patchstream.get_metadata_for_list(options.branch,
Simon Glass950a2312014-09-05 19:00:23 -0600289 options.git_dir, count, series=None, allow_overwrite=True)
Simon Glassfea58582014-08-09 15:32:59 -0600290 else:
291 series = None
Simon Glass8d7523c2017-01-23 05:38:56 -0700292 if not options.dry_run:
293 options.verbose = True
294 if not options.summary:
295 options.show_errors = True
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000296
297 # By default we have one thread per CPU. But if there are not enough jobs
298 # we can have fewer threads and use a high '-j' value for make.
Simon Glassb82492b2021-01-30 22:17:46 -0700299 if options.threads is None:
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000300 options.threads = min(multiprocessing.cpu_count(), len(selected))
301 if not options.jobs:
302 options.jobs = max(1, (multiprocessing.cpu_count() +
Simon Glassc05aa032019-10-31 07:42:53 -0600303 len(selected) - 1) // len(selected))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000304
305 if not options.step:
306 options.step = len(series.commits) - 1
307
Masahiro Yamada99796922014-07-22 11:19:09 +0900308 gnu_make = command.Output(os.path.join(options.git,
Simon Glass785f1542016-07-25 18:59:00 -0600309 'scripts/show-gnu-make'), raise_on_error=False).rstrip()
Masahiro Yamada99796922014-07-22 11:19:09 +0900310 if not gnu_make:
Masahiro Yamada31e21412014-08-16 00:59:26 +0900311 sys.exit('GNU Make not found')
Masahiro Yamada99796922014-07-22 11:19:09 +0900312
Simon Glass05c96b12014-12-01 17:33:52 -0700313 # Create a new builder with the selected options.
314 output_dir = options.output_dir
Simon Glassfea58582014-08-09 15:32:59 -0600315 if options.branch:
Simon Glassf7582ce2014-09-05 19:00:22 -0600316 dirname = options.branch.replace('/', '_')
Simon Glass5971ab52014-12-01 17:33:55 -0700317 # As a special case allow the board directory to be placed in the
318 # output directory itself rather than any subdirectory.
319 if not options.no_subdirs:
320 output_dir = os.path.join(options.output_dir, dirname)
Lothar Waßmann409fc022018-04-08 05:14:11 -0600321 if clean_dir and os.path.exists(output_dir):
322 shutil.rmtree(output_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000323 builder = Builder(toolchains, output_dir, options.git_dir,
Masahiro Yamada99796922014-07-22 11:19:09 +0900324 options.threads, options.jobs, gnu_make=gnu_make, checkout=True,
Simon Glass5971ab52014-12-01 17:33:55 -0700325 show_unknown=options.show_unknown, step=options.step,
Simon Glassd2ce6582014-12-01 17:34:07 -0700326 no_subdirs=options.no_subdirs, full_path=options.full_path,
Stephen Warrenf79f1e02016-04-11 10:48:44 -0600327 verbose_build=options.verbose_build,
Simon Glasseb70a2c2020-04-09 15:08:51 -0600328 mrproper=options.mrproper,
Simon Glassb50113f2016-11-13 14:25:51 -0700329 per_board_out_dir=options.per_board_out_dir,
Simon Glassb464f8e2016-11-13 14:25:53 -0700330 config_only=options.config_only,
Daniel Schwierzeck2371d1b2018-01-26 16:31:05 +0100331 squash_config_y=not options.preserve_config_y,
Simon Glassd829f122020-03-18 09:42:42 -0600332 warnings_as_errors=options.warnings_as_errors,
333 work_in_output=options.work_in_output)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000334 builder.force_config_on_failure = not options.quick
Simon Glassd4144e42014-09-05 19:00:13 -0600335 if make_func:
336 builder.do_make = make_func
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000337
338 # For a dry run, just show our actions as a sanity check
339 if options.dry_run:
Simon Glass06890362018-06-11 23:26:46 -0600340 ShowActions(series, why_selected, selected, builder, options,
341 board_warnings)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000342 else:
343 builder.force_build = options.force_build
Simon Glass4266dc22014-07-13 12:22:31 -0600344 builder.force_build_failures = options.force_build_failures
Simon Glass97e91522014-07-14 17:51:02 -0600345 builder.force_reconfig = options.force_reconfig
Simon Glass189a4962014-07-14 17:51:03 -0600346 builder.in_tree = options.in_tree
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000347
348 # Work out which boards to build
349 board_selected = boards.GetSelectedDict()
350
Simon Glassfea58582014-08-09 15:32:59 -0600351 if series:
352 commits = series.commits
Simon Glass883a3212014-09-05 19:00:18 -0600353 # Number the commits for test purposes
354 for commit in range(len(commits)):
355 commits[commit].sequence = commit
Simon Glassfea58582014-08-09 15:32:59 -0600356 else:
357 commits = None
358
Simon Glassd4144e42014-09-05 19:00:13 -0600359 Print(GetActionSummary(options.summary, commits, board_selected,
Simon Glass174592b2020-04-09 15:08:52 -0600360 options))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000361
Simon Glass7798e222014-09-14 20:23:16 -0600362 # We can't show function sizes without board details at present
363 if options.show_bloat:
364 options.show_detail = True
Simon Glass174592b2020-04-09 15:08:52 -0600365 builder.SetDisplayOptions(
366 options.show_errors, options.show_sizes, options.show_detail,
367 options.show_bloat, options.list_error_boards, options.show_config,
Simon Glass113a8a52020-04-09 15:08:53 -0600368 options.show_environment, options.filter_dtb_warnings,
369 options.filter_migration_warnings)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000370 if options.summary:
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600371 builder.ShowSummary(commits, board_selected)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000372 else:
Simon Glass2c3deb92014-08-28 09:43:39 -0600373 fail, warned = builder.BuildBoards(commits, board_selected,
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600374 options.keep_outputs, options.verbose)
Simon Glass2c3deb92014-08-28 09:43:39 -0600375 if fail:
Simon Glassb1e5e6d2020-04-09 10:49:45 -0600376 return 100
Simon Glass7beb43c2020-03-18 09:42:44 -0600377 elif warned and not options.ignore_warnings:
Simon Glassb1e5e6d2020-04-09 10:49:45 -0600378 return 101
Simon Glass2c3deb92014-08-28 09:43:39 -0600379 return 0