xref: /rk3399_rockchip-uboot/tools/buildman/control.py (revision c6fcb603a2b92978a9cc0ecd654091eaddfeefd3)
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
16import toolchain
17import command
18import subprocess
19
20def GetPlural(count):
21    """Returns a plural 's' if count is not 1"""
22    return 's' if count != 1 else ''
23
24def 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
43def 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
80def 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 0
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                sys.exit(col.Color(col.RED, str))
114            count += 1   # Build upstream commit also
115
116    if not count:
117        str = ("No commits found to process in branch '%s': "
118               "set branch's upstream or use -c flag" % options.branch)
119        sys.exit(col.Color(col.RED, str))
120
121    # Work out what subset of the boards we are building
122    board_file = os.path.join(options.git, 'boards.cfg')
123    status = subprocess.call([os.path.join(options.git,
124                                           'tools/genboardscfg.py')])
125    if status != 0:
126        sys.exit("Failed to generate boards.cfg")
127
128    boards = board.Boards()
129    boards.ReadBoards(os.path.join(options.git, 'boards.cfg'))
130
131    exclude = []
132    if options.exclude:
133        for arg in options.exclude:
134            exclude += arg.split(',')
135
136    why_selected = boards.SelectBoards(args, exclude)
137    selected = boards.GetSelected()
138    if not len(selected):
139        sys.exit(col.Color(col.RED, 'No matching boards found'))
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        if count == -1:
148            range_expr = gitutil.GetRangeInBranch(options.git_dir,
149                                                  options.branch)
150            upstream_commit = gitutil.GetUpstream(options.git_dir,
151                                                  options.branch)
152            series = patchstream.GetMetaDataForList(upstream_commit,
153                options.git_dir, 1)
154
155            # Conflicting tags are not a problem for buildman, since it does
156            # not use them. For example, Series-version is not useful for
157            # buildman. On the other hand conflicting tags will cause an
158            # error. So allow later tags to overwrite earlier ones.
159            series.allow_overwrite = True
160            series = patchstream.GetMetaDataForList(range_expr,
161                                              options.git_dir, None, series)
162        else:
163            # Honour the count
164            series = patchstream.GetMetaDataForList(options.branch,
165                                                    options.git_dir, count)
166    else:
167        series = None
168        options.verbose = True
169        options.show_errors = True
170
171    # By default we have one thread per CPU. But if there are not enough jobs
172    # we can have fewer threads and use a high '-j' value for make.
173    if not options.threads:
174        options.threads = min(multiprocessing.cpu_count(), len(selected))
175    if not options.jobs:
176        options.jobs = max(1, (multiprocessing.cpu_count() +
177                len(selected) - 1) / len(selected))
178
179    if not options.step:
180        options.step = len(series.commits) - 1
181
182    gnu_make = command.Output(os.path.join(options.git,
183                                           'scripts/show-gnu-make')).rstrip()
184    if not gnu_make:
185        sys.exit('GNU Make not found')
186
187    # Create a new builder with the selected options
188    if options.branch:
189        dirname = options.branch
190    else:
191        dirname = 'current'
192    output_dir = os.path.join(options.output_dir, dirname)
193    builder = Builder(toolchains, output_dir, options.git_dir,
194            options.threads, options.jobs, gnu_make=gnu_make, checkout=True,
195            show_unknown=options.show_unknown, step=options.step)
196    builder.force_config_on_failure = not options.quick
197
198    # For a dry run, just show our actions as a sanity check
199    if options.dry_run:
200        ShowActions(series, why_selected, selected, builder, options)
201    else:
202        builder.force_build = options.force_build
203        builder.force_build_failures = options.force_build_failures
204        builder.force_reconfig = options.force_reconfig
205        builder.in_tree = options.in_tree
206
207        # Work out which boards to build
208        board_selected = boards.GetSelectedDict()
209
210        if series:
211            commits = series.commits
212        else:
213            commits = None
214
215        print GetActionSummary(options.summary, commits, board_selected,
216                               options)
217
218        builder.SetDisplayOptions(options.show_errors, options.show_sizes,
219                                  options.show_detail, options.show_bloat,
220                                  options.list_error_boards)
221        if options.summary:
222            # We can't show function sizes without board details at present
223            if options.show_bloat:
224                options.show_detail = True
225            builder.ShowSummary(commits, board_selected)
226        else:
227            fail, warned = builder.BuildBoards(commits, board_selected,
228                                options.keep_outputs, options.verbose)
229            if fail:
230                return 128
231            elif warned:
232                return 129
233    return 0
234