]> git.sur5r.net Git - u-boot/blob - tools/buildman/control.py
buildman: Add a functional test
[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 sys
9
10 import board
11 import bsettings
12 from builder import Builder
13 import gitutil
14 import patchstream
15 import terminal
16 from terminal import Print
17 import toolchain
18 import command
19 import subprocess
20
21 def GetPlural(count):
22     """Returns a plural 's' if count is not 1"""
23     return 's' if count != 1 else ''
24
25 def GetActionSummary(is_summary, commits, selected, options):
26     """Return a string summarising the intended action.
27
28     Returns:
29         Summary string.
30     """
31     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,
39         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
44 def 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
62     if series:
63         commits = series.commits
64     else:
65         commits = None
66     print GetActionSummary(False, commits, boards_selected,
67             options)
68     print 'Build directory: %s' % builder.base_dir
69     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
74     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
81 def DoBuildman(options, args, toolchains=None, make_func=None):
82     """The main control code for buildman
83
84     Args:
85         options: Command line options object
86         args: Command line arguments (list of strings)
87         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.
93     """
94     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
102     gitutil.Setup()
103
104     bsettings.Setup(options.config_file)
105     options.git_dir = os.path.join(options.git, '.git')
106
107     if not toolchains:
108         toolchains = toolchain.Toolchains()
109         toolchains.GetSettings()
110         toolchains.Scan(options.list_tool_chains)
111     if options.list_tool_chains:
112         toolchains.List()
113         print
114         return 0
115
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:
123             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)
130                 sys.exit(col.Color(col.RED, str))
131             count += 1   # Build upstream commit also
132
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)
136         sys.exit(col.Color(col.RED, str))
137
138     # Work out what subset of the boards we are building
139     board_file = os.path.join(options.git, 'boards.cfg')
140     status = subprocess.call([os.path.join(options.git,
141                                            'tools/genboardscfg.py')])
142     if status != 0:
143         sys.exit("Failed to generate boards.cfg")
144
145     boards = board.Boards()
146     boards.ReadBoards(os.path.join(options.git, 'boards.cfg'))
147
148     exclude = []
149     if options.exclude:
150         for arg in options.exclude:
151             exclude += arg.split(',')
152
153     why_selected = boards.SelectBoards(args, exclude)
154     selected = boards.GetSelected()
155     if not len(selected):
156         sys.exit(col.Color(col.RED, 'No matching boards found'))
157
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)
163     if options.branch:
164         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)
171
172             # 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)
183     else:
184         series = None
185         options.verbose = True
186         options.show_errors = True
187
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
199     gnu_make = command.Output(os.path.join(options.git,
200                                            'scripts/show-gnu-make')).rstrip()
201     if not gnu_make:
202         sys.exit('GNU Make not found')
203
204     # Create a new builder with the selected options
205     if options.branch:
206         dirname = options.branch
207     else:
208         dirname = 'current'
209     output_dir = os.path.join(options.output_dir, dirname)
210     builder = Builder(toolchains, output_dir, options.git_dir,
211             options.threads, options.jobs, gnu_make=gnu_make, checkout=True,
212             show_unknown=options.show_unknown, step=options.step)
213     builder.force_config_on_failure = not options.quick
214     if make_func:
215         builder.do_make = make_func
216
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
222         builder.force_build_failures = options.force_build_failures
223         builder.force_reconfig = options.force_reconfig
224         builder.in_tree = options.in_tree
225
226         # Work out which boards to build
227         board_selected = boards.GetSelectedDict()
228
229         if series:
230             commits = series.commits
231         else:
232             commits = None
233
234         Print(GetActionSummary(options.summary, commits, board_selected,
235                                 options))
236
237         builder.SetDisplayOptions(options.show_errors, options.show_sizes,
238                                   options.show_detail, options.show_bloat,
239                                   options.list_error_boards)
240         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
244             builder.ShowSummary(commits, board_selected)
245         else:
246             fail, warned = builder.BuildBoards(commits, board_selected,
247                                 options.keep_outputs, options.verbose)
248             if fail:
249                 return 128
250             elif warned:
251                 return 129
252     return 0