]> git.kernelconcepts.de Git - karo-tx-uboot.git/blob - tools/buildman/control.py
buildman: Add an option to specify the buildman config file
[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 import toolchain
17 import command
18 import subprocess
19
20 def GetPlural(count):
21     """Returns a plural 's' if count is not 1"""
22     return 's' if count != 1 else ''
23
24 def GetActionSummary(is_summary, commits, selected, options):
25     """Return a string summarising the intended action.
26
27     Returns:
28         Summary string.
29     """
30     if commits:
31         count = len(commits)
32         count = (count + options.step - 1) / options.step
33         commit_str = '%d commit%s' % (count, GetPlural(count))
34     else:
35         commit_str = 'current source'
36     str = '%s %s for %d boards' % (
37         'Summary of' if is_summary else 'Building', commit_str,
38         len(selected))
39     str += ' (%d thread%s, %d job%s per thread)' % (options.threads,
40             GetPlural(options.threads), options.jobs, GetPlural(options.jobs))
41     return str
42
43 def ShowActions(series, why_selected, boards_selected, builder, options):
44     """Display a list of actions that we would take, if not a dry run.
45
46     Args:
47         series: Series object
48         why_selected: Dictionary where each key is a buildman argument
49                 provided by the user, and the value is the boards brought
50                 in by that argument. For example, 'arm' might bring in
51                 400 boards, so in this case the key would be 'arm' and
52                 the value would be a list of board names.
53         boards_selected: Dict of selected boards, key is target name,
54                 value is Board object
55         builder: The builder that will be used to build the commits
56         options: Command line options object
57     """
58     col = terminal.Color()
59     print 'Dry run, so not doing much. But I would do this:'
60     print
61     if series:
62         commits = series.commits
63     else:
64         commits = None
65     print GetActionSummary(False, commits, boards_selected,
66             options)
67     print 'Build directory: %s' % builder.base_dir
68     if commits:
69         for upto in range(0, len(series.commits), options.step):
70             commit = series.commits[upto]
71             print '   ', col.Color(col.YELLOW, commit.hash, bright=False),
72             print commit.subject
73     print
74     for arg in why_selected:
75         if arg != 'all':
76             print arg, ': %d boards' % why_selected[arg]
77     print ('Total boards to build for each commit: %d\n' %
78             why_selected['all'])
79
80 def DoBuildman(options, args):
81     """The main control code for buildman
82
83     Args:
84         options: Command line options object
85         args: Command line arguments (list of strings)
86     """
87     gitutil.Setup()
88
89     bsettings.Setup(options.config_file)
90     options.git_dir = os.path.join(options.git, '.git')
91
92     toolchains = toolchain.Toolchains()
93     toolchains.Scan(options.list_tool_chains)
94     if options.list_tool_chains:
95         toolchains.List()
96         print
97         return
98
99     # Work out how many commits to build. We want to build everything on the
100     # branch. We also build the upstream commit as a control so we can see
101     # problems introduced by the first commit on the branch.
102     col = terminal.Color()
103     count = options.count
104     if count == -1:
105         if not options.branch:
106             count = 1
107         else:
108             count = gitutil.CountCommitsInBranch(options.git_dir,
109                                                  options.branch)
110             if count is None:
111                 str = ("Branch '%s' not found or has no upstream" %
112                        options.branch)
113                 print col.Color(col.RED, str)
114                 sys.exit(1)
115             count += 1   # Build upstream commit also
116
117     if not count:
118         str = ("No commits found to process in branch '%s': "
119                "set branch's upstream or use -c flag" % options.branch)
120         print col.Color(col.RED, str)
121         sys.exit(1)
122
123     # Work out what subset of the boards we are building
124     board_file = os.path.join(options.git, 'boards.cfg')
125     if not os.path.exists(board_file):
126         print 'Could not find %s' % board_file
127         status = subprocess.call([os.path.join(options.git,
128                                                'tools/genboardscfg.py')])
129         if status != 0:
130             print >> sys.stderr, "Failed to generate boards.cfg"
131             sys.exit(1)
132
133     boards = board.Boards()
134     boards.ReadBoards(os.path.join(options.git, 'boards.cfg'))
135     why_selected = boards.SelectBoards(args)
136     selected = boards.GetSelected()
137     if not len(selected):
138         print col.Color(col.RED, 'No matching boards found')
139         sys.exit(1)
140
141     # Read the metadata from the commits. First look at the upstream commit,
142     # then the ones in the branch. We would like to do something like
143     # upstream/master~..branch but that isn't possible if upstream/master is
144     # a merge commit (it will list all the commits that form part of the
145     # merge)
146     if options.branch:
147         range_expr = gitutil.GetRangeInBranch(options.git_dir, options.branch)
148         upstream_commit = gitutil.GetUpstream(options.git_dir, options.branch)
149         series = patchstream.GetMetaDataForList(upstream_commit,
150             options.git_dir, 1)
151
152         # Conflicting tags are not a problem for buildman, since it does not
153         # use them. For example, Series-version is not useful for buildman. On
154         # the other hand conflicting tags will cause an error. So allow later
155         # tags to overwrite earlier ones.
156         series.allow_overwrite = True
157         series = patchstream.GetMetaDataForList(range_expr, options.git_dir, None,
158                 series)
159     else:
160         series = None
161         options.verbose = True
162         options.show_errors = True
163
164     # By default we have one thread per CPU. But if there are not enough jobs
165     # we can have fewer threads and use a high '-j' value for make.
166     if not options.threads:
167         options.threads = min(multiprocessing.cpu_count(), len(selected))
168     if not options.jobs:
169         options.jobs = max(1, (multiprocessing.cpu_count() +
170                 len(selected) - 1) / len(selected))
171
172     if not options.step:
173         options.step = len(series.commits) - 1
174
175     gnu_make = command.Output(os.path.join(options.git,
176                                            'scripts/show-gnu-make')).rstrip()
177     if not gnu_make:
178         print >> sys.stderr, 'GNU Make not found'
179         sys.exit(1)
180
181     # Create a new builder with the selected options
182     if options.branch:
183         dirname = options.branch
184     else:
185         dirname = 'current'
186     output_dir = os.path.join(options.output_dir, dirname)
187     builder = Builder(toolchains, output_dir, options.git_dir,
188             options.threads, options.jobs, gnu_make=gnu_make, checkout=True,
189             show_unknown=options.show_unknown, step=options.step)
190     builder.force_config_on_failure = not options.quick
191
192     # For a dry run, just show our actions as a sanity check
193     if options.dry_run:
194         ShowActions(series, why_selected, selected, builder, options)
195     else:
196         builder.force_build = options.force_build
197         builder.force_build_failures = options.force_build_failures
198         builder.force_reconfig = options.force_reconfig
199         builder.in_tree = options.in_tree
200
201         # Work out which boards to build
202         board_selected = boards.GetSelectedDict()
203
204         if series:
205             commits = series.commits
206         else:
207             commits = None
208
209         print GetActionSummary(options.summary, commits, board_selected,
210                                options)
211
212         builder.SetDisplayOptions(options.show_errors, options.show_sizes,
213                                   options.show_detail, options.show_bloat)
214         if options.summary:
215             # We can't show function sizes without board details at present
216             if options.show_bloat:
217                 options.show_detail = True
218             builder.ShowSummary(commits, board_selected)
219         else:
220             builder.BuildBoards(commits, board_selected,
221                                 options.keep_outputs, options.verbose)