blob: 213e235a11304befcad3fe547c3be877709ec667 [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
8import 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
50 provided by the user, and the value is the boards brought
51 in by that argument. For example, 'arm' might bring in
52 400 boards, so in this case the key would be 'arm' and
53 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]
72 print ' ', col.Color(col.YELLOW, commit.hash, bright=False),
73 print commit.subject
Simon Glassfc3fe1c2013-04-03 11:07:16 +000074 print
75 for arg in why_selected:
76 if arg != 'all':
77 print arg, ': %d boards' % why_selected[arg]
78 print ('Total boards to build for each commit: %d\n' %
79 why_selected['all'])
80
Simon Glassd4144e42014-09-05 19:00:13 -060081def DoBuildman(options, args, toolchains=None, make_func=None):
Simon Glassfc3fe1c2013-04-03 11:07:16 +000082 """The main control code for buildman
83
84 Args:
85 options: Command line options object
86 args: Command line arguments (list of strings)
Simon Glassd4144e42014-09-05 19:00:13 -060087 toolchains: Toolchains to use - this should be a Toolchains()
88 object. If None, then it will be created and scanned
89 make_func: Make function to use for the builder. This is called
90 to execute 'make'. If this is None, the normal function
91 will be used, which calls the 'make' tool with suitable
92 arguments. This setting is useful for tests.
Simon Glassfc3fe1c2013-04-03 11:07:16 +000093 """
Simon Glass48ba5852014-09-05 19:00:11 -060094 if options.full_help:
95 pager = os.getenv('PAGER')
96 if not pager:
97 pager = 'more'
98 fname = os.path.join(os.path.dirname(sys.argv[0]), 'README')
99 command.Run(pager, fname)
100 return 0
101
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000102 gitutil.Setup()
103
Simon Glass0f7c9dd2014-08-09 15:33:05 -0600104 bsettings.Setup(options.config_file)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000105 options.git_dir = os.path.join(options.git, '.git')
106
Simon Glassd4144e42014-09-05 19:00:13 -0600107 if not toolchains:
108 toolchains = toolchain.Toolchains()
109 toolchains.GetSettings()
110 toolchains.Scan(options.list_tool_chains)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000111 if options.list_tool_chains:
112 toolchains.List()
113 print
Simon Glass2c3deb92014-08-28 09:43:39 -0600114 return 0
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000115
116 # Work out how many commits to build. We want to build everything on the
117 # branch. We also build the upstream commit as a control so we can see
118 # problems introduced by the first commit on the branch.
119 col = terminal.Color()
120 count = options.count
121 if count == -1:
122 if not options.branch:
Simon Glassfea58582014-08-09 15:32:59 -0600123 count = 1
124 else:
125 count = gitutil.CountCommitsInBranch(options.git_dir,
126 options.branch)
127 if count is None:
128 str = ("Branch '%s' not found or has no upstream" %
129 options.branch)
Masahiro Yamada31e21412014-08-16 00:59:26 +0900130 sys.exit(col.Color(col.RED, str))
Simon Glassfea58582014-08-09 15:32:59 -0600131 count += 1 # Build upstream commit also
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000132
133 if not count:
134 str = ("No commits found to process in branch '%s': "
135 "set branch's upstream or use -c flag" % options.branch)
Masahiro Yamada31e21412014-08-16 00:59:26 +0900136 sys.exit(col.Color(col.RED, str))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000137
138 # Work out what subset of the boards we are building
Masahiro Yamada73f30b92014-07-30 14:08:22 +0900139 board_file = os.path.join(options.git, 'boards.cfg')
Masahiro Yamada390f7032014-08-22 14:33:41 +0900140 status = subprocess.call([os.path.join(options.git,
141 'tools/genboardscfg.py')])
142 if status != 0:
143 sys.exit("Failed to generate boards.cfg")
Masahiro Yamada73f30b92014-07-30 14:08:22 +0900144
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000145 boards = board.Boards()
146 boards.ReadBoards(os.path.join(options.git, 'boards.cfg'))
Simon Glass3cf4ae62014-08-28 09:43:41 -0600147
148 exclude = []
149 if options.exclude:
150 for arg in options.exclude:
151 exclude += arg.split(',')
152
153 why_selected = boards.SelectBoards(args, exclude)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000154 selected = boards.GetSelected()
155 if not len(selected):
Masahiro Yamada31e21412014-08-16 00:59:26 +0900156 sys.exit(col.Color(col.RED, 'No matching boards found'))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000157
158 # Read the metadata from the commits. First look at the upstream commit,
159 # then the ones in the branch. We would like to do something like
160 # upstream/master~..branch but that isn't possible if upstream/master is
161 # a merge commit (it will list all the commits that form part of the
162 # merge)
Simon Glassfea58582014-08-09 15:32:59 -0600163 if options.branch:
Simon Glass3b74ba52014-08-09 15:33:09 -0600164 if count == -1:
165 range_expr = gitutil.GetRangeInBranch(options.git_dir,
166 options.branch)
167 upstream_commit = gitutil.GetUpstream(options.git_dir,
168 options.branch)
169 series = patchstream.GetMetaDataForList(upstream_commit,
170 options.git_dir, 1)
Simon Glassfea58582014-08-09 15:32:59 -0600171
Simon Glass3b74ba52014-08-09 15:33:09 -0600172 # Conflicting tags are not a problem for buildman, since it does
173 # not use them. For example, Series-version is not useful for
174 # buildman. On the other hand conflicting tags will cause an
175 # error. So allow later tags to overwrite earlier ones.
176 series.allow_overwrite = True
177 series = patchstream.GetMetaDataForList(range_expr,
178 options.git_dir, None, series)
179 else:
180 # Honour the count
181 series = patchstream.GetMetaDataForList(options.branch,
182 options.git_dir, count)
Simon Glassfea58582014-08-09 15:32:59 -0600183 else:
184 series = None
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600185 options.verbose = True
186 options.show_errors = True
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000187
188 # By default we have one thread per CPU. But if there are not enough jobs
189 # we can have fewer threads and use a high '-j' value for make.
190 if not options.threads:
191 options.threads = min(multiprocessing.cpu_count(), len(selected))
192 if not options.jobs:
193 options.jobs = max(1, (multiprocessing.cpu_count() +
194 len(selected) - 1) / len(selected))
195
196 if not options.step:
197 options.step = len(series.commits) - 1
198
Masahiro Yamada99796922014-07-22 11:19:09 +0900199 gnu_make = command.Output(os.path.join(options.git,
200 'scripts/show-gnu-make')).rstrip()
201 if not gnu_make:
Masahiro Yamada31e21412014-08-16 00:59:26 +0900202 sys.exit('GNU Make not found')
Masahiro Yamada99796922014-07-22 11:19:09 +0900203
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000204 # Create a new builder with the selected options
Simon Glassfea58582014-08-09 15:32:59 -0600205 if options.branch:
206 dirname = options.branch
207 else:
208 dirname = 'current'
209 output_dir = os.path.join(options.output_dir, dirname)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000210 builder = Builder(toolchains, output_dir, options.git_dir,
Masahiro Yamada99796922014-07-22 11:19:09 +0900211 options.threads, options.jobs, gnu_make=gnu_make, checkout=True,
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000212 show_unknown=options.show_unknown, step=options.step)
213 builder.force_config_on_failure = not options.quick
Simon Glassd4144e42014-09-05 19:00:13 -0600214 if make_func:
215 builder.do_make = make_func
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000216
217 # For a dry run, just show our actions as a sanity check
218 if options.dry_run:
219 ShowActions(series, why_selected, selected, builder, options)
220 else:
221 builder.force_build = options.force_build
Simon Glass4266dc22014-07-13 12:22:31 -0600222 builder.force_build_failures = options.force_build_failures
Simon Glass97e91522014-07-14 17:51:02 -0600223 builder.force_reconfig = options.force_reconfig
Simon Glass189a4962014-07-14 17:51:03 -0600224 builder.in_tree = options.in_tree
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000225
226 # Work out which boards to build
227 board_selected = boards.GetSelectedDict()
228
Simon Glassfea58582014-08-09 15:32:59 -0600229 if series:
230 commits = series.commits
231 else:
232 commits = None
233
Simon Glassd4144e42014-09-05 19:00:13 -0600234 Print(GetActionSummary(options.summary, commits, board_selected,
235 options))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000236
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600237 builder.SetDisplayOptions(options.show_errors, options.show_sizes,
Simon Glassed966652014-08-28 09:43:43 -0600238 options.show_detail, options.show_bloat,
239 options.list_error_boards)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000240 if options.summary:
241 # We can't show function sizes without board details at present
242 if options.show_bloat:
243 options.show_detail = True
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600244 builder.ShowSummary(commits, board_selected)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000245 else:
Simon Glass2c3deb92014-08-28 09:43:39 -0600246 fail, warned = builder.BuildBoards(commits, board_selected,
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600247 options.keep_outputs, options.verbose)
Simon Glass2c3deb92014-08-28 09:43:39 -0600248 if fail:
249 return 128
250 elif warned:
251 return 129
252 return 0