Merge branch 'buildpatman' of http://git.denx.de/u-boot-x86
[platform/kernel/u-boot.git] / tools / buildman / control.py
1 # Copyright (c) 2013 The Chromium OS Authors.
2 #
3 # SPDX-License-Identifier:      GPL-2.0+
4 #
5
6 import multiprocessing
7 import os
8 import sys
9
10 import board
11 import bsettings
12 from builder import Builder
13 import gitutil
14 import patchstream
15 import terminal
16 import toolchain
17
18 def GetPlural(count):
19     """Returns a plural 's' if count is not 1"""
20     return 's' if count != 1 else ''
21
22 def 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
36 def 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
68 def 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.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)