blob: 969d866547a4d775c365cadb6f0ef63f7009321e [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 Glassfc3fe1c2013-04-03 11:07:16 +0000266
Simon Glassfc3fe1c2013-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 Glass950a2312014-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 Glassfea58582014-08-09 15:32:59 -0600276 if options.branch:
Simon Glass3b74ba52014-08-09 15:33:09 -0600277 if count == -1:
Simon Glass5abab202014-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 Glass3b74ba52014-08-09 15:33:09 -0600283 upstream_commit = gitutil.GetUpstream(options.git_dir,
284 options.branch)
285 series = patchstream.GetMetaDataForList(upstream_commit,
Simon Glass950a2312014-09-05 19:00:23 -0600286 options.git_dir, 1, series=None, allow_overwrite=True)
Simon Glassfea58582014-08-09 15:32:59 -0600287
Simon Glass3b74ba52014-08-09 15:33:09 -0600288 series = patchstream.GetMetaDataForList(range_expr,
Simon Glass950a2312014-09-05 19:00:23 -0600289 options.git_dir, None, series, allow_overwrite=True)
Simon Glass3b74ba52014-08-09 15:33:09 -0600290 else:
291 # Honour the count
292 series = patchstream.GetMetaDataForList(options.branch,
Simon Glass950a2312014-09-05 19:00:23 -0600293 options.git_dir, count, series=None, allow_overwrite=True)
Simon Glassfea58582014-08-09 15:32:59 -0600294 else:
295 series = None
Simon Glass8d7523c2017-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 Glassfc3fe1c2013-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 Glassc05aa032019-10-31 07:42:53 -0600307 len(selected) - 1) // len(selected))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000308
309 if not options.step:
310 options.step = len(series.commits) - 1
311
Masahiro Yamada99796922014-07-22 11:19:09 +0900312 gnu_make = command.Output(os.path.join(options.git,
Simon Glass785f1542016-07-25 18:59:00 -0600313 'scripts/show-gnu-make'), raise_on_error=False).rstrip()
Masahiro Yamada99796922014-07-22 11:19:09 +0900314 if not gnu_make:
Masahiro Yamada31e21412014-08-16 00:59:26 +0900315 sys.exit('GNU Make not found')
Masahiro Yamada99796922014-07-22 11:19:09 +0900316
Simon Glass05c96b12014-12-01 17:33:52 -0700317 # Create a new builder with the selected options.
318 output_dir = options.output_dir
Simon Glassfea58582014-08-09 15:32:59 -0600319 if options.branch:
Simon Glassf7582ce2014-09-05 19:00:22 -0600320 dirname = options.branch.replace('/', '_')
Simon Glass5971ab52014-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ßmann409fc022018-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 Glassfc3fe1c2013-04-03 11:07:16 +0000328 builder = Builder(toolchains, output_dir, options.git_dir,
Masahiro Yamada99796922014-07-22 11:19:09 +0900329 options.threads, options.jobs, gnu_make=gnu_make, checkout=True,
Simon Glass5971ab52014-12-01 17:33:55 -0700330 show_unknown=options.show_unknown, step=options.step,
Simon Glassd2ce6582014-12-01 17:34:07 -0700331 no_subdirs=options.no_subdirs, full_path=options.full_path,
Stephen Warrenf79f1e02016-04-11 10:48:44 -0600332 verbose_build=options.verbose_build,
333 incremental=options.incremental,
Simon Glassb50113f2016-11-13 14:25:51 -0700334 per_board_out_dir=options.per_board_out_dir,
Simon Glassb464f8e2016-11-13 14:25:53 -0700335 config_only=options.config_only,
Daniel Schwierzeck2371d1b2018-01-26 16:31:05 +0100336 squash_config_y=not options.preserve_config_y,
337 warnings_as_errors=options.warnings_as_errors)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000338 builder.force_config_on_failure = not options.quick
Simon Glassd4144e42014-09-05 19:00:13 -0600339 if make_func:
340 builder.do_make = make_func
Simon Glassfc3fe1c2013-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 Glass06890362018-06-11 23:26:46 -0600344 ShowActions(series, why_selected, selected, builder, options,
345 board_warnings)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000346 else:
347 builder.force_build = options.force_build
Simon Glass4266dc22014-07-13 12:22:31 -0600348 builder.force_build_failures = options.force_build_failures
Simon Glass97e91522014-07-14 17:51:02 -0600349 builder.force_reconfig = options.force_reconfig
Simon Glass189a4962014-07-14 17:51:03 -0600350 builder.in_tree = options.in_tree
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000351
352 # Work out which boards to build
353 board_selected = boards.GetSelectedDict()
354
Simon Glassfea58582014-08-09 15:32:59 -0600355 if series:
356 commits = series.commits
Simon Glass883a3212014-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 Glassfea58582014-08-09 15:32:59 -0600360 else:
361 commits = None
362
Simon Glassd4144e42014-09-05 19:00:13 -0600363 Print(GetActionSummary(options.summary, commits, board_selected,
364 options))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000365
Simon Glass7798e222014-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 Glassb2ea7ab2014-08-09 15:33:02 -0600369 builder.SetDisplayOptions(options.show_errors, options.show_sizes,
Simon Glassed966652014-08-28 09:43:43 -0600370 options.show_detail, options.show_bloat,
Simon Glass843312d2015-02-05 22:06:15 -0700371 options.list_error_boards,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000372 options.show_config,
373 options.show_environment)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000374 if options.summary:
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600375 builder.ShowSummary(commits, board_selected)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000376 else:
Simon Glass2c3deb92014-08-28 09:43:39 -0600377 fail, warned = builder.BuildBoards(commits, board_selected,
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600378 options.keep_outputs, options.verbose)
Simon Glass2c3deb92014-08-28 09:43:39 -0600379 if fail:
380 return 128
381 elif warned:
382 return 129
383 return 0