xref: /rk3399_rockchip-uboot/tools/buildman/control.py (revision b2ea7ab25258621871db1f884be1a2f2b1641741)
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()
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
162    # By default we have one thread per CPU. But if there are not enough jobs
163    # we can have fewer threads and use a high '-j' value for make.
164    if not options.threads:
165        options.threads = min(multiprocessing.cpu_count(), len(selected))
166    if not options.jobs:
167        options.jobs = max(1, (multiprocessing.cpu_count() +
168                len(selected) - 1) / len(selected))
169
170    if not options.step:
171        options.step = len(series.commits) - 1
172
173    gnu_make = command.Output(os.path.join(options.git,
174                                           'scripts/show-gnu-make')).rstrip()
175    if not gnu_make:
176        print >> sys.stderr, 'GNU Make not found'
177        sys.exit(1)
178
179    # Create a new builder with the selected options
180    if options.branch:
181        dirname = options.branch
182    else:
183        dirname = 'current'
184    output_dir = os.path.join(options.output_dir, dirname)
185    builder = Builder(toolchains, output_dir, options.git_dir,
186            options.threads, options.jobs, gnu_make=gnu_make, checkout=True,
187            show_unknown=options.show_unknown, step=options.step)
188    builder.force_config_on_failure = not options.quick
189
190    # For a dry run, just show our actions as a sanity check
191    if options.dry_run:
192        ShowActions(series, why_selected, selected, builder, options)
193    else:
194        builder.force_build = options.force_build
195        builder.force_build_failures = options.force_build_failures
196        builder.force_reconfig = options.force_reconfig
197        builder.in_tree = options.in_tree
198
199        # Work out which boards to build
200        board_selected = boards.GetSelectedDict()
201
202        if series:
203            commits = series.commits
204        else:
205            commits = None
206
207        print GetActionSummary(options.summary, commits, board_selected,
208                               options)
209
210        builder.SetDisplayOptions(options.show_errors, options.show_sizes,
211                                  options.show_detail, options.show_bloat)
212        if options.summary:
213            # We can't show function sizes without board details at present
214            if options.show_bloat:
215                options.show_detail = True
216            builder.ShowSummary(commits, board_selected)
217        else:
218            builder.BuildBoards(commits, board_selected,
219                                options.keep_outputs)
220