blob: cd0333ca1d56cc9e02e9f9a6194f3e75e40dfd63 [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
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 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':
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 Glass883a3212014-09-05 19:00:18 -060082def DoBuildman(options, args, toolchains=None, make_func=None, boards=None,
83 clean_dir=False):
Simon Glassfc3fe1c2013-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 Glassd4144e42014-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 Glass823e60b2014-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 Glassfc3fe1c2013-04-03 11:07:16 +000097 """
Simon Glass883a3212014-09-05 19:00:18 -060098 global builder
99
Simon Glass48ba5852014-09-05 19:00:11 -0600100 if options.full_help:
101 pager = os.getenv('PAGER')
102 if not pager:
103 pager = 'more'
104 fname = os.path.join(os.path.dirname(sys.argv[0]), 'README')
105 command.Run(pager, fname)
106 return 0
107
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000108 gitutil.Setup()
109
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000110 options.git_dir = os.path.join(options.git, '.git')
111
Simon Glassd4144e42014-09-05 19:00:13 -0600112 if not toolchains:
113 toolchains = toolchain.Toolchains()
114 toolchains.GetSettings()
115 toolchains.Scan(options.list_tool_chains)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000116 if options.list_tool_chains:
117 toolchains.List()
118 print
Simon Glass2c3deb92014-08-28 09:43:39 -0600119 return 0
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000120
121 # Work out how many commits to build. We want to build everything on the
122 # branch. We also build the upstream commit as a control so we can see
123 # problems introduced by the first commit on the branch.
124 col = terminal.Color()
125 count = options.count
Simon Glass5abab202014-12-01 17:33:57 -0700126 has_range = options.branch and '..' in options.branch
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000127 if count == -1:
128 if not options.branch:
Simon Glassfea58582014-08-09 15:32:59 -0600129 count = 1
130 else:
Simon Glass5abab202014-12-01 17:33:57 -0700131 if has_range:
132 count, msg = gitutil.CountCommitsInRange(options.git_dir,
133 options.branch)
134 else:
135 count, msg = gitutil.CountCommitsInBranch(options.git_dir,
136 options.branch)
Simon Glassfea58582014-08-09 15:32:59 -0600137 if count is None:
Simon Glass2a9e2c62014-12-01 17:33:54 -0700138 sys.exit(col.Color(col.RED, msg))
Simon Glass5abab202014-12-01 17:33:57 -0700139 elif count == 0:
140 sys.exit(col.Color(col.RED, "Range '%s' has no commits" %
141 options.branch))
Simon Glass2a9e2c62014-12-01 17:33:54 -0700142 if msg:
143 print col.Color(col.YELLOW, msg)
Simon Glassfea58582014-08-09 15:32:59 -0600144 count += 1 # Build upstream commit also
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000145
146 if not count:
147 str = ("No commits found to process in branch '%s': "
148 "set branch's upstream or use -c flag" % options.branch)
Masahiro Yamada31e21412014-08-16 00:59:26 +0900149 sys.exit(col.Color(col.RED, str))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000150
151 # Work out what subset of the boards we are building
Simon Glass823e60b2014-09-05 19:00:16 -0600152 if not boards:
153 board_file = os.path.join(options.git, 'boards.cfg')
154 status = subprocess.call([os.path.join(options.git,
155 'tools/genboardscfg.py')])
156 if status != 0:
157 sys.exit("Failed to generate boards.cfg")
Masahiro Yamada73f30b92014-07-30 14:08:22 +0900158
Simon Glass823e60b2014-09-05 19:00:16 -0600159 boards = board.Boards()
160 boards.ReadBoards(os.path.join(options.git, 'boards.cfg'))
Simon Glass3cf4ae62014-08-28 09:43:41 -0600161
162 exclude = []
163 if options.exclude:
164 for arg in options.exclude:
165 exclude += arg.split(',')
166
167 why_selected = boards.SelectBoards(args, exclude)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000168 selected = boards.GetSelected()
169 if not len(selected):
Masahiro Yamada31e21412014-08-16 00:59:26 +0900170 sys.exit(col.Color(col.RED, 'No matching boards found'))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000171
172 # Read the metadata from the commits. First look at the upstream commit,
173 # then the ones in the branch. We would like to do something like
174 # upstream/master~..branch but that isn't possible if upstream/master is
175 # a merge commit (it will list all the commits that form part of the
176 # merge)
Simon Glass950a2312014-09-05 19:00:23 -0600177 # Conflicting tags are not a problem for buildman, since it does not use
178 # them. For example, Series-version is not useful for buildman. On the
179 # other hand conflicting tags will cause an error. So allow later tags
180 # to overwrite earlier ones by setting allow_overwrite=True
Simon Glassfea58582014-08-09 15:32:59 -0600181 if options.branch:
Simon Glass3b74ba52014-08-09 15:33:09 -0600182 if count == -1:
Simon Glass5abab202014-12-01 17:33:57 -0700183 if has_range:
184 range_expr = options.branch
185 else:
186 range_expr = gitutil.GetRangeInBranch(options.git_dir,
187 options.branch)
Simon Glass3b74ba52014-08-09 15:33:09 -0600188 upstream_commit = gitutil.GetUpstream(options.git_dir,
189 options.branch)
190 series = patchstream.GetMetaDataForList(upstream_commit,
Simon Glass950a2312014-09-05 19:00:23 -0600191 options.git_dir, 1, series=None, allow_overwrite=True)
Simon Glassfea58582014-08-09 15:32:59 -0600192
Simon Glass3b74ba52014-08-09 15:33:09 -0600193 series = patchstream.GetMetaDataForList(range_expr,
Simon Glass950a2312014-09-05 19:00:23 -0600194 options.git_dir, None, series, allow_overwrite=True)
Simon Glass3b74ba52014-08-09 15:33:09 -0600195 else:
196 # Honour the count
197 series = patchstream.GetMetaDataForList(options.branch,
Simon Glass950a2312014-09-05 19:00:23 -0600198 options.git_dir, count, series=None, allow_overwrite=True)
Simon Glassfea58582014-08-09 15:32:59 -0600199 else:
200 series = None
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600201 options.verbose = True
Simon Glass58d818f2014-10-15 14:37:25 +0200202 if not options.summary:
203 options.show_errors = True
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000204
205 # By default we have one thread per CPU. But if there are not enough jobs
206 # we can have fewer threads and use a high '-j' value for make.
207 if not options.threads:
208 options.threads = min(multiprocessing.cpu_count(), len(selected))
209 if not options.jobs:
210 options.jobs = max(1, (multiprocessing.cpu_count() +
211 len(selected) - 1) / len(selected))
212
213 if not options.step:
214 options.step = len(series.commits) - 1
215
Masahiro Yamada99796922014-07-22 11:19:09 +0900216 gnu_make = command.Output(os.path.join(options.git,
217 'scripts/show-gnu-make')).rstrip()
218 if not gnu_make:
Masahiro Yamada31e21412014-08-16 00:59:26 +0900219 sys.exit('GNU Make not found')
Masahiro Yamada99796922014-07-22 11:19:09 +0900220
Simon Glass05c96b12014-12-01 17:33:52 -0700221 # Create a new builder with the selected options.
222 output_dir = options.output_dir
Simon Glassfea58582014-08-09 15:32:59 -0600223 if options.branch:
Simon Glassf7582ce2014-09-05 19:00:22 -0600224 dirname = options.branch.replace('/', '_')
Simon Glass5971ab52014-12-01 17:33:55 -0700225 # As a special case allow the board directory to be placed in the
226 # output directory itself rather than any subdirectory.
227 if not options.no_subdirs:
228 output_dir = os.path.join(options.output_dir, dirname)
Simon Glass07401272014-12-01 17:33:56 -0700229 if (clean_dir and output_dir != options.output_dir and
230 os.path.exists(output_dir)):
Simon Glass883a3212014-09-05 19:00:18 -0600231 shutil.rmtree(output_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000232 builder = Builder(toolchains, output_dir, options.git_dir,
Masahiro Yamada99796922014-07-22 11:19:09 +0900233 options.threads, options.jobs, gnu_make=gnu_make, checkout=True,
Simon Glass5971ab52014-12-01 17:33:55 -0700234 show_unknown=options.show_unknown, step=options.step,
Simon Glassbb1501f2014-12-01 17:34:00 -0700235 no_subdirs=options.no_subdirs, full_path=options.full_path)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000236 builder.force_config_on_failure = not options.quick
Simon Glassd4144e42014-09-05 19:00:13 -0600237 if make_func:
238 builder.do_make = make_func
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000239
240 # For a dry run, just show our actions as a sanity check
241 if options.dry_run:
242 ShowActions(series, why_selected, selected, builder, options)
243 else:
244 builder.force_build = options.force_build
Simon Glass4266dc22014-07-13 12:22:31 -0600245 builder.force_build_failures = options.force_build_failures
Simon Glass97e91522014-07-14 17:51:02 -0600246 builder.force_reconfig = options.force_reconfig
Simon Glass189a4962014-07-14 17:51:03 -0600247 builder.in_tree = options.in_tree
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000248
249 # Work out which boards to build
250 board_selected = boards.GetSelectedDict()
251
Simon Glassfea58582014-08-09 15:32:59 -0600252 if series:
253 commits = series.commits
Simon Glass883a3212014-09-05 19:00:18 -0600254 # Number the commits for test purposes
255 for commit in range(len(commits)):
256 commits[commit].sequence = commit
Simon Glassfea58582014-08-09 15:32:59 -0600257 else:
258 commits = None
259
Simon Glassd4144e42014-09-05 19:00:13 -0600260 Print(GetActionSummary(options.summary, commits, board_selected,
261 options))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000262
Simon Glass7798e222014-09-14 20:23:16 -0600263 # We can't show function sizes without board details at present
264 if options.show_bloat:
265 options.show_detail = True
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600266 builder.SetDisplayOptions(options.show_errors, options.show_sizes,
Simon Glassed966652014-08-28 09:43:43 -0600267 options.show_detail, options.show_bloat,
268 options.list_error_boards)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000269 if options.summary:
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600270 builder.ShowSummary(commits, board_selected)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000271 else:
Simon Glass2c3deb92014-08-28 09:43:39 -0600272 fail, warned = builder.BuildBoards(commits, board_selected,
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600273 options.keep_outputs, options.verbose)
Simon Glass2c3deb92014-08-28 09:43:39 -0600274 if fail:
275 return 128
276 elif warned:
277 return 129
278 return 0