xref: /rk3399_rockchip-uboot/tools/buildman/control.py (revision 823e60b62a98061a662536093d46f8f26f6d318f)
1# Copyright (c) 2013 The Chromium OS Authors.
2#
3# SPDX-License-Identifier:	GPL-2.0+
4#
5
6import multiprocessing
7import os
8import sys
9
10import board
11import bsettings
12from builder import Builder
13import gitutil
14import patchstream
15import terminal
16from terminal import Print
17import toolchain
18import command
19import subprocess
20
21def GetPlural(count):
22    """Returns a plural 's' if count is not 1"""
23    return 's' if count != 1 else ''
24
25def 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
44def 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
81def 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
255