xref: /openbmc/u-boot/tools/buildman/control.py (revision 0a9064fb)
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
18
19def GetPlural(count):
20    """Returns a plural 's' if count is not 1"""
21    return 's' if count != 1 else ''
22
23def GetActionSummary(is_summary, count, selected, options):
24    """Return a string summarising the intended action.
25
26    Returns:
27        Summary string.
28    """
29    count = (count + options.step - 1) / options.step
30    str = '%s %d commit%s for %d boards' % (
31        'Summary of' if is_summary else 'Building', count, GetPlural(count),
32        len(selected))
33    str += ' (%d thread%s, %d job%s per thread)' % (options.threads,
34            GetPlural(options.threads), options.jobs, GetPlural(options.jobs))
35    return str
36
37def ShowActions(series, why_selected, boards_selected, builder, options):
38    """Display a list of actions that we would take, if not a dry run.
39
40    Args:
41        series: Series object
42        why_selected: Dictionary where each key is a buildman argument
43                provided by the user, and the value is the boards brought
44                in by that argument. For example, 'arm' might bring in
45                400 boards, so in this case the key would be 'arm' and
46                the value would be a list of board names.
47        boards_selected: Dict of selected boards, key is target name,
48                value is Board object
49        builder: The builder that will be used to build the commits
50        options: Command line options object
51    """
52    col = terminal.Color()
53    print 'Dry run, so not doing much. But I would do this:'
54    print
55    print GetActionSummary(False, len(series.commits), boards_selected,
56            options)
57    print 'Build directory: %s' % builder.base_dir
58    for upto in range(0, len(series.commits), options.step):
59        commit = series.commits[upto]
60        print '   ', col.Color(col.YELLOW, commit.hash, bright=False),
61        print commit.subject
62    print
63    for arg in why_selected:
64        if arg != 'all':
65            print arg, ': %d boards' % why_selected[arg]
66    print ('Total boards to build for each commit: %d\n' %
67            why_selected['all'])
68
69def DoBuildman(options, args):
70    """The main control code for buildman
71
72    Args:
73        options: Command line options object
74        args: Command line arguments (list of strings)
75    """
76    gitutil.Setup()
77
78    bsettings.Setup()
79    options.git_dir = os.path.join(options.git, '.git')
80
81    toolchains = toolchain.Toolchains()
82    toolchains.Scan(options.list_tool_chains)
83    if options.list_tool_chains:
84        toolchains.List()
85        print
86        return
87
88    # Work out how many commits to build. We want to build everything on the
89    # branch. We also build the upstream commit as a control so we can see
90    # problems introduced by the first commit on the branch.
91    col = terminal.Color()
92    count = options.count
93    if count == -1:
94        if not options.branch:
95            str = 'Please use -b to specify a branch to build'
96            print col.Color(col.RED, str)
97            sys.exit(1)
98        count = gitutil.CountCommitsInBranch(options.git_dir, options.branch)
99        if count is None:
100            str = "Branch '%s' not found or has no upstream" % options.branch
101            print col.Color(col.RED, str)
102            sys.exit(1)
103        count += 1   # Build upstream commit also
104
105    if not count:
106        str = ("No commits found to process in branch '%s': "
107               "set branch's upstream or use -c flag" % options.branch)
108        print col.Color(col.RED, str)
109        sys.exit(1)
110
111    # Work out what subset of the boards we are building
112    boards = board.Boards()
113    boards.ReadBoards(os.path.join(options.git, 'boards.cfg'))
114    why_selected = boards.SelectBoards(args)
115    selected = boards.GetSelected()
116    if not len(selected):
117        print col.Color(col.RED, 'No matching boards found')
118        sys.exit(1)
119
120    # Read the metadata from the commits. First look at the upstream commit,
121    # then the ones in the branch. We would like to do something like
122    # upstream/master~..branch but that isn't possible if upstream/master is
123    # a merge commit (it will list all the commits that form part of the
124    # merge)
125    range_expr = gitutil.GetRangeInBranch(options.git_dir, options.branch)
126    upstream_commit = gitutil.GetUpstream(options.git_dir, options.branch)
127    series = patchstream.GetMetaDataForList(upstream_commit, options.git_dir,
128            1)
129    # Conflicting tags are not a problem for buildman, since it does not use
130    # them. For example, Series-version is not useful for buildman. On the
131    # other hand conflicting tags will cause an error. So allow later tags
132    # to overwrite earlier ones.
133    series.allow_overwrite = True
134    series = patchstream.GetMetaDataForList(range_expr, options.git_dir, None,
135            series)
136
137    # By default we have one thread per CPU. But if there are not enough jobs
138    # we can have fewer threads and use a high '-j' value for make.
139    if not options.threads:
140        options.threads = min(multiprocessing.cpu_count(), len(selected))
141    if not options.jobs:
142        options.jobs = max(1, (multiprocessing.cpu_count() +
143                len(selected) - 1) / len(selected))
144
145    if not options.step:
146        options.step = len(series.commits) - 1
147
148    gnu_make = command.Output(os.path.join(options.git,
149                                           'scripts/show-gnu-make')).rstrip()
150    if not gnu_make:
151        print >> sys.stderr, 'GNU Make not found'
152        sys.exit(1)
153
154    # Create a new builder with the selected options
155    output_dir = os.path.join(options.output_dir, options.branch)
156    builder = Builder(toolchains, output_dir, options.git_dir,
157            options.threads, options.jobs, gnu_make=gnu_make, checkout=True,
158            show_unknown=options.show_unknown, step=options.step)
159    builder.force_config_on_failure = not options.quick
160
161    # For a dry run, just show our actions as a sanity check
162    if options.dry_run:
163        ShowActions(series, why_selected, selected, builder, options)
164    else:
165        builder.force_build = options.force_build
166        builder.force_build_failures = options.force_build_failures
167        builder.force_reconfig = options.force_reconfig
168        builder.in_tree = options.in_tree
169
170        # Work out which boards to build
171        board_selected = boards.GetSelectedDict()
172
173        print GetActionSummary(options.summary, count, board_selected, options)
174
175        if options.summary:
176            # We can't show function sizes without board details at present
177            if options.show_bloat:
178                options.show_detail = True
179            builder.ShowSummary(series.commits, board_selected,
180                    options.show_errors, options.show_sizes,
181                    options.show_detail, options.show_bloat)
182        else:
183            builder.BuildBoards(series.commits, board_selected,
184                    options.show_errors, options.keep_outputs)
185