blob: 969d866547a4d775c365cadb6f0ef63f7009321e [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))
266
Simon Glassc05694f2013-04-03 11:07:16 +0000267 # Read the metadata from the commits. First look at the upstream commit,
268 # then the ones in the branch. We would like to do something like
269 # upstream/master~..branch but that isn't possible if upstream/master is
270 # a merge commit (it will list all the commits that form part of the
271 # merge)
Simon Glass359b55a62014-09-05 19:00:23 -0600272 # Conflicting tags are not a problem for buildman, since it does not use
273 # them. For example, Series-version is not useful for buildman. On the
274 # other hand conflicting tags will cause an error. So allow later tags
275 # to overwrite earlier ones by setting allow_overwrite=True
Simon Glassd326ad72014-08-09 15:32:59 -0600276 if options.branch:
Simon Glass16a52882014-08-09 15:33:09 -0600277 if count == -1:
Simon Glass5eeef462014-12-01 17:33:57 -0700278 if has_range:
279 range_expr = options.branch
280 else:
281 range_expr = gitutil.GetRangeInBranch(options.git_dir,
282 options.branch)
Simon Glass16a52882014-08-09 15:33:09 -0600283 upstream_commit = gitutil.GetUpstream(options.git_dir,
284 options.branch)
285 series = patchstream.GetMetaDataForList(upstream_commit,
Simon Glass359b55a62014-09-05 19:00:23 -0600286 options.git_dir, 1, series=None, allow_overwrite=True)
Simon Glassd326ad72014-08-09 15:32:59 -0600287
Simon Glass16a52882014-08-09 15:33:09 -0600288 series = patchstream.GetMetaDataForList(range_expr,
Simon Glass359b55a62014-09-05 19:00:23 -0600289 options.git_dir, None, series, allow_overwrite=True)
Simon Glass16a52882014-08-09 15:33:09 -0600290 else:
291 # Honour the count
292 series = patchstream.GetMetaDataForList(options.branch,
Simon Glass359b55a62014-09-05 19:00:23 -0600293 options.git_dir, count, series=None, allow_overwrite=True)
Simon Glassd326ad72014-08-09 15:32:59 -0600294 else:
295 series = None
Simon Glass6af145f2017-01-23 05:38:56 -0700296 if not options.dry_run:
297 options.verbose = True
298 if not options.summary:
299 options.show_errors = True
Simon Glassc05694f2013-04-03 11:07:16 +0000300
301 # By default we have one thread per CPU. But if there are not enough jobs
302 # we can have fewer threads and use a high '-j' value for make.
303 if not options.threads:
304 options.threads = min(multiprocessing.cpu_count(), len(selected))
305 if not options.jobs:
306 options.jobs = max(1, (multiprocessing.cpu_count() +
Simon Glassc78ed662019-10-31 07:42:53 -0600307 len(selected) - 1) // len(selected))
Simon Glassc05694f2013-04-03 11:07:16 +0000308
309 if not options.step:
310 options.step = len(series.commits) - 1
311
Masahiro Yamada1fe610d2014-07-22 11:19:09 +0900312 gnu_make = command.Output(os.path.join(options.git,
Simon Glassc55e0562016-07-25 18:59:00 -0600313 'scripts/show-gnu-make'), raise_on_error=False).rstrip()
Masahiro Yamada1fe610d2014-07-22 11:19:09 +0900314 if not gnu_make:
Masahiro Yamada880828d2014-08-16 00:59:26 +0900315 sys.exit('GNU Make not found')
Masahiro Yamada1fe610d2014-07-22 11:19:09 +0900316
Simon Glassdbc01c72014-12-01 17:33:52 -0700317 # Create a new builder with the selected options.
318 output_dir = options.output_dir
Simon Glassd326ad72014-08-09 15:32:59 -0600319 if options.branch:
Simon Glass4aeceb92014-09-05 19:00:22 -0600320 dirname = options.branch.replace('/', '_')
Simon Glasse87bde12014-12-01 17:33:55 -0700321 # As a special case allow the board directory to be placed in the
322 # output directory itself rather than any subdirectory.
323 if not options.no_subdirs:
324 output_dir = os.path.join(options.output_dir, dirname)
Lothar Waßmannce6df922018-04-08 05:14:11 -0600325 if clean_dir and os.path.exists(output_dir):
326 shutil.rmtree(output_dir)
327 CheckOutputDir(output_dir)
Simon Glassc05694f2013-04-03 11:07:16 +0000328 builder = Builder(toolchains, output_dir, options.git_dir,
Masahiro Yamada1fe610d2014-07-22 11:19:09 +0900329 options.threads, options.jobs, gnu_make=gnu_make, checkout=True,
Simon Glasse87bde12014-12-01 17:33:55 -0700330 show_unknown=options.show_unknown, step=options.step,
Simon Glass655b6102014-12-01 17:34:07 -0700331 no_subdirs=options.no_subdirs, full_path=options.full_path,
Stephen Warren97c96902016-04-11 10:48:44 -0600332 verbose_build=options.verbose_build,
333 incremental=options.incremental,
Simon Glass739e8512016-11-13 14:25:51 -0700334 per_board_out_dir=options.per_board_out_dir,
Simon Glasscde5c302016-11-13 14:25:53 -0700335 config_only=options.config_only,
Daniel Schwierzeck20e2ea92018-01-26 16:31:05 +0100336 squash_config_y=not options.preserve_config_y,
337 warnings_as_errors=options.warnings_as_errors)
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,
364 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 Glasseb48bbc2014-08-09 15:33:02 -0600369 builder.SetDisplayOptions(options.show_errors, options.show_sizes,
Simon Glass3394c9f2014-08-28 09:43:43 -0600370 options.show_detail, options.show_bloat,
Simon Glassdb17fb82015-02-05 22:06:15 -0700371 options.list_error_boards,
Alex Kiernan4059e302018-05-31 04:48:34 +0000372 options.show_config,
373 options.show_environment)
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 Glassc2f91072014-08-28 09:43:39 -0600377 fail, warned = builder.BuildBoards(commits, board_selected,
Simon Glass78e418e2014-08-09 15:33:03 -0600378 options.keep_outputs, options.verbose)
Simon Glassc2f91072014-08-28 09:43:39 -0600379 if fail:
380 return 128
381 elif warned:
382 return 129
383 return 0