xref: /rk3399_rockchip-uboot/tools/buildman/control.py (revision 6933b5c9f3b6844b5fc3b81b3c8157cb17eaea3e)
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
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        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        print >> sys.stderr, 'GNU Make not found'
186        sys.exit(1)
187
188    # Create a new builder with the selected options
189    if options.branch:
190        dirname = options.branch
191    else:
192        dirname = 'current'
193    output_dir = os.path.join(options.output_dir, dirname)
194    builder = Builder(toolchains, output_dir, options.git_dir,
195            options.threads, options.jobs, gnu_make=gnu_make, checkout=True,
196            show_unknown=options.show_unknown, step=options.step)
197    builder.force_config_on_failure = not options.quick
198
199    # For a dry run, just show our actions as a sanity check
200    if options.dry_run:
201        ShowActions(series, why_selected, selected, builder, options)
202    else:
203        builder.force_build = options.force_build
204        builder.force_build_failures = options.force_build_failures
205        builder.force_reconfig = options.force_reconfig
206        builder.in_tree = options.in_tree
207
208        # Work out which boards to build
209        board_selected = boards.GetSelectedDict()
210
211        if series:
212            commits = series.commits
213        else:
214            commits = None
215
216        print GetActionSummary(options.summary, commits, board_selected,
217                               options)
218
219        builder.SetDisplayOptions(options.show_errors, options.show_sizes,
220                                  options.show_detail, options.show_bloat)
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            builder.BuildBoards(commits, board_selected,
228                                options.keep_outputs, options.verbose)
229