blob: a9c5022e48fb5b52c2e8bc56ad9f411adb714991 [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 Glass883a3212014-09-05 19:00:18 -0600110def DoBuildman(options, args, toolchains=None, make_func=None, boards=None,
111 clean_dir=False):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000112 """The main control code for buildman
113
114 Args:
115 options: Command line options object
116 args: Command line arguments (list of strings)
Simon Glassd4144e42014-09-05 19:00:13 -0600117 toolchains: Toolchains to use - this should be a Toolchains()
118 object. If None, then it will be created and scanned
119 make_func: Make function to use for the builder. This is called
120 to execute 'make'. If this is None, the normal function
121 will be used, which calls the 'make' tool with suitable
122 arguments. This setting is useful for tests.
Simon Glass823e60b2014-09-05 19:00:16 -0600123 board: Boards() object to use, containing a list of available
124 boards. If this is None it will be created and scanned.
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000125 """
Simon Glass883a3212014-09-05 19:00:18 -0600126 global builder
127
Simon Glass48ba5852014-09-05 19:00:11 -0600128 if options.full_help:
129 pager = os.getenv('PAGER')
130 if not pager:
131 pager = 'more'
Simon Glass2bdeade2016-03-06 19:45:34 -0700132 fname = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])),
133 'README')
Simon Glass48ba5852014-09-05 19:00:11 -0600134 command.Run(pager, fname)
135 return 0
136
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000137 gitutil.Setup()
Simon Glass713bea32016-07-27 20:33:02 -0600138 col = terminal.Color()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000139
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000140 options.git_dir = os.path.join(options.git, '.git')
141
Simon Glass7e92e462016-07-27 20:33:04 -0600142 no_toolchains = toolchains is None
143 if no_toolchains:
Simon Glass00beb242019-01-07 16:44:20 -0700144 toolchains = toolchain.Toolchains(options.override_toolchain)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000145
Simon Glass827e37b2014-12-01 17:34:06 -0700146 if options.fetch_arch:
147 if options.fetch_arch == 'list':
148 sorted_list = toolchains.ListArchs()
Simon Glassc05aa032019-10-31 07:42:53 -0600149 print(col.Color(col.BLUE, 'Available architectures: %s\n' %
150 ' '.join(sorted_list)))
Simon Glass827e37b2014-12-01 17:34:06 -0700151 return 0
152 else:
153 fetch_arch = options.fetch_arch
154 if fetch_arch == 'all':
155 fetch_arch = ','.join(toolchains.ListArchs())
Simon Glassc05aa032019-10-31 07:42:53 -0600156 print(col.Color(col.CYAN, '\nDownloading toolchains: %s' %
157 fetch_arch))
Simon Glass827e37b2014-12-01 17:34:06 -0700158 for arch in fetch_arch.split(','):
Simon Glassc05aa032019-10-31 07:42:53 -0600159 print()
Simon Glass827e37b2014-12-01 17:34:06 -0700160 ret = toolchains.FetchAndInstall(arch)
161 if ret:
162 return ret
163 return 0
164
Simon Glass7e92e462016-07-27 20:33:04 -0600165 if no_toolchains:
166 toolchains.GetSettings()
Simon Glass40232c92018-11-06 16:02:10 -0700167 toolchains.Scan(options.list_tool_chains and options.verbose)
Simon Glass7e92e462016-07-27 20:33:04 -0600168 if options.list_tool_chains:
169 toolchains.List()
Simon Glassc05aa032019-10-31 07:42:53 -0600170 print()
Simon Glass7e92e462016-07-27 20:33:04 -0600171 return 0
172
Simon Glass7c66ead2019-12-05 15:59:13 -0700173 # Work out what subset of the boards we are building
174 if not boards:
175 if not os.path.exists(options.output_dir):
176 os.makedirs(options.output_dir)
177 board_file = os.path.join(options.output_dir, 'boards.cfg')
178 genboardscfg = os.path.join(options.git, 'tools/genboardscfg.py')
179 status = subprocess.call([genboardscfg, '-q', '-o', board_file])
180 if status != 0:
181 sys.exit("Failed to generate boards.cfg")
182
183 boards = board.Boards()
184 boards.ReadBoards(board_file)
185
186 exclude = []
187 if options.exclude:
188 for arg in options.exclude:
189 exclude += arg.split(',')
190
191 if options.boards:
192 requested_boards = []
193 for b in options.boards:
194 requested_boards += b.split(',')
195 else:
196 requested_boards = None
197 why_selected, board_warnings = boards.SelectBoards(args, exclude,
198 requested_boards)
199 selected = boards.GetSelected()
200 if not len(selected):
201 sys.exit(col.Color(col.RED, 'No matching boards found'))
202
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000203 # Work out how many commits to build. We want to build everything on the
204 # branch. We also build the upstream commit as a control so we can see
205 # problems introduced by the first commit on the branch.
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000206 count = options.count
Simon Glass5abab202014-12-01 17:33:57 -0700207 has_range = options.branch and '..' in options.branch
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000208 if count == -1:
209 if not options.branch:
Simon Glassfea58582014-08-09 15:32:59 -0600210 count = 1
211 else:
Simon Glass5abab202014-12-01 17:33:57 -0700212 if has_range:
213 count, msg = gitutil.CountCommitsInRange(options.git_dir,
214 options.branch)
215 else:
216 count, msg = gitutil.CountCommitsInBranch(options.git_dir,
217 options.branch)
Simon Glassfea58582014-08-09 15:32:59 -0600218 if count is None:
Simon Glass2a9e2c62014-12-01 17:33:54 -0700219 sys.exit(col.Color(col.RED, msg))
Simon Glass5abab202014-12-01 17:33:57 -0700220 elif count == 0:
221 sys.exit(col.Color(col.RED, "Range '%s' has no commits" %
222 options.branch))
Simon Glass2a9e2c62014-12-01 17:33:54 -0700223 if msg:
Simon Glassc05aa032019-10-31 07:42:53 -0600224 print(col.Color(col.YELLOW, msg))
Simon Glassfea58582014-08-09 15:32:59 -0600225 count += 1 # Build upstream commit also
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000226
227 if not count:
228 str = ("No commits found to process in branch '%s': "
229 "set branch's upstream or use -c flag" % options.branch)
Masahiro Yamada31e21412014-08-16 00:59:26 +0900230 sys.exit(col.Color(col.RED, str))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000231
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000232 # Read the metadata from the commits. First look at the upstream commit,
233 # then the ones in the branch. We would like to do something like
234 # upstream/master~..branch but that isn't possible if upstream/master is
235 # a merge commit (it will list all the commits that form part of the
236 # merge)
Simon Glass950a2312014-09-05 19:00:23 -0600237 # Conflicting tags are not a problem for buildman, since it does not use
238 # them. For example, Series-version is not useful for buildman. On the
239 # other hand conflicting tags will cause an error. So allow later tags
240 # to overwrite earlier ones by setting allow_overwrite=True
Simon Glassfea58582014-08-09 15:32:59 -0600241 if options.branch:
Simon Glass3b74ba52014-08-09 15:33:09 -0600242 if count == -1:
Simon Glass5abab202014-12-01 17:33:57 -0700243 if has_range:
244 range_expr = options.branch
245 else:
246 range_expr = gitutil.GetRangeInBranch(options.git_dir,
247 options.branch)
Simon Glass3b74ba52014-08-09 15:33:09 -0600248 upstream_commit = gitutil.GetUpstream(options.git_dir,
249 options.branch)
250 series = patchstream.GetMetaDataForList(upstream_commit,
Simon Glass950a2312014-09-05 19:00:23 -0600251 options.git_dir, 1, series=None, allow_overwrite=True)
Simon Glassfea58582014-08-09 15:32:59 -0600252
Simon Glass3b74ba52014-08-09 15:33:09 -0600253 series = patchstream.GetMetaDataForList(range_expr,
Simon Glass950a2312014-09-05 19:00:23 -0600254 options.git_dir, None, series, allow_overwrite=True)
Simon Glass3b74ba52014-08-09 15:33:09 -0600255 else:
256 # Honour the count
257 series = patchstream.GetMetaDataForList(options.branch,
Simon Glass950a2312014-09-05 19:00:23 -0600258 options.git_dir, count, series=None, allow_overwrite=True)
Simon Glassfea58582014-08-09 15:32:59 -0600259 else:
260 series = None
Simon Glass8d7523c2017-01-23 05:38:56 -0700261 if not options.dry_run:
262 options.verbose = True
263 if not options.summary:
264 options.show_errors = True
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000265
266 # By default we have one thread per CPU. But if there are not enough jobs
267 # we can have fewer threads and use a high '-j' value for make.
268 if not options.threads:
269 options.threads = min(multiprocessing.cpu_count(), len(selected))
270 if not options.jobs:
271 options.jobs = max(1, (multiprocessing.cpu_count() +
Simon Glassc05aa032019-10-31 07:42:53 -0600272 len(selected) - 1) // len(selected))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000273
274 if not options.step:
275 options.step = len(series.commits) - 1
276
Masahiro Yamada99796922014-07-22 11:19:09 +0900277 gnu_make = command.Output(os.path.join(options.git,
Simon Glass785f1542016-07-25 18:59:00 -0600278 'scripts/show-gnu-make'), raise_on_error=False).rstrip()
Masahiro Yamada99796922014-07-22 11:19:09 +0900279 if not gnu_make:
Masahiro Yamada31e21412014-08-16 00:59:26 +0900280 sys.exit('GNU Make not found')
Masahiro Yamada99796922014-07-22 11:19:09 +0900281
Simon Glass05c96b12014-12-01 17:33:52 -0700282 # Create a new builder with the selected options.
283 output_dir = options.output_dir
Simon Glassfea58582014-08-09 15:32:59 -0600284 if options.branch:
Simon Glassf7582ce2014-09-05 19:00:22 -0600285 dirname = options.branch.replace('/', '_')
Simon Glass5971ab52014-12-01 17:33:55 -0700286 # As a special case allow the board directory to be placed in the
287 # output directory itself rather than any subdirectory.
288 if not options.no_subdirs:
289 output_dir = os.path.join(options.output_dir, dirname)
Lothar Waßmann409fc022018-04-08 05:14:11 -0600290 if clean_dir and os.path.exists(output_dir):
291 shutil.rmtree(output_dir)
292 CheckOutputDir(output_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000293 builder = Builder(toolchains, output_dir, options.git_dir,
Masahiro Yamada99796922014-07-22 11:19:09 +0900294 options.threads, options.jobs, gnu_make=gnu_make, checkout=True,
Simon Glass5971ab52014-12-01 17:33:55 -0700295 show_unknown=options.show_unknown, step=options.step,
Simon Glassd2ce6582014-12-01 17:34:07 -0700296 no_subdirs=options.no_subdirs, full_path=options.full_path,
Stephen Warrenf79f1e02016-04-11 10:48:44 -0600297 verbose_build=options.verbose_build,
298 incremental=options.incremental,
Simon Glassb50113f2016-11-13 14:25:51 -0700299 per_board_out_dir=options.per_board_out_dir,
Simon Glassb464f8e2016-11-13 14:25:53 -0700300 config_only=options.config_only,
Daniel Schwierzeck2371d1b2018-01-26 16:31:05 +0100301 squash_config_y=not options.preserve_config_y,
302 warnings_as_errors=options.warnings_as_errors)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000303 builder.force_config_on_failure = not options.quick
Simon Glassd4144e42014-09-05 19:00:13 -0600304 if make_func:
305 builder.do_make = make_func
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000306
307 # For a dry run, just show our actions as a sanity check
308 if options.dry_run:
Simon Glass06890362018-06-11 23:26:46 -0600309 ShowActions(series, why_selected, selected, builder, options,
310 board_warnings)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000311 else:
312 builder.force_build = options.force_build
Simon Glass4266dc22014-07-13 12:22:31 -0600313 builder.force_build_failures = options.force_build_failures
Simon Glass97e91522014-07-14 17:51:02 -0600314 builder.force_reconfig = options.force_reconfig
Simon Glass189a4962014-07-14 17:51:03 -0600315 builder.in_tree = options.in_tree
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000316
317 # Work out which boards to build
318 board_selected = boards.GetSelectedDict()
319
Simon Glassfea58582014-08-09 15:32:59 -0600320 if series:
321 commits = series.commits
Simon Glass883a3212014-09-05 19:00:18 -0600322 # Number the commits for test purposes
323 for commit in range(len(commits)):
324 commits[commit].sequence = commit
Simon Glassfea58582014-08-09 15:32:59 -0600325 else:
326 commits = None
327
Simon Glassd4144e42014-09-05 19:00:13 -0600328 Print(GetActionSummary(options.summary, commits, board_selected,
329 options))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000330
Simon Glass7798e222014-09-14 20:23:16 -0600331 # We can't show function sizes without board details at present
332 if options.show_bloat:
333 options.show_detail = True
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600334 builder.SetDisplayOptions(options.show_errors, options.show_sizes,
Simon Glassed966652014-08-28 09:43:43 -0600335 options.show_detail, options.show_bloat,
Simon Glass843312d2015-02-05 22:06:15 -0700336 options.list_error_boards,
Alex Kiernan48ae4122018-05-31 04:48:34 +0000337 options.show_config,
338 options.show_environment)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000339 if options.summary:
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600340 builder.ShowSummary(commits, board_selected)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000341 else:
Simon Glass2c3deb92014-08-28 09:43:39 -0600342 fail, warned = builder.BuildBoards(commits, board_selected,
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600343 options.keep_outputs, options.verbose)
Simon Glass2c3deb92014-08-28 09:43:39 -0600344 if fail:
345 return 128
346 elif warned:
347 return 129
348 return 0