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