blob: aeb128a6a3e92cd5b0649ff6967d0f3bd67a3eeb [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
51 provided by the user, and the value is the boards brought
52 in by that argument. For example, 'arm' might bring in
53 400 boards, so in this case the key would be 'arm' and
54 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':
78 print arg, ': %d boards' % why_selected[arg]
79 print ('Total boards to build for each commit: %d\n' %
80 why_selected['all'])
81
Simon Glassa10ebe12014-09-05 19:00:18 -060082def DoBuildman(options, args, toolchains=None, make_func=None, boards=None,
83 clean_dir=False):
Simon Glassc05694f2013-04-03 11:07:16 +000084 """The main control code for buildman
85
86 Args:
87 options: Command line options object
88 args: Command line arguments (list of strings)
Simon Glassed098bb2014-09-05 19:00:13 -060089 toolchains: Toolchains to use - this should be a Toolchains()
90 object. If None, then it will be created and scanned
91 make_func: Make function to use for the builder. This is called
92 to execute 'make'. If this is None, the normal function
93 will be used, which calls the 'make' tool with suitable
94 arguments. This setting is useful for tests.
Simon Glasscbd36582014-09-05 19:00:16 -060095 board: Boards() object to use, containing a list of available
96 boards. If this is None it will be created and scanned.
Simon Glassc05694f2013-04-03 11:07:16 +000097 """
Simon Glassa10ebe12014-09-05 19:00:18 -060098 global builder
99
Simon Glassca9b06e2014-09-05 19:00:11 -0600100 if options.full_help:
101 pager = os.getenv('PAGER')
102 if not pager:
103 pager = 'more'
Simon Glassc29101f2016-03-06 19:45:34 -0700104 fname = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])),
105 'README')
Simon Glassca9b06e2014-09-05 19:00:11 -0600106 command.Run(pager, fname)
107 return 0
108
Simon Glassc05694f2013-04-03 11:07:16 +0000109 gitutil.Setup()
110
Simon Glassc05694f2013-04-03 11:07:16 +0000111 options.git_dir = os.path.join(options.git, '.git')
112
Simon Glassed098bb2014-09-05 19:00:13 -0600113 if not toolchains:
114 toolchains = toolchain.Toolchains()
115 toolchains.GetSettings()
116 toolchains.Scan(options.list_tool_chains)
Simon Glassc05694f2013-04-03 11:07:16 +0000117 if options.list_tool_chains:
118 toolchains.List()
119 print
Simon Glassc2f91072014-08-28 09:43:39 -0600120 return 0
Simon Glassc05694f2013-04-03 11:07:16 +0000121
Simon Glass7e803e12014-12-01 17:34:06 -0700122 if options.fetch_arch:
123 if options.fetch_arch == 'list':
124 sorted_list = toolchains.ListArchs()
125 print 'Available architectures: %s\n' % ' '.join(sorted_list)
126 return 0
127 else:
128 fetch_arch = options.fetch_arch
129 if fetch_arch == 'all':
130 fetch_arch = ','.join(toolchains.ListArchs())
131 print 'Downloading toolchains: %s\n' % fetch_arch
132 for arch in fetch_arch.split(','):
133 ret = toolchains.FetchAndInstall(arch)
134 if ret:
135 return ret
136 return 0
137
Simon Glassc05694f2013-04-03 11:07:16 +0000138 # Work out how many commits to build. We want to build everything on the
139 # branch. We also build the upstream commit as a control so we can see
140 # problems introduced by the first commit on the branch.
141 col = terminal.Color()
142 count = options.count
Simon Glass5eeef462014-12-01 17:33:57 -0700143 has_range = options.branch and '..' in options.branch
Simon Glassc05694f2013-04-03 11:07:16 +0000144 if count == -1:
145 if not options.branch:
Simon Glassd326ad72014-08-09 15:32:59 -0600146 count = 1
147 else:
Simon Glass5eeef462014-12-01 17:33:57 -0700148 if has_range:
149 count, msg = gitutil.CountCommitsInRange(options.git_dir,
150 options.branch)
151 else:
152 count, msg = gitutil.CountCommitsInBranch(options.git_dir,
153 options.branch)
Simon Glassd326ad72014-08-09 15:32:59 -0600154 if count is None:
Simon Glassf204ab12014-12-01 17:33:54 -0700155 sys.exit(col.Color(col.RED, msg))
Simon Glass5eeef462014-12-01 17:33:57 -0700156 elif count == 0:
157 sys.exit(col.Color(col.RED, "Range '%s' has no commits" %
158 options.branch))
Simon Glassf204ab12014-12-01 17:33:54 -0700159 if msg:
160 print col.Color(col.YELLOW, msg)
Simon Glassd326ad72014-08-09 15:32:59 -0600161 count += 1 # Build upstream commit also
Simon Glassc05694f2013-04-03 11:07:16 +0000162
163 if not count:
164 str = ("No commits found to process in branch '%s': "
165 "set branch's upstream or use -c flag" % options.branch)
Masahiro Yamada880828d2014-08-16 00:59:26 +0900166 sys.exit(col.Color(col.RED, str))
Simon Glassc05694f2013-04-03 11:07:16 +0000167
168 # Work out what subset of the boards we are building
Simon Glasscbd36582014-09-05 19:00:16 -0600169 if not boards:
170 board_file = os.path.join(options.git, 'boards.cfg')
171 status = subprocess.call([os.path.join(options.git,
172 'tools/genboardscfg.py')])
173 if status != 0:
174 sys.exit("Failed to generate boards.cfg")
Masahiro Yamadae9bc8d22014-07-30 14:08:22 +0900175
Simon Glasscbd36582014-09-05 19:00:16 -0600176 boards = board.Boards()
177 boards.ReadBoards(os.path.join(options.git, 'boards.cfg'))
Simon Glass924c73a2014-08-28 09:43:41 -0600178
179 exclude = []
180 if options.exclude:
181 for arg in options.exclude:
182 exclude += arg.split(',')
183
184 why_selected = boards.SelectBoards(args, exclude)
Simon Glassc05694f2013-04-03 11:07:16 +0000185 selected = boards.GetSelected()
186 if not len(selected):
Masahiro Yamada880828d2014-08-16 00:59:26 +0900187 sys.exit(col.Color(col.RED, 'No matching boards found'))
Simon Glassc05694f2013-04-03 11:07:16 +0000188
189 # Read the metadata from the commits. First look at the upstream commit,
190 # then the ones in the branch. We would like to do something like
191 # upstream/master~..branch but that isn't possible if upstream/master is
192 # a merge commit (it will list all the commits that form part of the
193 # merge)
Simon Glass359b55a62014-09-05 19:00:23 -0600194 # Conflicting tags are not a problem for buildman, since it does not use
195 # them. For example, Series-version is not useful for buildman. On the
196 # other hand conflicting tags will cause an error. So allow later tags
197 # to overwrite earlier ones by setting allow_overwrite=True
Simon Glassd326ad72014-08-09 15:32:59 -0600198 if options.branch:
Simon Glass16a52882014-08-09 15:33:09 -0600199 if count == -1:
Simon Glass5eeef462014-12-01 17:33:57 -0700200 if has_range:
201 range_expr = options.branch
202 else:
203 range_expr = gitutil.GetRangeInBranch(options.git_dir,
204 options.branch)
Simon Glass16a52882014-08-09 15:33:09 -0600205 upstream_commit = gitutil.GetUpstream(options.git_dir,
206 options.branch)
207 series = patchstream.GetMetaDataForList(upstream_commit,
Simon Glass359b55a62014-09-05 19:00:23 -0600208 options.git_dir, 1, series=None, allow_overwrite=True)
Simon Glassd326ad72014-08-09 15:32:59 -0600209
Simon Glass16a52882014-08-09 15:33:09 -0600210 series = patchstream.GetMetaDataForList(range_expr,
Simon Glass359b55a62014-09-05 19:00:23 -0600211 options.git_dir, None, series, allow_overwrite=True)
Simon Glass16a52882014-08-09 15:33:09 -0600212 else:
213 # Honour the count
214 series = patchstream.GetMetaDataForList(options.branch,
Simon Glass359b55a62014-09-05 19:00:23 -0600215 options.git_dir, count, series=None, allow_overwrite=True)
Simon Glassd326ad72014-08-09 15:32:59 -0600216 else:
217 series = None
Simon Glass78e418e2014-08-09 15:33:03 -0600218 options.verbose = True
Simon Glassd1f00562014-10-15 14:37:25 +0200219 if not options.summary:
220 options.show_errors = True
Simon Glassc05694f2013-04-03 11:07:16 +0000221
222 # By default we have one thread per CPU. But if there are not enough jobs
223 # we can have fewer threads and use a high '-j' value for make.
224 if not options.threads:
225 options.threads = min(multiprocessing.cpu_count(), len(selected))
226 if not options.jobs:
227 options.jobs = max(1, (multiprocessing.cpu_count() +
228 len(selected) - 1) / len(selected))
229
230 if not options.step:
231 options.step = len(series.commits) - 1
232
Masahiro Yamada1fe610d2014-07-22 11:19:09 +0900233 gnu_make = command.Output(os.path.join(options.git,
234 'scripts/show-gnu-make')).rstrip()
235 if not gnu_make:
Masahiro Yamada880828d2014-08-16 00:59:26 +0900236 sys.exit('GNU Make not found')
Masahiro Yamada1fe610d2014-07-22 11:19:09 +0900237
Simon Glassdbc01c72014-12-01 17:33:52 -0700238 # Create a new builder with the selected options.
239 output_dir = options.output_dir
Simon Glassd326ad72014-08-09 15:32:59 -0600240 if options.branch:
Simon Glass4aeceb92014-09-05 19:00:22 -0600241 dirname = options.branch.replace('/', '_')
Simon Glasse87bde12014-12-01 17:33:55 -0700242 # As a special case allow the board directory to be placed in the
243 # output directory itself rather than any subdirectory.
244 if not options.no_subdirs:
245 output_dir = os.path.join(options.output_dir, dirname)
Simon Glass30a664d2014-12-01 17:33:56 -0700246 if (clean_dir and output_dir != options.output_dir and
247 os.path.exists(output_dir)):
Simon Glassa10ebe12014-09-05 19:00:18 -0600248 shutil.rmtree(output_dir)
Simon Glassc05694f2013-04-03 11:07:16 +0000249 builder = Builder(toolchains, output_dir, options.git_dir,
Masahiro Yamada1fe610d2014-07-22 11:19:09 +0900250 options.threads, options.jobs, gnu_make=gnu_make, checkout=True,
Simon Glasse87bde12014-12-01 17:33:55 -0700251 show_unknown=options.show_unknown, step=options.step,
Simon Glass655b6102014-12-01 17:34:07 -0700252 no_subdirs=options.no_subdirs, full_path=options.full_path,
Stephen Warren97c96902016-04-11 10:48:44 -0600253 verbose_build=options.verbose_build,
254 incremental=options.incremental,
255 per_board_out_dir=options.per_board_out_dir,)
Simon Glassc05694f2013-04-03 11:07:16 +0000256 builder.force_config_on_failure = not options.quick
Simon Glassed098bb2014-09-05 19:00:13 -0600257 if make_func:
258 builder.do_make = make_func
Simon Glassc05694f2013-04-03 11:07:16 +0000259
260 # For a dry run, just show our actions as a sanity check
261 if options.dry_run:
262 ShowActions(series, why_selected, selected, builder, options)
263 else:
264 builder.force_build = options.force_build
Simon Glass7041c392014-07-13 12:22:31 -0600265 builder.force_build_failures = options.force_build_failures
Simon Glassf3018b7a2014-07-14 17:51:02 -0600266 builder.force_reconfig = options.force_reconfig
Simon Glass38df2e22014-07-14 17:51:03 -0600267 builder.in_tree = options.in_tree
Simon Glassc05694f2013-04-03 11:07:16 +0000268
269 # Work out which boards to build
270 board_selected = boards.GetSelectedDict()
271
Simon Glassd326ad72014-08-09 15:32:59 -0600272 if series:
273 commits = series.commits
Simon Glassa10ebe12014-09-05 19:00:18 -0600274 # Number the commits for test purposes
275 for commit in range(len(commits)):
276 commits[commit].sequence = commit
Simon Glassd326ad72014-08-09 15:32:59 -0600277 else:
278 commits = None
279
Simon Glassed098bb2014-09-05 19:00:13 -0600280 Print(GetActionSummary(options.summary, commits, board_selected,
281 options))
Simon Glassc05694f2013-04-03 11:07:16 +0000282
Simon Glass232d8502014-09-14 20:23:16 -0600283 # We can't show function sizes without board details at present
284 if options.show_bloat:
285 options.show_detail = True
Simon Glasseb48bbc2014-08-09 15:33:02 -0600286 builder.SetDisplayOptions(options.show_errors, options.show_sizes,
Simon Glass3394c9f2014-08-28 09:43:43 -0600287 options.show_detail, options.show_bloat,
Simon Glassdb17fb82015-02-05 22:06:15 -0700288 options.list_error_boards,
289 options.show_config)
Simon Glassc05694f2013-04-03 11:07:16 +0000290 if options.summary:
Simon Glasseb48bbc2014-08-09 15:33:02 -0600291 builder.ShowSummary(commits, board_selected)
Simon Glassc05694f2013-04-03 11:07:16 +0000292 else:
Simon Glassc2f91072014-08-28 09:43:39 -0600293 fail, warned = builder.BuildBoards(commits, board_selected,
Simon Glass78e418e2014-08-09 15:33:03 -0600294 options.keep_outputs, options.verbose)
Simon Glassc2f91072014-08-28 09:43:39 -0600295 if fail:
296 return 128
297 elif warned:
298 return 129
299 return 0