xref: /rk3399_rockchip-uboot/tools/buildman/control.py (revision fd03d63f347e28e4e4394245210f048c82c9b085)
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):
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    """
94    if options.full_help:
95        pager = os.getenv('PAGER')
96        if not pager:
97            pager = 'more'
98        fname = os.path.join(os.path.dirname(sys.argv[0]), 'README')
99        command.Run(pager, fname)
100        return 0
101
102    gitutil.Setup()
103
104    options.git_dir = os.path.join(options.git, '.git')
105
106    if not toolchains:
107        toolchains = toolchain.Toolchains()
108        toolchains.GetSettings()
109        toolchains.Scan(options.list_tool_chains)
110    if options.list_tool_chains:
111        toolchains.List()
112        print
113        return 0
114
115    # Work out how many commits to build. We want to build everything on the
116    # branch. We also build the upstream commit as a control so we can see
117    # problems introduced by the first commit on the branch.
118    col = terminal.Color()
119    count = options.count
120    if count == -1:
121        if not options.branch:
122            count = 1
123        else:
124            count = gitutil.CountCommitsInBranch(options.git_dir,
125                                                 options.branch)
126            if count is None:
127                str = ("Branch '%s' not found or has no upstream" %
128                       options.branch)
129                sys.exit(col.Color(col.RED, str))
130            count += 1   # Build upstream commit also
131
132    if not count:
133        str = ("No commits found to process in branch '%s': "
134               "set branch's upstream or use -c flag" % options.branch)
135        sys.exit(col.Color(col.RED, str))
136
137    # Work out what subset of the boards we are building
138    board_file = os.path.join(options.git, 'boards.cfg')
139    status = subprocess.call([os.path.join(options.git,
140                                           'tools/genboardscfg.py')])
141    if status != 0:
142        sys.exit("Failed to generate boards.cfg")
143
144    boards = board.Boards()
145    boards.ReadBoards(os.path.join(options.git, 'boards.cfg'))
146
147    exclude = []
148    if options.exclude:
149        for arg in options.exclude:
150            exclude += arg.split(',')
151
152    why_selected = boards.SelectBoards(args, exclude)
153    selected = boards.GetSelected()
154    if not len(selected):
155        sys.exit(col.Color(col.RED, 'No matching boards found'))
156
157    # Read the metadata from the commits. First look at the upstream commit,
158    # then the ones in the branch. We would like to do something like
159    # upstream/master~..branch but that isn't possible if upstream/master is
160    # a merge commit (it will list all the commits that form part of the
161    # merge)
162    if options.branch:
163        if count == -1:
164            range_expr = gitutil.GetRangeInBranch(options.git_dir,
165                                                  options.branch)
166            upstream_commit = gitutil.GetUpstream(options.git_dir,
167                                                  options.branch)
168            series = patchstream.GetMetaDataForList(upstream_commit,
169                options.git_dir, 1)
170
171            # Conflicting tags are not a problem for buildman, since it does
172            # not use them. For example, Series-version is not useful for
173            # buildman. On the other hand conflicting tags will cause an
174            # error. So allow later tags to overwrite earlier ones.
175            series.allow_overwrite = True
176            series = patchstream.GetMetaDataForList(range_expr,
177                                              options.git_dir, None, series)
178        else:
179            # Honour the count
180            series = patchstream.GetMetaDataForList(options.branch,
181                                                    options.git_dir, count)
182    else:
183        series = None
184        options.verbose = True
185        options.show_errors = True
186
187    # By default we have one thread per CPU. But if there are not enough jobs
188    # we can have fewer threads and use a high '-j' value for make.
189    if not options.threads:
190        options.threads = min(multiprocessing.cpu_count(), len(selected))
191    if not options.jobs:
192        options.jobs = max(1, (multiprocessing.cpu_count() +
193                len(selected) - 1) / len(selected))
194
195    if not options.step:
196        options.step = len(series.commits) - 1
197
198    gnu_make = command.Output(os.path.join(options.git,
199                                           'scripts/show-gnu-make')).rstrip()
200    if not gnu_make:
201        sys.exit('GNU Make not found')
202
203    # Create a new builder with the selected options
204    if options.branch:
205        dirname = options.branch
206    else:
207        dirname = 'current'
208    output_dir = os.path.join(options.output_dir, dirname)
209    builder = Builder(toolchains, output_dir, options.git_dir,
210            options.threads, options.jobs, gnu_make=gnu_make, checkout=True,
211            show_unknown=options.show_unknown, step=options.step)
212    builder.force_config_on_failure = not options.quick
213    if make_func:
214        builder.do_make = make_func
215
216    # For a dry run, just show our actions as a sanity check
217    if options.dry_run:
218        ShowActions(series, why_selected, selected, builder, options)
219    else:
220        builder.force_build = options.force_build
221        builder.force_build_failures = options.force_build_failures
222        builder.force_reconfig = options.force_reconfig
223        builder.in_tree = options.in_tree
224
225        # Work out which boards to build
226        board_selected = boards.GetSelectedDict()
227
228        if series:
229            commits = series.commits
230        else:
231            commits = None
232
233        Print(GetActionSummary(options.summary, commits, board_selected,
234                                options))
235
236        builder.SetDisplayOptions(options.show_errors, options.show_sizes,
237                                  options.show_detail, options.show_bloat,
238                                  options.list_error_boards)
239        if options.summary:
240            # We can't show function sizes without board details at present
241            if options.show_bloat:
242                options.show_detail = True
243            builder.ShowSummary(commits, board_selected)
244        else:
245            fail, warned = builder.BuildBoards(commits, board_selected,
246                                options.keep_outputs, options.verbose)
247            if fail:
248                return 128
249            elif warned:
250                return 129
251    return 0
252