blob: 747c80d76a57d2c6f5f0e80ea5f9e242ae93430a [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
126 if count == -1:
127 if not options.branch:
Simon Glassfea58582014-08-09 15:32:59 -0600128 count = 1
129 else:
Simon Glass2a9e2c62014-12-01 17:33:54 -0700130 count, msg = gitutil.CountCommitsInBranch(options.git_dir,
131 options.branch)
Simon Glassfea58582014-08-09 15:32:59 -0600132 if count is None:
Simon Glass2a9e2c62014-12-01 17:33:54 -0700133 sys.exit(col.Color(col.RED, msg))
134 if msg:
135 print col.Color(col.YELLOW, msg)
Simon Glassfea58582014-08-09 15:32:59 -0600136 count += 1 # Build upstream commit also
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000137
138 if not count:
139 str = ("No commits found to process in branch '%s': "
140 "set branch's upstream or use -c flag" % options.branch)
Masahiro Yamada31e21412014-08-16 00:59:26 +0900141 sys.exit(col.Color(col.RED, str))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000142
143 # Work out what subset of the boards we are building
Simon Glass823e60b2014-09-05 19:00:16 -0600144 if not boards:
145 board_file = os.path.join(options.git, 'boards.cfg')
146 status = subprocess.call([os.path.join(options.git,
147 'tools/genboardscfg.py')])
148 if status != 0:
149 sys.exit("Failed to generate boards.cfg")
Masahiro Yamada73f30b92014-07-30 14:08:22 +0900150
Simon Glass823e60b2014-09-05 19:00:16 -0600151 boards = board.Boards()
152 boards.ReadBoards(os.path.join(options.git, 'boards.cfg'))
Simon Glass3cf4ae62014-08-28 09:43:41 -0600153
154 exclude = []
155 if options.exclude:
156 for arg in options.exclude:
157 exclude += arg.split(',')
158
159 why_selected = boards.SelectBoards(args, exclude)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000160 selected = boards.GetSelected()
161 if not len(selected):
Masahiro Yamada31e21412014-08-16 00:59:26 +0900162 sys.exit(col.Color(col.RED, 'No matching boards found'))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000163
164 # Read the metadata from the commits. First look at the upstream commit,
165 # then the ones in the branch. We would like to do something like
166 # upstream/master~..branch but that isn't possible if upstream/master is
167 # a merge commit (it will list all the commits that form part of the
168 # merge)
Simon Glass950a2312014-09-05 19:00:23 -0600169 # Conflicting tags are not a problem for buildman, since it does not use
170 # them. For example, Series-version is not useful for buildman. On the
171 # other hand conflicting tags will cause an error. So allow later tags
172 # to overwrite earlier ones by setting allow_overwrite=True
Simon Glassfea58582014-08-09 15:32:59 -0600173 if options.branch:
Simon Glass3b74ba52014-08-09 15:33:09 -0600174 if count == -1:
175 range_expr = gitutil.GetRangeInBranch(options.git_dir,
176 options.branch)
177 upstream_commit = gitutil.GetUpstream(options.git_dir,
178 options.branch)
179 series = patchstream.GetMetaDataForList(upstream_commit,
Simon Glass950a2312014-09-05 19:00:23 -0600180 options.git_dir, 1, series=None, allow_overwrite=True)
Simon Glassfea58582014-08-09 15:32:59 -0600181
Simon Glass3b74ba52014-08-09 15:33:09 -0600182 series = patchstream.GetMetaDataForList(range_expr,
Simon Glass950a2312014-09-05 19:00:23 -0600183 options.git_dir, None, series, allow_overwrite=True)
Simon Glass3b74ba52014-08-09 15:33:09 -0600184 else:
185 # Honour the count
186 series = patchstream.GetMetaDataForList(options.branch,
Simon Glass950a2312014-09-05 19:00:23 -0600187 options.git_dir, count, series=None, allow_overwrite=True)
Simon Glassfea58582014-08-09 15:32:59 -0600188 else:
189 series = None
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600190 options.verbose = True
Simon Glass58d818f2014-10-15 14:37:25 +0200191 if not options.summary:
192 options.show_errors = True
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000193
194 # By default we have one thread per CPU. But if there are not enough jobs
195 # we can have fewer threads and use a high '-j' value for make.
196 if not options.threads:
197 options.threads = min(multiprocessing.cpu_count(), len(selected))
198 if not options.jobs:
199 options.jobs = max(1, (multiprocessing.cpu_count() +
200 len(selected) - 1) / len(selected))
201
202 if not options.step:
203 options.step = len(series.commits) - 1
204
Masahiro Yamada99796922014-07-22 11:19:09 +0900205 gnu_make = command.Output(os.path.join(options.git,
206 'scripts/show-gnu-make')).rstrip()
207 if not gnu_make:
Masahiro Yamada31e21412014-08-16 00:59:26 +0900208 sys.exit('GNU Make not found')
Masahiro Yamada99796922014-07-22 11:19:09 +0900209
Simon Glass05c96b12014-12-01 17:33:52 -0700210 # Create a new builder with the selected options.
211 output_dir = options.output_dir
Simon Glassfea58582014-08-09 15:32:59 -0600212 if options.branch:
Simon Glassf7582ce2014-09-05 19:00:22 -0600213 dirname = options.branch.replace('/', '_')
Simon Glass5971ab52014-12-01 17:33:55 -0700214 # As a special case allow the board directory to be placed in the
215 # output directory itself rather than any subdirectory.
216 if not options.no_subdirs:
217 output_dir = os.path.join(options.output_dir, dirname)
Simon Glass883a3212014-09-05 19:00:18 -0600218 if clean_dir and os.path.exists(output_dir):
219 shutil.rmtree(output_dir)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000220 builder = Builder(toolchains, output_dir, options.git_dir,
Masahiro Yamada99796922014-07-22 11:19:09 +0900221 options.threads, options.jobs, gnu_make=gnu_make, checkout=True,
Simon Glass5971ab52014-12-01 17:33:55 -0700222 show_unknown=options.show_unknown, step=options.step,
223 no_subdirs=options.no_subdirs)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000224 builder.force_config_on_failure = not options.quick
Simon Glassd4144e42014-09-05 19:00:13 -0600225 if make_func:
226 builder.do_make = make_func
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000227
228 # For a dry run, just show our actions as a sanity check
229 if options.dry_run:
230 ShowActions(series, why_selected, selected, builder, options)
231 else:
232 builder.force_build = options.force_build
Simon Glass4266dc22014-07-13 12:22:31 -0600233 builder.force_build_failures = options.force_build_failures
Simon Glass97e91522014-07-14 17:51:02 -0600234 builder.force_reconfig = options.force_reconfig
Simon Glass189a4962014-07-14 17:51:03 -0600235 builder.in_tree = options.in_tree
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000236
237 # Work out which boards to build
238 board_selected = boards.GetSelectedDict()
239
Simon Glassfea58582014-08-09 15:32:59 -0600240 if series:
241 commits = series.commits
Simon Glass883a3212014-09-05 19:00:18 -0600242 # Number the commits for test purposes
243 for commit in range(len(commits)):
244 commits[commit].sequence = commit
Simon Glassfea58582014-08-09 15:32:59 -0600245 else:
246 commits = None
247
Simon Glassd4144e42014-09-05 19:00:13 -0600248 Print(GetActionSummary(options.summary, commits, board_selected,
249 options))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000250
Simon Glass7798e222014-09-14 20:23:16 -0600251 # We can't show function sizes without board details at present
252 if options.show_bloat:
253 options.show_detail = True
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600254 builder.SetDisplayOptions(options.show_errors, options.show_sizes,
Simon Glassed966652014-08-28 09:43:43 -0600255 options.show_detail, options.show_bloat,
256 options.list_error_boards)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000257 if options.summary:
Simon Glassb2ea7ab2014-08-09 15:33:02 -0600258 builder.ShowSummary(commits, board_selected)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000259 else:
Simon Glass2c3deb92014-08-28 09:43:39 -0600260 fail, warned = builder.BuildBoards(commits, board_selected,
Simon Glasse5a0e5d2014-08-09 15:33:03 -0600261 options.keep_outputs, options.verbose)
Simon Glass2c3deb92014-08-28 09:43:39 -0600262 if fail:
263 return 128
264 elif warned:
265 return 129
266 return 0