blob: bc0819784f86147e2cecb2f3be474ee1ad8d3400 [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)
33 count = (count + options.step - 1) / options.step
34 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
44def ShowActions(series, why_selected, boards_selected, builder, options):
45 """Display a list of actions that we would take, if not a dry run.
46
47 Args:
48 series: Series object
49 why_selected: Dictionary where each key is a buildman argument
Simon Glass6af145f2017-01-23 05:38:56 -070050 provided by the user, and the value is the list of boards
51 brought in by that argument. For example, 'arm' might bring
52 in 400 boards, so in this case the key would be 'arm' and
Simon Glassc05694f2013-04-03 11:07:16 +000053 the value would be a list of board names.
54 boards_selected: Dict of selected boards, key is target name,
55 value is Board object
56 builder: The builder that will be used to build the commits
57 options: Command line options object
58 """
59 col = terminal.Color()
60 print 'Dry run, so not doing much. But I would do this:'
61 print
Simon Glassd326ad72014-08-09 15:32:59 -060062 if series:
63 commits = series.commits
64 else:
65 commits = None
66 print GetActionSummary(False, commits, boards_selected,
Simon Glassc05694f2013-04-03 11:07:16 +000067 options)
68 print 'Build directory: %s' % builder.base_dir
Simon Glassd326ad72014-08-09 15:32:59 -060069 if commits:
70 for upto in range(0, len(series.commits), options.step):
71 commit = series.commits[upto]
Simon Glasse2fb4192014-10-15 02:27:00 -060072 print ' ', col.Color(col.YELLOW, commit.hash[:8], bright=False),
Simon Glassd326ad72014-08-09 15:32:59 -060073 print commit.subject
Simon Glassc05694f2013-04-03 11:07:16 +000074 print
75 for arg in why_selected:
76 if arg != 'all':
Simon Glass6af145f2017-01-23 05:38:56 -070077 print arg, ': %d boards' % len(why_selected[arg])
78 if options.verbose:
79 print ' %s' % ' '.join(why_selected[arg])
Simon Glassc05694f2013-04-03 11:07:16 +000080 print ('Total boards to build for each commit: %d\n' %
Simon Glass6af145f2017-01-23 05:38:56 -070081 len(why_selected['all']))
Simon Glassc05694f2013-04-03 11:07:16 +000082
Lothar Waßmannce6df922018-04-08 05:14:11 -060083def CheckOutputDir(output_dir):
84 """Make sure that the output directory is not within the current directory
85
86 If we try to use an output directory which is within the current directory
87 (which is assumed to hold the U-Boot source) we may end up deleting the
88 U-Boot source code. Detect this and print an error in this case.
89
90 Args:
91 output_dir: Output directory path to check
92 """
93 path = os.path.realpath(output_dir)
94 cwd_path = os.path.realpath('.')
95 while True:
96 if os.path.realpath(path) == cwd_path:
97 Print("Cannot use output directory '%s' since it is within the current directtory '%s'" %
98 (path, cwd_path))
99 sys.exit(1)
100 parent = os.path.dirname(path)
101 if parent == path:
102 break
103 path = parent
104
Simon Glassa10ebe12014-09-05 19:00:18 -0600105def DoBuildman(options, args, toolchains=None, make_func=None, boards=None,
106 clean_dir=False):
Simon Glassc05694f2013-04-03 11:07:16 +0000107 """The main control code for buildman
108
109 Args:
110 options: Command line options object
111 args: Command line arguments (list of strings)
Simon Glassed098bb2014-09-05 19:00:13 -0600112 toolchains: Toolchains to use - this should be a Toolchains()
113 object. If None, then it will be created and scanned
114 make_func: Make function to use for the builder. This is called
115 to execute 'make'. If this is None, the normal function
116 will be used, which calls the 'make' tool with suitable
117 arguments. This setting is useful for tests.
Simon Glasscbd36582014-09-05 19:00:16 -0600118 board: Boards() object to use, containing a list of available
119 boards. If this is None it will be created and scanned.
Simon Glassc05694f2013-04-03 11:07:16 +0000120 """
Simon Glassa10ebe12014-09-05 19:00:18 -0600121 global builder
122
Simon Glassca9b06e2014-09-05 19:00:11 -0600123 if options.full_help:
124 pager = os.getenv('PAGER')
125 if not pager:
126 pager = 'more'
Simon Glassc29101f2016-03-06 19:45:34 -0700127 fname = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])),
128 'README')
Simon Glassca9b06e2014-09-05 19:00:11 -0600129 command.Run(pager, fname)
130 return 0
131
Simon Glassc05694f2013-04-03 11:07:16 +0000132 gitutil.Setup()
Simon Glass9f1ba0f2016-07-27 20:33:02 -0600133 col = terminal.Color()
Simon Glassc05694f2013-04-03 11:07:16 +0000134
Simon Glassc05694f2013-04-03 11:07:16 +0000135 options.git_dir = os.path.join(options.git, '.git')
136
Simon Glassa3d9b4f2016-07-27 20:33:04 -0600137 no_toolchains = toolchains is None
138 if no_toolchains:
Simon Glassed098bb2014-09-05 19:00:13 -0600139 toolchains = toolchain.Toolchains()
Simon Glassc05694f2013-04-03 11:07:16 +0000140
Simon Glass7e803e12014-12-01 17:34:06 -0700141 if options.fetch_arch:
142 if options.fetch_arch == 'list':
143 sorted_list = toolchains.ListArchs()
Simon Glass9f1ba0f2016-07-27 20:33:02 -0600144 print col.Color(col.BLUE, 'Available architectures: %s\n' %
145 ' '.join(sorted_list))
Simon Glass7e803e12014-12-01 17:34:06 -0700146 return 0
147 else:
148 fetch_arch = options.fetch_arch
149 if fetch_arch == 'all':
150 fetch_arch = ','.join(toolchains.ListArchs())
Simon Glass9f1ba0f2016-07-27 20:33:02 -0600151 print col.Color(col.CYAN, '\nDownloading toolchains: %s' %
152 fetch_arch)
Simon Glass7e803e12014-12-01 17:34:06 -0700153 for arch in fetch_arch.split(','):
Simon Glass9f1ba0f2016-07-27 20:33:02 -0600154 print
Simon Glass7e803e12014-12-01 17:34:06 -0700155 ret = toolchains.FetchAndInstall(arch)
156 if ret:
157 return ret
158 return 0
159
Simon Glassa3d9b4f2016-07-27 20:33:04 -0600160 if no_toolchains:
161 toolchains.GetSettings()
162 toolchains.Scan(options.list_tool_chains)
163 if options.list_tool_chains:
164 toolchains.List()
165 print
166 return 0
167
Simon Glassc05694f2013-04-03 11:07:16 +0000168 # Work out how many commits to build. We want to build everything on the
169 # branch. We also build the upstream commit as a control so we can see
170 # problems introduced by the first commit on the branch.
Simon Glassc05694f2013-04-03 11:07:16 +0000171 count = options.count
Simon Glass5eeef462014-12-01 17:33:57 -0700172 has_range = options.branch and '..' in options.branch
Simon Glassc05694f2013-04-03 11:07:16 +0000173 if count == -1:
174 if not options.branch:
Simon Glassd326ad72014-08-09 15:32:59 -0600175 count = 1
176 else:
Simon Glass5eeef462014-12-01 17:33:57 -0700177 if has_range:
178 count, msg = gitutil.CountCommitsInRange(options.git_dir,
179 options.branch)
180 else:
181 count, msg = gitutil.CountCommitsInBranch(options.git_dir,
182 options.branch)
Simon Glassd326ad72014-08-09 15:32:59 -0600183 if count is None:
Simon Glassf204ab12014-12-01 17:33:54 -0700184 sys.exit(col.Color(col.RED, msg))
Simon Glass5eeef462014-12-01 17:33:57 -0700185 elif count == 0:
186 sys.exit(col.Color(col.RED, "Range '%s' has no commits" %
187 options.branch))
Simon Glassf204ab12014-12-01 17:33:54 -0700188 if msg:
189 print col.Color(col.YELLOW, msg)
Simon Glassd326ad72014-08-09 15:32:59 -0600190 count += 1 # Build upstream commit also
Simon Glassc05694f2013-04-03 11:07:16 +0000191
192 if not count:
193 str = ("No commits found to process in branch '%s': "
194 "set branch's upstream or use -c flag" % options.branch)
Masahiro Yamada880828d2014-08-16 00:59:26 +0900195 sys.exit(col.Color(col.RED, str))
Simon Glassc05694f2013-04-03 11:07:16 +0000196
197 # Work out what subset of the boards we are building
Simon Glasscbd36582014-09-05 19:00:16 -0600198 if not boards:
199 board_file = os.path.join(options.git, 'boards.cfg')
200 status = subprocess.call([os.path.join(options.git,
201 'tools/genboardscfg.py')])
202 if status != 0:
203 sys.exit("Failed to generate boards.cfg")
Masahiro Yamadae9bc8d22014-07-30 14:08:22 +0900204
Simon Glasscbd36582014-09-05 19:00:16 -0600205 boards = board.Boards()
206 boards.ReadBoards(os.path.join(options.git, 'boards.cfg'))
Simon Glass924c73a2014-08-28 09:43:41 -0600207
208 exclude = []
209 if options.exclude:
210 for arg in options.exclude:
211 exclude += arg.split(',')
212
213 why_selected = boards.SelectBoards(args, exclude)
Simon Glassc05694f2013-04-03 11:07:16 +0000214 selected = boards.GetSelected()
215 if not len(selected):
Masahiro Yamada880828d2014-08-16 00:59:26 +0900216 sys.exit(col.Color(col.RED, 'No matching boards found'))
Simon Glassc05694f2013-04-03 11:07:16 +0000217
218 # Read the metadata from the commits. First look at the upstream commit,
219 # then the ones in the branch. We would like to do something like
220 # upstream/master~..branch but that isn't possible if upstream/master is
221 # a merge commit (it will list all the commits that form part of the
222 # merge)
Simon Glass359b55a62014-09-05 19:00:23 -0600223 # Conflicting tags are not a problem for buildman, since it does not use
224 # them. For example, Series-version is not useful for buildman. On the
225 # other hand conflicting tags will cause an error. So allow later tags
226 # to overwrite earlier ones by setting allow_overwrite=True
Simon Glassd326ad72014-08-09 15:32:59 -0600227 if options.branch:
Simon Glass16a52882014-08-09 15:33:09 -0600228 if count == -1:
Simon Glass5eeef462014-12-01 17:33:57 -0700229 if has_range:
230 range_expr = options.branch
231 else:
232 range_expr = gitutil.GetRangeInBranch(options.git_dir,
233 options.branch)
Simon Glass16a52882014-08-09 15:33:09 -0600234 upstream_commit = gitutil.GetUpstream(options.git_dir,
235 options.branch)
236 series = patchstream.GetMetaDataForList(upstream_commit,
Simon Glass359b55a62014-09-05 19:00:23 -0600237 options.git_dir, 1, series=None, allow_overwrite=True)
Simon Glassd326ad72014-08-09 15:32:59 -0600238
Simon Glass16a52882014-08-09 15:33:09 -0600239 series = patchstream.GetMetaDataForList(range_expr,
Simon Glass359b55a62014-09-05 19:00:23 -0600240 options.git_dir, None, series, allow_overwrite=True)
Simon Glass16a52882014-08-09 15:33:09 -0600241 else:
242 # Honour the count
243 series = patchstream.GetMetaDataForList(options.branch,
Simon Glass359b55a62014-09-05 19:00:23 -0600244 options.git_dir, count, series=None, allow_overwrite=True)
Simon Glassd326ad72014-08-09 15:32:59 -0600245 else:
246 series = None
Simon Glass6af145f2017-01-23 05:38:56 -0700247 if not options.dry_run:
248 options.verbose = True
249 if not options.summary:
250 options.show_errors = True
Simon Glassc05694f2013-04-03 11:07:16 +0000251
252 # By default we have one thread per CPU. But if there are not enough jobs
253 # we can have fewer threads and use a high '-j' value for make.
254 if not options.threads:
255 options.threads = min(multiprocessing.cpu_count(), len(selected))
256 if not options.jobs:
257 options.jobs = max(1, (multiprocessing.cpu_count() +
258 len(selected) - 1) / len(selected))
259
260 if not options.step:
261 options.step = len(series.commits) - 1
262
Masahiro Yamada1fe610d2014-07-22 11:19:09 +0900263 gnu_make = command.Output(os.path.join(options.git,
Simon Glassc55e0562016-07-25 18:59:00 -0600264 'scripts/show-gnu-make'), raise_on_error=False).rstrip()
Masahiro Yamada1fe610d2014-07-22 11:19:09 +0900265 if not gnu_make:
Masahiro Yamada880828d2014-08-16 00:59:26 +0900266 sys.exit('GNU Make not found')
Masahiro Yamada1fe610d2014-07-22 11:19:09 +0900267
Simon Glassdbc01c72014-12-01 17:33:52 -0700268 # Create a new builder with the selected options.
269 output_dir = options.output_dir
Simon Glassd326ad72014-08-09 15:32:59 -0600270 if options.branch:
Simon Glass4aeceb92014-09-05 19:00:22 -0600271 dirname = options.branch.replace('/', '_')
Simon Glasse87bde12014-12-01 17:33:55 -0700272 # As a special case allow the board directory to be placed in the
273 # output directory itself rather than any subdirectory.
274 if not options.no_subdirs:
275 output_dir = os.path.join(options.output_dir, dirname)
Lothar Waßmannce6df922018-04-08 05:14:11 -0600276 if clean_dir and os.path.exists(output_dir):
277 shutil.rmtree(output_dir)
278 CheckOutputDir(output_dir)
Simon Glassc05694f2013-04-03 11:07:16 +0000279 builder = Builder(toolchains, output_dir, options.git_dir,
Masahiro Yamada1fe610d2014-07-22 11:19:09 +0900280 options.threads, options.jobs, gnu_make=gnu_make, checkout=True,
Simon Glasse87bde12014-12-01 17:33:55 -0700281 show_unknown=options.show_unknown, step=options.step,
Simon Glass655b6102014-12-01 17:34:07 -0700282 no_subdirs=options.no_subdirs, full_path=options.full_path,
Stephen Warren97c96902016-04-11 10:48:44 -0600283 verbose_build=options.verbose_build,
284 incremental=options.incremental,
Simon Glass739e8512016-11-13 14:25:51 -0700285 per_board_out_dir=options.per_board_out_dir,
Simon Glasscde5c302016-11-13 14:25:53 -0700286 config_only=options.config_only,
Daniel Schwierzeck20e2ea92018-01-26 16:31:05 +0100287 squash_config_y=not options.preserve_config_y,
288 warnings_as_errors=options.warnings_as_errors)
Simon Glassc05694f2013-04-03 11:07:16 +0000289 builder.force_config_on_failure = not options.quick
Simon Glassed098bb2014-09-05 19:00:13 -0600290 if make_func:
291 builder.do_make = make_func
Simon Glassc05694f2013-04-03 11:07:16 +0000292
293 # For a dry run, just show our actions as a sanity check
294 if options.dry_run:
295 ShowActions(series, why_selected, selected, builder, options)
296 else:
297 builder.force_build = options.force_build
Simon Glass7041c392014-07-13 12:22:31 -0600298 builder.force_build_failures = options.force_build_failures
Simon Glassf3018b7a2014-07-14 17:51:02 -0600299 builder.force_reconfig = options.force_reconfig
Simon Glass38df2e22014-07-14 17:51:03 -0600300 builder.in_tree = options.in_tree
Simon Glassc05694f2013-04-03 11:07:16 +0000301
302 # Work out which boards to build
303 board_selected = boards.GetSelectedDict()
304
Simon Glassd326ad72014-08-09 15:32:59 -0600305 if series:
306 commits = series.commits
Simon Glassa10ebe12014-09-05 19:00:18 -0600307 # Number the commits for test purposes
308 for commit in range(len(commits)):
309 commits[commit].sequence = commit
Simon Glassd326ad72014-08-09 15:32:59 -0600310 else:
311 commits = None
312
Simon Glassed098bb2014-09-05 19:00:13 -0600313 Print(GetActionSummary(options.summary, commits, board_selected,
314 options))
Simon Glassc05694f2013-04-03 11:07:16 +0000315
Simon Glass232d8502014-09-14 20:23:16 -0600316 # We can't show function sizes without board details at present
317 if options.show_bloat:
318 options.show_detail = True
Simon Glasseb48bbc2014-08-09 15:33:02 -0600319 builder.SetDisplayOptions(options.show_errors, options.show_sizes,
Simon Glass3394c9f2014-08-28 09:43:43 -0600320 options.show_detail, options.show_bloat,
Simon Glassdb17fb82015-02-05 22:06:15 -0700321 options.list_error_boards,
Alex Kiernan4059e302018-05-31 04:48:34 +0000322 options.show_config,
323 options.show_environment)
Simon Glassc05694f2013-04-03 11:07:16 +0000324 if options.summary:
Simon Glasseb48bbc2014-08-09 15:33:02 -0600325 builder.ShowSummary(commits, board_selected)
Simon Glassc05694f2013-04-03 11:07:16 +0000326 else:
Simon Glassc2f91072014-08-28 09:43:39 -0600327 fail, warned = builder.BuildBoards(commits, board_selected,
Simon Glass78e418e2014-08-09 15:33:03 -0600328 options.keep_outputs, options.verbose)
Simon Glassc2f91072014-08-28 09:43:39 -0600329 if fail:
330 return 128
331 elif warned:
332 return 129
333 return 0