]> git.kernelconcepts.de Git - karo-tx-uboot.git/blob - tools/buildman/control.py
buildman: Allow tests to have their own boards
[karo-tx-uboot.git] / 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, boards=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         board: Boards() object to use, containing a list of available
94                 boards. If this is None it will be created and scanned.
95     """
96     if options.full_help:
97         pager = os.getenv('PAGER')
98         if not pager:
99             pager = 'more'
100         fname = os.path.join(os.path.dirname(sys.argv[0]), 'README')
101         command.Run(pager, fname)
102         return 0
103
104     gitutil.Setup()
105
106     options.git_dir = os.path.join(options.git, '.git')
107
108     if not toolchains:
109         toolchains = toolchain.Toolchains()
110         toolchains.GetSettings()
111         toolchains.Scan(options.list_tool_chains)
112     if options.list_tool_chains:
113         toolchains.List()
114         print
115         return 0
116
117     # Work out how many commits to build. We want to build everything on the
118     # branch. We also build the upstream commit as a control so we can see
119     # problems introduced by the first commit on the branch.
120     col = terminal.Color()
121     count = options.count
122     if count == -1:
123         if not options.branch:
124             count = 1
125         else:
126             count = gitutil.CountCommitsInBranch(options.git_dir,
127                                                  options.branch)
128             if count is None:
129                 str = ("Branch '%s' not found or has no upstream" %
130                        options.branch)
131                 sys.exit(col.Color(col.RED, str))
132             count += 1   # Build upstream commit also
133
134     if not count:
135         str = ("No commits found to process in branch '%s': "
136                "set branch's upstream or use -c flag" % options.branch)
137         sys.exit(col.Color(col.RED, str))
138
139     # Work out what subset of the boards we are building
140     if not boards:
141         board_file = os.path.join(options.git, 'boards.cfg')
142         status = subprocess.call([os.path.join(options.git,
143                                                 'tools/genboardscfg.py')])
144         if status != 0:
145                 sys.exit("Failed to generate boards.cfg")
146
147         boards = board.Boards()
148         boards.ReadBoards(os.path.join(options.git, 'boards.cfg'))
149
150     exclude = []
151     if options.exclude:
152         for arg in options.exclude:
153             exclude += arg.split(',')
154
155     why_selected = boards.SelectBoards(args, exclude)
156     selected = boards.GetSelected()
157     if not len(selected):
158         sys.exit(col.Color(col.RED, 'No matching boards found'))
159
160     # Read the metadata from the commits. First look at the upstream commit,
161     # then the ones in the branch. We would like to do something like
162     # upstream/master~..branch but that isn't possible if upstream/master is
163     # a merge commit (it will list all the commits that form part of the
164     # merge)
165     if options.branch:
166         if count == -1:
167             range_expr = gitutil.GetRangeInBranch(options.git_dir,
168                                                   options.branch)
169             upstream_commit = gitutil.GetUpstream(options.git_dir,
170                                                   options.branch)
171             series = patchstream.GetMetaDataForList(upstream_commit,
172                 options.git_dir, 1)
173
174             # Conflicting tags are not a problem for buildman, since it does
175             # not use them. For example, Series-version is not useful for
176             # buildman. On the other hand conflicting tags will cause an
177             # error. So allow later tags to overwrite earlier ones.
178             series.allow_overwrite = True
179             series = patchstream.GetMetaDataForList(range_expr,
180                                               options.git_dir, None, series)
181         else:
182             # Honour the count
183             series = patchstream.GetMetaDataForList(options.branch,
184                                                     options.git_dir, count)
185     else:
186         series = None
187         options.verbose = True
188         options.show_errors = True
189
190     # By default we have one thread per CPU. But if there are not enough jobs
191     # we can have fewer threads and use a high '-j' value for make.
192     if not options.threads:
193         options.threads = min(multiprocessing.cpu_count(), len(selected))
194     if not options.jobs:
195         options.jobs = max(1, (multiprocessing.cpu_count() +
196                 len(selected) - 1) / len(selected))
197
198     if not options.step:
199         options.step = len(series.commits) - 1
200
201     gnu_make = command.Output(os.path.join(options.git,
202                                            'scripts/show-gnu-make')).rstrip()
203     if not gnu_make:
204         sys.exit('GNU Make not found')
205
206     # Create a new builder with the selected options
207     if options.branch:
208         dirname = options.branch
209     else:
210         dirname = 'current'
211     output_dir = os.path.join(options.output_dir, dirname)
212     builder = Builder(toolchains, output_dir, options.git_dir,
213             options.threads, options.jobs, gnu_make=gnu_make, checkout=True,
214             show_unknown=options.show_unknown, step=options.step)
215     builder.force_config_on_failure = not options.quick
216     if make_func:
217         builder.do_make = make_func
218
219     # For a dry run, just show our actions as a sanity check
220     if options.dry_run:
221         ShowActions(series, why_selected, selected, builder, options)
222     else:
223         builder.force_build = options.force_build
224         builder.force_build_failures = options.force_build_failures
225         builder.force_reconfig = options.force_reconfig
226         builder.in_tree = options.in_tree
227
228         # Work out which boards to build
229         board_selected = boards.GetSelectedDict()
230
231         if series:
232             commits = series.commits
233         else:
234             commits = None
235
236         Print(GetActionSummary(options.summary, commits, board_selected,
237                                 options))
238
239         builder.SetDisplayOptions(options.show_errors, options.show_sizes,
240                                   options.show_detail, options.show_bloat,
241                                   options.list_error_boards)
242         if options.summary:
243             # We can't show function sizes without board details at present
244             if options.show_bloat:
245                 options.show_detail = True
246             builder.ShowSummary(commits, board_selected)
247         else:
248             fail, warned = builder.BuildBoards(commits, board_selected,
249                                 options.keep_outputs, options.verbose)
250             if fail:
251                 return 128
252             elif warned:
253                 return 129
254     return 0