]> git.sur5r.net Git - u-boot/blob - tools/buildman/control.py
buildman: add option -E for treating compiler warnings as errors
[u-boot] / tools / buildman / control.py
1 # Copyright (c) 2013 The Chromium OS Authors.
2 #
3 # SPDX-License-Identifier:      GPL-2.0+
4 #
5
6 import multiprocessing
7 import os
8 import shutil
9 import sys
10
11 import board
12 import bsettings
13 from builder import Builder
14 import gitutil
15 import patchstream
16 import terminal
17 from terminal import Print
18 import toolchain
19 import command
20 import subprocess
21
22 def GetPlural(count):
23     """Returns a plural 's' if count is not 1"""
24     return 's' if count != 1 else ''
25
26 def GetActionSummary(is_summary, commits, selected, options):
27     """Return a string summarising the intended action.
28
29     Returns:
30         Summary string.
31     """
32     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,
40         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
45 def 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 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
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
63     if series:
64         commits = series.commits
65     else:
66         commits = None
67     print GetActionSummary(False, commits, boards_selected,
68             options)
69     print 'Build directory: %s' % builder.base_dir
70     if commits:
71         for upto in range(0, len(series.commits), options.step):
72             commit = series.commits[upto]
73             print '   ', col.Color(col.YELLOW, commit.hash[:8], bright=False),
74             print commit.subject
75     print
76     for arg in why_selected:
77         if arg != 'all':
78             print arg, ': %d boards' % len(why_selected[arg])
79             if options.verbose:
80                 print '   %s' % ' '.join(why_selected[arg])
81     print ('Total boards to build for each commit: %d\n' %
82             len(why_selected['all']))
83
84 def DoBuildman(options, args, toolchains=None, make_func=None, boards=None,
85                clean_dir=False):
86     """The main control code for buildman
87
88     Args:
89         options: Command line options object
90         args: Command line arguments (list of strings)
91         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.
97         board: Boards() object to use, containing a list of available
98                 boards. If this is None it will be created and scanned.
99     """
100     global builder
101
102     if options.full_help:
103         pager = os.getenv('PAGER')
104         if not pager:
105             pager = 'more'
106         fname = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])),
107                              'README')
108         command.Run(pager, fname)
109         return 0
110
111     gitutil.Setup()
112     col = terminal.Color()
113
114     options.git_dir = os.path.join(options.git, '.git')
115
116     no_toolchains = toolchains is None
117     if no_toolchains:
118         toolchains = toolchain.Toolchains()
119
120     if options.fetch_arch:
121         if options.fetch_arch == 'list':
122             sorted_list = toolchains.ListArchs()
123             print col.Color(col.BLUE, 'Available architectures: %s\n' %
124                             ' '.join(sorted_list))
125             return 0
126         else:
127             fetch_arch = options.fetch_arch
128             if fetch_arch == 'all':
129                 fetch_arch = ','.join(toolchains.ListArchs())
130                 print col.Color(col.CYAN, '\nDownloading toolchains: %s' %
131                                 fetch_arch)
132             for arch in fetch_arch.split(','):
133                 print
134                 ret = toolchains.FetchAndInstall(arch)
135                 if ret:
136                     return ret
137             return 0
138
139     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
147     # 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.
150     count = options.count
151     has_range = options.branch and '..' in options.branch
152     if count == -1:
153         if not options.branch:
154             count = 1
155         else:
156             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)
162             if count is None:
163                 sys.exit(col.Color(col.RED, msg))
164             elif count == 0:
165                 sys.exit(col.Color(col.RED, "Range '%s' has no commits" %
166                                    options.branch))
167             if msg:
168                 print col.Color(col.YELLOW, msg)
169             count += 1   # Build upstream commit also
170
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)
174         sys.exit(col.Color(col.RED, str))
175
176     # Work out what subset of the boards we are building
177     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")
183
184         boards = board.Boards()
185         boards.ReadBoards(os.path.join(options.git, 'boards.cfg'))
186
187     exclude = []
188     if options.exclude:
189         for arg in options.exclude:
190             exclude += arg.split(',')
191
192     why_selected = boards.SelectBoards(args, exclude)
193     selected = boards.GetSelected()
194     if not len(selected):
195         sys.exit(col.Color(col.RED, 'No matching boards found'))
196
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)
202     # 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
206     if options.branch:
207         if count == -1:
208             if has_range:
209                 range_expr = options.branch
210             else:
211                 range_expr = gitutil.GetRangeInBranch(options.git_dir,
212                                                       options.branch)
213             upstream_commit = gitutil.GetUpstream(options.git_dir,
214                                                   options.branch)
215             series = patchstream.GetMetaDataForList(upstream_commit,
216                 options.git_dir, 1, series=None, allow_overwrite=True)
217
218             series = patchstream.GetMetaDataForList(range_expr,
219                     options.git_dir, None, series, allow_overwrite=True)
220         else:
221             # Honour the count
222             series = patchstream.GetMetaDataForList(options.branch,
223                     options.git_dir, count, series=None, allow_overwrite=True)
224     else:
225         series = None
226         if not options.dry_run:
227             options.verbose = True
228             if not options.summary:
229                 options.show_errors = True
230
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
242     gnu_make = command.Output(os.path.join(options.git,
243             'scripts/show-gnu-make'), raise_on_error=False).rstrip()
244     if not gnu_make:
245         sys.exit('GNU Make not found')
246
247     # Create a new builder with the selected options.
248     output_dir = options.output_dir
249     if options.branch:
250         dirname = options.branch.replace('/', '_')
251         # 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)
255     if (clean_dir and output_dir != options.output_dir and
256             os.path.exists(output_dir)):
257         shutil.rmtree(output_dir)
258     builder = Builder(toolchains, output_dir, options.git_dir,
259             options.threads, options.jobs, gnu_make=gnu_make, checkout=True,
260             show_unknown=options.show_unknown, step=options.step,
261             no_subdirs=options.no_subdirs, full_path=options.full_path,
262             verbose_build=options.verbose_build,
263             incremental=options.incremental,
264             per_board_out_dir=options.per_board_out_dir,
265             config_only=options.config_only,
266             squash_config_y=not options.preserve_config_y,
267             warnings_as_errors=options.warnings_as_errors)
268     builder.force_config_on_failure = not options.quick
269     if make_func:
270         builder.do_make = make_func
271
272     # For a dry run, just show our actions as a sanity check
273     if options.dry_run:
274         ShowActions(series, why_selected, selected, builder, options)
275     else:
276         builder.force_build = options.force_build
277         builder.force_build_failures = options.force_build_failures
278         builder.force_reconfig = options.force_reconfig
279         builder.in_tree = options.in_tree
280
281         # Work out which boards to build
282         board_selected = boards.GetSelectedDict()
283
284         if series:
285             commits = series.commits
286             # Number the commits for test purposes
287             for commit in range(len(commits)):
288                 commits[commit].sequence = commit
289         else:
290             commits = None
291
292         Print(GetActionSummary(options.summary, commits, board_selected,
293                                 options))
294
295         # We can't show function sizes without board details at present
296         if options.show_bloat:
297             options.show_detail = True
298         builder.SetDisplayOptions(options.show_errors, options.show_sizes,
299                                   options.show_detail, options.show_bloat,
300                                   options.list_error_boards,
301                                   options.show_config)
302         if options.summary:
303             builder.ShowSummary(commits, board_selected)
304         else:
305             fail, warned = builder.BuildBoards(commits, board_selected,
306                                 options.keep_outputs, options.verbose)
307             if fail:
308                 return 128
309             elif warned:
310                 return 129
311     return 0