blob: c14b87842da5c9ee04af5772243f5afddb5abb63 [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)
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 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
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 Glass8d7523c2017-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 Glassfc3fe1c2013-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 Glassfea58582014-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 Glassfc3fe1c2013-04-03 11:07:16 +000067 options)
68 print 'Build directory: %s' % builder.base_dir
Simon Glassfea58582014-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 Glass1ddda1b2014-10-15 02:27:00 -060072 print ' ', col.Color(col.YELLOW, commit.hash[:8], bright=False),
Simon Glassfea58582014-08-09 15:32:59 -060073 print commit.subject
Simon Glassfc3fe1c2013-04-03 11:07:16 +000074 print
75 for arg in why_selected:
76 if arg != 'all':
Simon Glass8d7523c2017-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 Glassfc3fe1c2013-04-03 11:07:16 +000080 print ('Total boards to build for each commit: %d\n' %
Simon Glass8d7523c2017-01-23 05:38:56 -070081 len(why_selected['all']))
Simon Glassfc3fe1c2013-04-03 11:07:16 +000082
Simon Glass883a3212014-09-05 19:00:18 -060083def DoBuildman(options, args, toolchains=None, make_func=None, boards=None,
84 clean_dir=False):
Simon Glassfc3fe1c2013-04-03 11:07:16 +000085 """The main control code for buildman
86
87 Args:
88 options: Command line options object
89 args: Command line arguments (list of strings)
Simon Glassd4144e42014-09-05 19:00:13 -060090 toolchains: Toolchains to use - this should be a Toolchains()
91 object. If None, then it will be created and scanned
92 make_func: Make function to use for the builder. This is called
93 to execute 'make'. If this is None, the normal function
94 will be used, which calls the 'make' tool with suitable
95 arguments. This setting is useful for tests.
Simon Glass823e60b2014-09-05 19:00:16 -060096 board: Boards() object to use, containing a list of available
97 boards. If this is None it will be created and scanned.
Simon Glassfc3fe1c2013-04-03 11:07:16 +000098 """
Simon Glass883a3212014-09-05 19:00:18 -060099 global builder
100
Simon Glass48ba5852014-09-05 19:00:11 -0600101 if options.full_help:
102 pager = os.getenv('PAGER')
103 if not pager:
104 pager = 'more'
Simon Glass2bdeade2016-03-06 19:45:34 -0700105 fname = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])),
106 'README')
Simon Glass48ba5852014-09-05 19:00:11 -0600107 command.Run(pager, fname)
108 return 0
109
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000110 gitutil.Setup()
Simon Glass713bea32016-07-27 20:33:02 -0600111 col = terminal.Color()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000112
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000113 options.git_dir = os.path.join(options.git, '.git')
114
Simon Glass7e92e462016-07-27 20:33:04 -0600115 no_toolchains = toolchains is None
116 if no_toolchains:
Simon Glassd4144e42014-09-05 19:00:13 -0600117 toolchains = toolchain.Toolchains()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000118
Simon Glass827e37b2014-12-01 17:34:06 -0700119 if options.fetch_arch:
120 if options.fetch_arch == 'list':
121 sorted_list = toolchains.ListArchs()
Simon Glass713bea32016-07-27 20:33:02 -0600122 print col.Color(col.BLUE, 'Available architectures: %s\n' %
123 ' '.join(sorted_list))
Simon Glass827e37b2014-12-01 17:34:06 -0700124 return 0
125 else:
126 fetch_arch = options.fetch_arch
127 if fetch_arch == 'all':
128 fetch_arch = ','.join(toolchains.ListArchs())
Simon Glass713bea32016-07-27 20:33:02 -0600129 print col.Color(col.CYAN, '\nDownloading toolchains: %s' %
130 fetch_arch)
Simon Glass827e37b2014-12-01 17:34:06 -0700131 for arch in fetch_arch.split(','):
Simon Glass713bea32016-07-27 20:33:02 -0600132 print
Simon Glass827e37b2014-12-01 17:34:06 -0700133 ret = toolchains.FetchAndInstall(arch)
134 if ret:
135 return ret
136 return 0
137
Simon Glass7e92e462016-07-27 20:33:04 -0600138 if no_toolchains:
139 toolchains.GetSettings()
140 toolchains.Scan(options.list_tool_chains)
141 if options.list_tool_chains:
142 toolchains.List()
143 print
144 return 0
145
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000146 # Work out how many commits to build. We want to build everything on the
147 # branch. We also build the upstream commit as a control so we can see
148 # problems introduced by the first commit on the branch.
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000149 count = options.count
Simon Glass5abab202014-12-01 17:33:57 -0700150 has_range = options.branch and '..' in options.branch
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000151 if count == -1:
152 if not options.branch:
Simon Glassfea58582014-08-09 15:32:59 -0600153 count = 1
154 else:
Simon Glass5abab202014-12-01 17:33:57 -0700155 if has_range:
156 count, msg = gitutil.CountCommitsInRange(options.git_dir,
157 options.branch)
158 else:
159 count, msg = gitutil.CountCommitsInBranch(options.git_dir,
160 options.branch)
Simon Glassfea58582014-08-09 15:32:59 -0600161 if count is None:
Simon Glass2a9e2c62014-12-01 17:33:54 -0700162 sys.exit(col.Color(col.RED, msg))
Simon Glass5abab202014-12-01 17:33:57 -0700163 elif count == 0:
164 sys.exit(col.Color(col.RED, "Range '%s' has no commits" %
165 options.branch))
Simon Glass2a9e2c62014-12-01 17:33:54 -0700166 if msg:
167 print col.Color(col.YELLOW, msg)
Simon Glassfea58582014-08-09 15:32:59 -0600168 count += 1 # Build upstream commit also
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000169
170 if not count:
171 str = ("No commits found to process in branch '%s': "
172 "set branch's upstream or use -c flag" % options.branch)
Masahiro Yamada31e21412014-08-16 00:59:26 +0900173 sys.exit(col.Color(col.RED, str))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000174
175 # Work out what subset of the boards we are building
Simon Glass823e60b2014-09-05 19:00:16 -0600176 if not boards:
177 board_file = os.path.join(options.git, 'boards.cfg')
178 status = subprocess.call([os.path.join(options.git,
179 'tools/genboardscfg.py')])
180 if status != 0:
181 sys.exit("Failed to generate boards.cfg")
Masahiro Yamada73f30b92014-07-30 14:08:22 +0900182
Simon Glass823e60b2014-09-05 19:00:16 -0600183 boards = board.Boards()
184 boards.ReadBoards(os.path.join(options.git, 'boards.cfg'))
Simon Glass3cf4ae62014-08-28 09:43:41 -0600185
186 exclude = []
187 if options.exclude:
188 for arg in options.exclude:
189 exclude += arg.split(',')
190
191 why_selected = boards.SelectBoards(args, exclude)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000192 selected = boards.GetSelected()
193 if not len(selected):
Masahiro Yamada31e21412014-08-16 00:59:26 +0900194 sys.exit(col.Color(col.RED, 'No matching boards found'))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000195
196 # Read the metadata from the commits. First look at the upstream commit,
197 # then the ones in the branch. We would like to do something like
198 # upstream/master~..branch but that isn't possible if upstream/master is
199 # a merge commit (it will list all the commits that form part of the
200 # merge)
Simon Glass950a2312014-09-05 19:00:23 -0600201 # Conflicting tags are not a problem for buildman, since it does not use
202 # them. For example, Series-version is not useful for buildman. On the
203 # other hand conflicting tags will cause an error. So allow later tags
204 # to overwrite earlier ones by setting allow_overwrite=True
Simon Glassfea58582014-08-09 15:32:59 -0600205 if options.branch:
Simon Glass3b74ba52014-08-09 15:33:09 -0600206 if count == -1:
Simon Glass5abab202014-12-01 17:33:57 -0700207 if has_range:
208 range_expr = options.branch
209 else:
210 range_expr = gitutil.GetRangeInBranch(options.git_dir,
211 options.branch)
Simon Glass3b74ba52014-08-09 15:33:09 -0600212 upstream_commit = gitutil.GetUpstream(options.git_dir,
213 options.branch)
214 series = patchstream.GetMetaDataForList(upstream_commit,
Simon Glass950a2312014-09-05 19:00:23 -0600215 options.git_dir, 1, series=None, allow_overwrite=True)
Simon Glassfea58582014-08-09 15:32:59 -0600216
Simon Glass3b74ba52014-08-09 15:33:09 -0600217 series = patchstream.GetMetaDataForList(range_expr,
Simon Glass950a2312014-09-05 19:00:23 -0600218 options.git_dir, None, series, allow_overwrite=True)
Simon Glass3b74ba52014-08-09 15:33:09 -0600219 else:
220 # Honour the count
221 series = patchstream.GetMetaDataForList(options.branch,
Simon Glass950a2312014-09-05 19:00:23 -0600222 options.git_dir, count, series=None, allow_overwrite=True)
Simon Glassfea58582014-08-09 15:32:59 -0600223 else:
224 series = None
Simon Glass8d7523c2017-01-23 05:38:56 -0700225 if not options.dry_run:
226 options.verbose = True
227 if not options.summary:
228 options.show_errors = True
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000229
230 # By default we have one thread per CPU. But if there are not enough jobs
231 # we can have fewer threads and use a high '-j' value for make.
232 if not options.threads:
233 options.threads = min(multiprocessing.cpu_count(), len(selected))
234 if not options.jobs:
235 options.jobs = max(1, (multiprocessing.cpu_count() +
236 len(selected) - 1) / len(selected))
237
238 if not options.step:
239 options.step = len(series.commits) - 1
240
Masahiro Yamada99796922014-07-22 11:19:09 +0900241 gnu_make = command.Output(os.path.join(options.git,
Simon Glass785f1542016-07-25 18:59:00 -0600242 'scripts/show-gnu-make'), raise_on_error=False).rstrip()
Masahiro Yamada99796922014-07-22 11:19:09 +0900243 if not gnu_make:
Masahiro Yamada31e21412014-08-16 00:59:26 +0900244 sys.exit('GNU Make not found')
Masahiro Yamada99796922014-07-22 11:19:09 +0900245
Simon Glass05c96b12014-12-01 17:33:52 -0700246 # Create a new builder with the selected options.
247 output_dir = options.output_dir
Simon Glassfea58582014-08-09 15:32:59 -0600248 if options.branch:
Simon Glassf7582ce2014-09-05 19:00:22 -0600249 dirname = options.branch.replace('/', '_')
Simon Glass5971ab52014-12-01 17:33:55 -0700250 # As a special case allow the board directory to be placed in the
251 # output directory itself rather than any subdirectory.
252 if not options.no_subdirs:
253 output_dir = os.path.join(options.output_dir, dirname)
Simon Glass07401272014-12-01 17:33:56 -0700254 if (clean_dir and output_dir != options.output_dir and
255 os.path.exists(output_dir)):
Simon Glass883a3212014-09-05 19:00:18 -0600256 shutil.rmtree(output_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000257 builder = Builder(toolchains, output_dir, options.git_dir,
Masahiro Yamada99796922014-07-22 11:19:09 +0900258 options.threads, options.jobs, gnu_make=gnu_make, checkout=True,
Simon Glass5971ab52014-12-01 17:33:55 -0700259 show_unknown=options.show_unknown, step=options.step,
Simon Glassd2ce6582014-12-01 17:34:07 -0700260 no_subdirs=options.no_subdirs, full_path=options.full_path,
Stephen Warrenf79f1e02016-04-11 10:48:44 -0600261 verbose_build=options.verbose_build,
262 incremental=options.incremental,
Simon Glassb50113f2016-11-13 14:25:51 -0700263 per_board_out_dir=options.per_board_out_dir,
Simon Glassb464f8e2016-11-13 14:25:53 -0700264 config_only=options.config_only,
Daniel Schwierzeck2371d1b2018-01-26 16:31:05 +0100265 squash_config_y=not options.preserve_config_y,
266 warnings_as_errors=options.warnings_as_errors)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000267 builder.force_config_on_failure = not options.quick
Simon Glassd4144e42014-09-05 19:00:13 -0600268 if make_func:
269 builder.do_make = make_func
Simon Glassfc3fe1c2013-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 Glass4266dc22014-07-13 12:22:31 -0600276 builder.force_build_failures = options.force_build_failures
Simon Glass97e91522014-07-14 17:51:02 -0600277 builder.force_reconfig = options.force_reconfig
Simon Glass189a4962014-07-14 17:51:03 -0600278 builder.in_tree = options.in_tree
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000279
280 # Work out which boards to build
281 board_selected = boards.GetSelectedDict()
282
Simon Glassfea58582014-08-09 15:32:59 -0600283 if series:
284 commits = series.commits
Simon Glass883a3212014-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 Glassfea58582014-08-09 15:32:59 -0600288 else:
289 commits = None
290
Simon Glassd4144e42014-09-05 19:00:13 -0600291 Print(GetActionSummary(options.summary, commits, board_selected,
292 options))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000293
Simon Glass7798e222014-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 Glassb2ea7ab2014-08-09 15:33:02 -0600297 builder.SetDisplayOptions(options.show_errors, options.show_sizes,
Simon Glassed966652014-08-28 09:43:43 -0600298 options.show_detail, options.show_bloat,
Simon Glass843312d2015-02-05 22:06:15 -0700299 options.list_error_boards,
300 options.show_config)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000301 if options.summary:
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600302 builder.ShowSummary(commits, board_selected)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000303 else:
Simon Glass2c3deb92014-08-28 09:43:39 -0600304 fail, warned = builder.BuildBoards(commits, board_selected,
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600305 options.keep_outputs, options.verbose)
Simon Glass2c3deb92014-08-28 09:43:39 -0600306 if fail:
307 return 128
308 elif warned:
309 return 129
310 return 0