blob: 73b1a14fb6bb8428206ffc4a9a8b8f4e0926b8ec [file] [log] [blame]
Simon Glassc05694f2013-04-03 11:07:16 +00001# Copyright (c) 2013 The Chromium OS Authors.
2#
Wolfgang Denkd79de1d2013-07-08 09:37:19 +02003# SPDX-License-Identifier: GPL-2.0+
Simon Glassc05694f2013-04-03 11:07:16 +00004#
5
6import multiprocessing
7import os
Simon Glassa10ebe12014-09-05 19:00:18 -06008import shutil
Simon Glassc05694f2013-04-03 11:07:16 +00009import sys
10
11import board
12import bsettings
13from builder import Builder
14import gitutil
15import patchstream
16import terminal
Simon Glassed098bb2014-09-05 19:00:13 -060017from terminal import Print
Simon Glassc05694f2013-04-03 11:07:16 +000018import toolchain
Masahiro Yamada1fe610d2014-07-22 11:19:09 +090019import command
Masahiro Yamadae9bc8d22014-07-30 14:08:22 +090020import subprocess
Simon Glassc05694f2013-04-03 11:07:16 +000021
22def GetPlural(count):
23 """Returns a plural 's' if count is not 1"""
24 return 's' if count != 1 else ''
25
Simon Glassd326ad72014-08-09 15:32:59 -060026def GetActionSummary(is_summary, commits, selected, options):
Simon Glassc05694f2013-04-03 11:07:16 +000027 """Return a string summarising the intended action.
28
29 Returns:
30 Summary string.
31 """
Simon Glassd326ad72014-08-09 15:32:59 -060032 if commits:
33 count = len(commits)
34 count = (count + options.step - 1) / options.step
35 commit_str = '%d commit%s' % (count, GetPlural(count))
36 else:
37 commit_str = 'current source'
38 str = '%s %s for %d boards' % (
39 'Summary of' if is_summary else 'Building', commit_str,
Simon Glassc05694f2013-04-03 11:07:16 +000040 len(selected))
41 str += ' (%d thread%s, %d job%s per thread)' % (options.threads,
42 GetPlural(options.threads), options.jobs, GetPlural(options.jobs))
43 return str
44
45def ShowActions(series, why_selected, boards_selected, builder, options):
46 """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
59 """
60 col = terminal.Color()
61 print 'Dry run, so not doing much. But I would do this:'
62 print
Simon Glassd326ad72014-08-09 15:32:59 -060063 if series:
64 commits = series.commits
65 else:
66 commits = None
67 print GetActionSummary(False, commits, boards_selected,
Simon Glassc05694f2013-04-03 11:07:16 +000068 options)
69 print 'Build directory: %s' % builder.base_dir
Simon Glassd326ad72014-08-09 15:32:59 -060070 if commits:
71 for upto in range(0, len(series.commits), options.step):
72 commit = series.commits[upto]
Simon Glasse2fb4192014-10-15 02:27:00 -060073 print ' ', col.Color(col.YELLOW, commit.hash[:8], bright=False),
Simon Glassd326ad72014-08-09 15:32:59 -060074 print commit.subject
Simon Glassc05694f2013-04-03 11:07:16 +000075 print
76 for arg in why_selected:
77 if arg != 'all':
Simon Glass6af145f2017-01-23 05:38:56 -070078 print arg, ': %d boards' % len(why_selected[arg])
79 if options.verbose:
80 print ' %s' % ' '.join(why_selected[arg])
Simon Glassc05694f2013-04-03 11:07:16 +000081 print ('Total boards to build for each commit: %d\n' %
Simon Glass6af145f2017-01-23 05:38:56 -070082 len(why_selected['all']))
Simon Glassc05694f2013-04-03 11:07:16 +000083
Simon Glassa10ebe12014-09-05 19:00:18 -060084def DoBuildman(options, args, toolchains=None, make_func=None, boards=None,
85 clean_dir=False):
Simon Glassc05694f2013-04-03 11:07:16 +000086 """The main control code for buildman
87
88 Args:
89 options: Command line options object
90 args: Command line arguments (list of strings)
Simon Glassed098bb2014-09-05 19:00:13 -060091 toolchains: Toolchains to use - this should be a Toolchains()
92 object. If None, then it will be created and scanned
93 make_func: Make function to use for the builder. This is called
94 to execute 'make'. If this is None, the normal function
95 will be used, which calls the 'make' tool with suitable
96 arguments. This setting is useful for tests.
Simon Glasscbd36582014-09-05 19:00:16 -060097 board: Boards() object to use, containing a list of available
98 boards. If this is None it will be created and scanned.
Simon Glassc05694f2013-04-03 11:07:16 +000099 """
Simon Glassa10ebe12014-09-05 19:00:18 -0600100 global builder
101
Simon Glassca9b06e2014-09-05 19:00:11 -0600102 if options.full_help:
103 pager = os.getenv('PAGER')
104 if not pager:
105 pager = 'more'
Simon Glassc29101f2016-03-06 19:45:34 -0700106 fname = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])),
107 'README')
Simon Glassca9b06e2014-09-05 19:00:11 -0600108 command.Run(pager, fname)
109 return 0
110
Simon Glassc05694f2013-04-03 11:07:16 +0000111 gitutil.Setup()
Simon Glass9f1ba0f2016-07-27 20:33:02 -0600112 col = terminal.Color()
Simon Glassc05694f2013-04-03 11:07:16 +0000113
Simon Glassc05694f2013-04-03 11:07:16 +0000114 options.git_dir = os.path.join(options.git, '.git')
115
Simon Glassa3d9b4f2016-07-27 20:33:04 -0600116 no_toolchains = toolchains is None
117 if no_toolchains:
Simon Glassed098bb2014-09-05 19:00:13 -0600118 toolchains = toolchain.Toolchains()
Simon Glassc05694f2013-04-03 11:07:16 +0000119
Simon Glass7e803e12014-12-01 17:34:06 -0700120 if options.fetch_arch:
121 if options.fetch_arch == 'list':
122 sorted_list = toolchains.ListArchs()
Simon Glass9f1ba0f2016-07-27 20:33:02 -0600123 print col.Color(col.BLUE, 'Available architectures: %s\n' %
124 ' '.join(sorted_list))
Simon Glass7e803e12014-12-01 17:34:06 -0700125 return 0
126 else:
127 fetch_arch = options.fetch_arch
128 if fetch_arch == 'all':
129 fetch_arch = ','.join(toolchains.ListArchs())
Simon Glass9f1ba0f2016-07-27 20:33:02 -0600130 print col.Color(col.CYAN, '\nDownloading toolchains: %s' %
131 fetch_arch)
Simon Glass7e803e12014-12-01 17:34:06 -0700132 for arch in fetch_arch.split(','):
Simon Glass9f1ba0f2016-07-27 20:33:02 -0600133 print
Simon Glass7e803e12014-12-01 17:34:06 -0700134 ret = toolchains.FetchAndInstall(arch)
135 if ret:
136 return ret
137 return 0
138
Simon Glassa3d9b4f2016-07-27 20:33:04 -0600139 if no_toolchains:
140 toolchains.GetSettings()
141 toolchains.Scan(options.list_tool_chains)
142 if options.list_tool_chains:
143 toolchains.List()
144 print
145 return 0
146
Simon Glassc05694f2013-04-03 11:07:16 +0000147 # Work out how many commits to build. We want to build everything on the
148 # branch. We also build the upstream commit as a control so we can see
149 # problems introduced by the first commit on the branch.
Simon Glassc05694f2013-04-03 11:07:16 +0000150 count = options.count
Simon Glass5eeef462014-12-01 17:33:57 -0700151 has_range = options.branch and '..' in options.branch
Simon Glassc05694f2013-04-03 11:07:16 +0000152 if count == -1:
153 if not options.branch:
Simon Glassd326ad72014-08-09 15:32:59 -0600154 count = 1
155 else:
Simon Glass5eeef462014-12-01 17:33:57 -0700156 if has_range:
157 count, msg = gitutil.CountCommitsInRange(options.git_dir,
158 options.branch)
159 else:
160 count, msg = gitutil.CountCommitsInBranch(options.git_dir,
161 options.branch)
Simon Glassd326ad72014-08-09 15:32:59 -0600162 if count is None:
Simon Glassf204ab12014-12-01 17:33:54 -0700163 sys.exit(col.Color(col.RED, msg))
Simon Glass5eeef462014-12-01 17:33:57 -0700164 elif count == 0:
165 sys.exit(col.Color(col.RED, "Range '%s' has no commits" %
166 options.branch))
Simon Glassf204ab12014-12-01 17:33:54 -0700167 if msg:
168 print col.Color(col.YELLOW, msg)
Simon Glassd326ad72014-08-09 15:32:59 -0600169 count += 1 # Build upstream commit also
Simon Glassc05694f2013-04-03 11:07:16 +0000170
171 if not count:
172 str = ("No commits found to process in branch '%s': "
173 "set branch's upstream or use -c flag" % options.branch)
Masahiro Yamada880828d2014-08-16 00:59:26 +0900174 sys.exit(col.Color(col.RED, str))
Simon Glassc05694f2013-04-03 11:07:16 +0000175
176 # Work out what subset of the boards we are building
Simon Glasscbd36582014-09-05 19:00:16 -0600177 if not boards:
178 board_file = os.path.join(options.git, 'boards.cfg')
179 status = subprocess.call([os.path.join(options.git,
180 'tools/genboardscfg.py')])
181 if status != 0:
182 sys.exit("Failed to generate boards.cfg")
Masahiro Yamadae9bc8d22014-07-30 14:08:22 +0900183
Simon Glasscbd36582014-09-05 19:00:16 -0600184 boards = board.Boards()
185 boards.ReadBoards(os.path.join(options.git, 'boards.cfg'))
Simon Glass924c73a2014-08-28 09:43:41 -0600186
187 exclude = []
188 if options.exclude:
189 for arg in options.exclude:
190 exclude += arg.split(',')
191
192 why_selected = boards.SelectBoards(args, exclude)
Simon Glassc05694f2013-04-03 11:07:16 +0000193 selected = boards.GetSelected()
194 if not len(selected):
Masahiro Yamada880828d2014-08-16 00:59:26 +0900195 sys.exit(col.Color(col.RED, 'No matching boards found'))
Simon Glassc05694f2013-04-03 11:07:16 +0000196
197 # Read the metadata from the commits. First look at the upstream commit,
198 # then the ones in the branch. We would like to do something like
199 # upstream/master~..branch but that isn't possible if upstream/master is
200 # a merge commit (it will list all the commits that form part of the
201 # merge)
Simon Glass359b55a62014-09-05 19:00:23 -0600202 # Conflicting tags are not a problem for buildman, since it does not use
203 # them. For example, Series-version is not useful for buildman. On the
204 # other hand conflicting tags will cause an error. So allow later tags
205 # to overwrite earlier ones by setting allow_overwrite=True
Simon Glassd326ad72014-08-09 15:32:59 -0600206 if options.branch:
Simon Glass16a52882014-08-09 15:33:09 -0600207 if count == -1:
Simon Glass5eeef462014-12-01 17:33:57 -0700208 if has_range:
209 range_expr = options.branch
210 else:
211 range_expr = gitutil.GetRangeInBranch(options.git_dir,
212 options.branch)
Simon Glass16a52882014-08-09 15:33:09 -0600213 upstream_commit = gitutil.GetUpstream(options.git_dir,
214 options.branch)
215 series = patchstream.GetMetaDataForList(upstream_commit,
Simon Glass359b55a62014-09-05 19:00:23 -0600216 options.git_dir, 1, series=None, allow_overwrite=True)
Simon Glassd326ad72014-08-09 15:32:59 -0600217
Simon Glass16a52882014-08-09 15:33:09 -0600218 series = patchstream.GetMetaDataForList(range_expr,
Simon Glass359b55a62014-09-05 19:00:23 -0600219 options.git_dir, None, series, allow_overwrite=True)
Simon Glass16a52882014-08-09 15:33:09 -0600220 else:
221 # Honour the count
222 series = patchstream.GetMetaDataForList(options.branch,
Simon Glass359b55a62014-09-05 19:00:23 -0600223 options.git_dir, count, series=None, allow_overwrite=True)
Simon Glassd326ad72014-08-09 15:32:59 -0600224 else:
225 series = None
Simon Glass6af145f2017-01-23 05:38:56 -0700226 if not options.dry_run:
227 options.verbose = True
228 if not options.summary:
229 options.show_errors = True
Simon Glassc05694f2013-04-03 11:07:16 +0000230
231 # By default we have one thread per CPU. But if there are not enough jobs
232 # we can have fewer threads and use a high '-j' value for make.
233 if not options.threads:
234 options.threads = min(multiprocessing.cpu_count(), len(selected))
235 if not options.jobs:
236 options.jobs = max(1, (multiprocessing.cpu_count() +
237 len(selected) - 1) / len(selected))
238
239 if not options.step:
240 options.step = len(series.commits) - 1
241
Masahiro Yamada1fe610d2014-07-22 11:19:09 +0900242 gnu_make = command.Output(os.path.join(options.git,
Simon Glassc55e0562016-07-25 18:59:00 -0600243 'scripts/show-gnu-make'), raise_on_error=False).rstrip()
Masahiro Yamada1fe610d2014-07-22 11:19:09 +0900244 if not gnu_make:
Masahiro Yamada880828d2014-08-16 00:59:26 +0900245 sys.exit('GNU Make not found')
Masahiro Yamada1fe610d2014-07-22 11:19:09 +0900246
Simon Glassdbc01c72014-12-01 17:33:52 -0700247 # Create a new builder with the selected options.
248 output_dir = options.output_dir
Simon Glassd326ad72014-08-09 15:32:59 -0600249 if options.branch:
Simon Glass4aeceb92014-09-05 19:00:22 -0600250 dirname = options.branch.replace('/', '_')
Simon Glasse87bde12014-12-01 17:33:55 -0700251 # As a special case allow the board directory to be placed in the
252 # output directory itself rather than any subdirectory.
253 if not options.no_subdirs:
254 output_dir = os.path.join(options.output_dir, dirname)
Simon Glass30a664d2014-12-01 17:33:56 -0700255 if (clean_dir and output_dir != options.output_dir and
256 os.path.exists(output_dir)):
Simon Glassa10ebe12014-09-05 19:00:18 -0600257 shutil.rmtree(output_dir)
Simon Glassc05694f2013-04-03 11:07:16 +0000258 builder = Builder(toolchains, output_dir, options.git_dir,
Masahiro Yamada1fe610d2014-07-22 11:19:09 +0900259 options.threads, options.jobs, gnu_make=gnu_make, checkout=True,
Simon Glasse87bde12014-12-01 17:33:55 -0700260 show_unknown=options.show_unknown, step=options.step,
Simon Glass655b6102014-12-01 17:34:07 -0700261 no_subdirs=options.no_subdirs, full_path=options.full_path,
Stephen Warren97c96902016-04-11 10:48:44 -0600262 verbose_build=options.verbose_build,
263 incremental=options.incremental,
Simon Glass739e8512016-11-13 14:25:51 -0700264 per_board_out_dir=options.per_board_out_dir,
Simon Glasscde5c302016-11-13 14:25:53 -0700265 config_only=options.config_only,
266 squash_config_y=not options.preserve_config_y)
Simon Glassc05694f2013-04-03 11:07:16 +0000267 builder.force_config_on_failure = not options.quick
Simon Glassed098bb2014-09-05 19:00:13 -0600268 if make_func:
269 builder.do_make = make_func
Simon Glassc05694f2013-04-03 11:07:16 +0000270
271 # For a dry run, just show our actions as a sanity check
272 if options.dry_run:
273 ShowActions(series, why_selected, selected, builder, options)
274 else:
275 builder.force_build = options.force_build
Simon Glass7041c392014-07-13 12:22:31 -0600276 builder.force_build_failures = options.force_build_failures
Simon Glassf3018b7a2014-07-14 17:51:02 -0600277 builder.force_reconfig = options.force_reconfig
Simon Glass38df2e22014-07-14 17:51:03 -0600278 builder.in_tree = options.in_tree
Simon Glassc05694f2013-04-03 11:07:16 +0000279
280 # Work out which boards to build
281 board_selected = boards.GetSelectedDict()
282
Simon Glassd326ad72014-08-09 15:32:59 -0600283 if series:
284 commits = series.commits
Simon Glassa10ebe12014-09-05 19:00:18 -0600285 # Number the commits for test purposes
286 for commit in range(len(commits)):
287 commits[commit].sequence = commit
Simon Glassd326ad72014-08-09 15:32:59 -0600288 else:
289 commits = None
290
Simon Glassed098bb2014-09-05 19:00:13 -0600291 Print(GetActionSummary(options.summary, commits, board_selected,
292 options))
Simon Glassc05694f2013-04-03 11:07:16 +0000293
Simon Glass232d8502014-09-14 20:23:16 -0600294 # We can't show function sizes without board details at present
295 if options.show_bloat:
296 options.show_detail = True
Simon Glasseb48bbc2014-08-09 15:33:02 -0600297 builder.SetDisplayOptions(options.show_errors, options.show_sizes,
Simon Glass3394c9f2014-08-28 09:43:43 -0600298 options.show_detail, options.show_bloat,
Simon Glassdb17fb82015-02-05 22:06:15 -0700299 options.list_error_boards,
300 options.show_config)
Simon Glassc05694f2013-04-03 11:07:16 +0000301 if options.summary:
Simon Glasseb48bbc2014-08-09 15:33:02 -0600302 builder.ShowSummary(commits, board_selected)
Simon Glassc05694f2013-04-03 11:07:16 +0000303 else:
Simon Glassc2f91072014-08-28 09:43:39 -0600304 fail, warned = builder.BuildBoards(commits, board_selected,
Simon Glass78e418e2014-08-09 15:33:03 -0600305 options.keep_outputs, options.verbose)
Simon Glassc2f91072014-08-28 09:43:39 -0600306 if fail:
307 return 128
308 elif warned:
309 return 129
310 return 0