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