xref: /rk3399_rockchip-uboot/tools/buildman/control.py (revision e5a0e5d84239c71a156a0a14d9332c5532f2411e)
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        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)
222