blob: 73b1a14fb6bb8428206ffc4a9a8b8f4e0926b8ec [file] [log] [blame]
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001# Copyright (c) 2013 The Chromium OS Authors.
2#
Wolfgang Denk1a459662013-07-08 09:37:19 +02003# SPDX-License-Identifier: GPL-2.0+
Simon Glassfc3fe1c2013-04-03 11:07:16 +00004#
5
6import multiprocessing
7import os
Simon Glass883a3212014-09-05 19:00:18 -06008import shutil
Simon Glassfc3fe1c2013-04-03 11:07:16 +00009import sys
10
11import board
12import bsettings
13from builder import Builder
14import gitutil
15import patchstream
16import terminal
Simon Glassd4144e42014-09-05 19:00:13 -060017from terminal import Print
Simon Glassfc3fe1c2013-04-03 11:07:16 +000018import toolchain
Masahiro Yamada99796922014-07-22 11:19:09 +090019import command
Masahiro Yamada73f30b92014-07-30 14:08:22 +090020import subprocess
Simon Glassfc3fe1c2013-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 Glassfea58582014-08-09 15:32:59 -060026def GetActionSummary(is_summary, commits, selected, options):
Simon Glassfc3fe1c2013-04-03 11:07:16 +000027 """Return a string summarising the intended action.
28
29 Returns:
30 Summary string.
31 """
Simon Glassfea58582014-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 Glassfc3fe1c2013-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 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
59 """
60 col = terminal.Color()
61 print 'Dry run, so not doing much. But I would do this:'
62 print
Simon Glassfea58582014-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 Glassfc3fe1c2013-04-03 11:07:16 +000068 options)
69 print 'Build directory: %s' % builder.base_dir
Simon Glassfea58582014-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 Glass1ddda1b2014-10-15 02:27:00 -060073 print ' ', col.Color(col.YELLOW, commit.hash[:8], bright=False),
Simon Glassfea58582014-08-09 15:32:59 -060074 print commit.subject
Simon Glassfc3fe1c2013-04-03 11:07:16 +000075 print
76 for arg in why_selected:
77 if arg != 'all':
Simon Glass8d7523c2017-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 Glassfc3fe1c2013-04-03 11:07:16 +000081 print ('Total boards to build for each commit: %d\n' %
Simon Glass8d7523c2017-01-23 05:38:56 -070082 len(why_selected['all']))
Simon Glassfc3fe1c2013-04-03 11:07:16 +000083
Simon Glass883a3212014-09-05 19:00:18 -060084def DoBuildman(options, args, toolchains=None, make_func=None, boards=None,
85 clean_dir=False):
Simon Glassfc3fe1c2013-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 Glassd4144e42014-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 Glass823e60b2014-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 Glassfc3fe1c2013-04-03 11:07:16 +000099 """
Simon Glass883a3212014-09-05 19:00:18 -0600100 global builder
101
Simon Glass48ba5852014-09-05 19:00:11 -0600102 if options.full_help:
103 pager = os.getenv('PAGER')
104 if not pager:
105 pager = 'more'
Simon Glass2bdeade2016-03-06 19:45:34 -0700106 fname = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])),
107 'README')
Simon Glass48ba5852014-09-05 19:00:11 -0600108 command.Run(pager, fname)
109 return 0
110
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000111 gitutil.Setup()
Simon Glass713bea32016-07-27 20:33:02 -0600112 col = terminal.Color()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000113
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000114 options.git_dir = os.path.join(options.git, '.git')
115
Simon Glass7e92e462016-07-27 20:33:04 -0600116 no_toolchains = toolchains is None
117 if no_toolchains:
Simon Glassd4144e42014-09-05 19:00:13 -0600118 toolchains = toolchain.Toolchains()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000119
Simon Glass827e37b2014-12-01 17:34:06 -0700120 if options.fetch_arch:
121 if options.fetch_arch == 'list':
122 sorted_list = toolchains.ListArchs()
Simon Glass713bea32016-07-27 20:33:02 -0600123 print col.Color(col.BLUE, 'Available architectures: %s\n' %
124 ' '.join(sorted_list))
Simon Glass827e37b2014-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 Glass713bea32016-07-27 20:33:02 -0600130 print col.Color(col.CYAN, '\nDownloading toolchains: %s' %
131 fetch_arch)
Simon Glass827e37b2014-12-01 17:34:06 -0700132 for arch in fetch_arch.split(','):
Simon Glass713bea32016-07-27 20:33:02 -0600133 print
Simon Glass827e37b2014-12-01 17:34:06 -0700134 ret = toolchains.FetchAndInstall(arch)
135 if ret:
136 return ret
137 return 0
138
Simon Glass7e92e462016-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 Glassfc3fe1c2013-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 Glassfc3fe1c2013-04-03 11:07:16 +0000150 count = options.count
Simon Glass5abab202014-12-01 17:33:57 -0700151 has_range = options.branch and '..' in options.branch
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000152 if count == -1:
153 if not options.branch:
Simon Glassfea58582014-08-09 15:32:59 -0600154 count = 1
155 else:
Simon Glass5abab202014-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 Glassfea58582014-08-09 15:32:59 -0600162 if count is None:
Simon Glass2a9e2c62014-12-01 17:33:54 -0700163 sys.exit(col.Color(col.RED, msg))
Simon Glass5abab202014-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 Glass2a9e2c62014-12-01 17:33:54 -0700167 if msg:
168 print col.Color(col.YELLOW, msg)
Simon Glassfea58582014-08-09 15:32:59 -0600169 count += 1 # Build upstream commit also
Simon Glassfc3fe1c2013-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 Yamada31e21412014-08-16 00:59:26 +0900174 sys.exit(col.Color(col.RED, str))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000175
176 # Work out what subset of the boards we are building
Simon Glass823e60b2014-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 Yamada73f30b92014-07-30 14:08:22 +0900183
Simon Glass823e60b2014-09-05 19:00:16 -0600184 boards = board.Boards()
185 boards.ReadBoards(os.path.join(options.git, 'boards.cfg'))
Simon Glass3cf4ae62014-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 Glassfc3fe1c2013-04-03 11:07:16 +0000193 selected = boards.GetSelected()
194 if not len(selected):
Masahiro Yamada31e21412014-08-16 00:59:26 +0900195 sys.exit(col.Color(col.RED, 'No matching boards found'))
Simon Glassfc3fe1c2013-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 Glass950a2312014-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 Glassfea58582014-08-09 15:32:59 -0600206 if options.branch:
Simon Glass3b74ba52014-08-09 15:33:09 -0600207 if count == -1:
Simon Glass5abab202014-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 Glass3b74ba52014-08-09 15:33:09 -0600213 upstream_commit = gitutil.GetUpstream(options.git_dir,
214 options.branch)
215 series = patchstream.GetMetaDataForList(upstream_commit,
Simon Glass950a2312014-09-05 19:00:23 -0600216 options.git_dir, 1, series=None, allow_overwrite=True)
Simon Glassfea58582014-08-09 15:32:59 -0600217
Simon Glass3b74ba52014-08-09 15:33:09 -0600218 series = patchstream.GetMetaDataForList(range_expr,
Simon Glass950a2312014-09-05 19:00:23 -0600219 options.git_dir, None, series, allow_overwrite=True)
Simon Glass3b74ba52014-08-09 15:33:09 -0600220 else:
221 # Honour the count
222 series = patchstream.GetMetaDataForList(options.branch,
Simon Glass950a2312014-09-05 19:00:23 -0600223 options.git_dir, count, series=None, allow_overwrite=True)
Simon Glassfea58582014-08-09 15:32:59 -0600224 else:
225 series = None
Simon Glass8d7523c2017-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 Glassfc3fe1c2013-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 Yamada99796922014-07-22 11:19:09 +0900242 gnu_make = command.Output(os.path.join(options.git,
Simon Glass785f1542016-07-25 18:59:00 -0600243 'scripts/show-gnu-make'), raise_on_error=False).rstrip()
Masahiro Yamada99796922014-07-22 11:19:09 +0900244 if not gnu_make:
Masahiro Yamada31e21412014-08-16 00:59:26 +0900245 sys.exit('GNU Make not found')
Masahiro Yamada99796922014-07-22 11:19:09 +0900246
Simon Glass05c96b12014-12-01 17:33:52 -0700247 # Create a new builder with the selected options.
248 output_dir = options.output_dir
Simon Glassfea58582014-08-09 15:32:59 -0600249 if options.branch:
Simon Glassf7582ce2014-09-05 19:00:22 -0600250 dirname = options.branch.replace('/', '_')
Simon Glass5971ab52014-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 Glass07401272014-12-01 17:33:56 -0700255 if (clean_dir and output_dir != options.output_dir and
256 os.path.exists(output_dir)):
Simon Glass883a3212014-09-05 19:00:18 -0600257 shutil.rmtree(output_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000258 builder = Builder(toolchains, output_dir, options.git_dir,
Masahiro Yamada99796922014-07-22 11:19:09 +0900259 options.threads, options.jobs, gnu_make=gnu_make, checkout=True,
Simon Glass5971ab52014-12-01 17:33:55 -0700260 show_unknown=options.show_unknown, step=options.step,
Simon Glassd2ce6582014-12-01 17:34:07 -0700261 no_subdirs=options.no_subdirs, full_path=options.full_path,
Stephen Warrenf79f1e02016-04-11 10:48:44 -0600262 verbose_build=options.verbose_build,
263 incremental=options.incremental,
Simon Glassb50113f2016-11-13 14:25:51 -0700264 per_board_out_dir=options.per_board_out_dir,
Simon Glassb464f8e2016-11-13 14:25:53 -0700265 config_only=options.config_only,
266 squash_config_y=not options.preserve_config_y)
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