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