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