blob: 75b6498e651e3cfb22a986028aaf3793df975a6f [file] [log] [blame]
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001# Copyright (c) 2013 The Chromium OS Authors.
2#
Wolfgang Denk1a459662013-07-08 09:37:19 +02003# SPDX-License-Identifier: GPL-2.0+
Simon Glassfc3fe1c2013-04-03 11:07:16 +00004#
5
6import multiprocessing
7import os
8import sys
9
10import board
11import bsettings
12from builder import Builder
13import gitutil
14import patchstream
15import terminal
16import toolchain
Masahiro Yamada99796922014-07-22 11:19:09 +090017import command
Masahiro Yamada73f30b92014-07-30 14:08:22 +090018import subprocess
Simon Glassfc3fe1c2013-04-03 11:07:16 +000019
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)
Simon Glasscce717a2013-05-08 08:06:08 +0000100 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)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000104 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
Masahiro Yamada73f30b92014-07-30 14:08:22 +0900113 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
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000122 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)
Simon Glassf0b739f2013-05-02 14:46:02 +0000139 # 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
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000144 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
Masahiro Yamada99796922014-07-22 11:19:09 +0900158 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
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000164 # Create a new builder with the selected options
Daniel Schwierzecke0ba9292014-04-17 21:13:11 +0200165 output_dir = os.path.join(options.output_dir, options.branch)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000166 builder = Builder(toolchains, output_dir, options.git_dir,
Masahiro Yamada99796922014-07-22 11:19:09 +0900167 options.threads, options.jobs, gnu_make=gnu_make, checkout=True,
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000168 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
Simon Glass4266dc22014-07-13 12:22:31 -0600176 builder.force_build_failures = options.force_build_failures
Simon Glass97e91522014-07-14 17:51:02 -0600177 builder.force_reconfig = options.force_reconfig
Simon Glass189a4962014-07-14 17:51:03 -0600178 builder.in_tree = options.in_tree
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000179
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)