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, boards=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 board: Boards() object to use, containing a list of available 94 boards. If this is None it will be created and scanned. 95 """ 96 if options.full_help: 97 pager = os.getenv('PAGER') 98 if not pager: 99 pager = 'more' 100 fname = os.path.join(os.path.dirname(sys.argv[0]), 'README') 101 command.Run(pager, fname) 102 return 0 103 104 gitutil.Setup() 105 106 options.git_dir = os.path.join(options.git, '.git') 107 108 if not toolchains: 109 toolchains = toolchain.Toolchains() 110 toolchains.GetSettings() 111 toolchains.Scan(options.list_tool_chains) 112 if options.list_tool_chains: 113 toolchains.List() 114 print 115 return 0 116 117 # Work out how many commits to build. We want to build everything on the 118 # branch. We also build the upstream commit as a control so we can see 119 # problems introduced by the first commit on the branch. 120 col = terminal.Color() 121 count = options.count 122 if count == -1: 123 if not options.branch: 124 count = 1 125 else: 126 count = gitutil.CountCommitsInBranch(options.git_dir, 127 options.branch) 128 if count is None: 129 str = ("Branch '%s' not found or has no upstream" % 130 options.branch) 131 sys.exit(col.Color(col.RED, str)) 132 count += 1 # Build upstream commit also 133 134 if not count: 135 str = ("No commits found to process in branch '%s': " 136 "set branch's upstream or use -c flag" % options.branch) 137 sys.exit(col.Color(col.RED, str)) 138 139 # Work out what subset of the boards we are building 140 if not boards: 141 board_file = os.path.join(options.git, 'boards.cfg') 142 status = subprocess.call([os.path.join(options.git, 143 'tools/genboardscfg.py')]) 144 if status != 0: 145 sys.exit("Failed to generate boards.cfg") 146 147 boards = board.Boards() 148 boards.ReadBoards(os.path.join(options.git, 'boards.cfg')) 149 150 exclude = [] 151 if options.exclude: 152 for arg in options.exclude: 153 exclude += arg.split(',') 154 155 why_selected = boards.SelectBoards(args, exclude) 156 selected = boards.GetSelected() 157 if not len(selected): 158 sys.exit(col.Color(col.RED, 'No matching boards found')) 159 160 # Read the metadata from the commits. First look at the upstream commit, 161 # then the ones in the branch. We would like to do something like 162 # upstream/master~..branch but that isn't possible if upstream/master is 163 # a merge commit (it will list all the commits that form part of the 164 # merge) 165 if options.branch: 166 if count == -1: 167 range_expr = gitutil.GetRangeInBranch(options.git_dir, 168 options.branch) 169 upstream_commit = gitutil.GetUpstream(options.git_dir, 170 options.branch) 171 series = patchstream.GetMetaDataForList(upstream_commit, 172 options.git_dir, 1) 173 174 # Conflicting tags are not a problem for buildman, since it does 175 # not use them. For example, Series-version is not useful for 176 # buildman. On the other hand conflicting tags will cause an 177 # error. So allow later tags to overwrite earlier ones. 178 series.allow_overwrite = True 179 series = patchstream.GetMetaDataForList(range_expr, 180 options.git_dir, None, series) 181 else: 182 # Honour the count 183 series = patchstream.GetMetaDataForList(options.branch, 184 options.git_dir, count) 185 else: 186 series = None 187 options.verbose = True 188 options.show_errors = True 189 190 # By default we have one thread per CPU. But if there are not enough jobs 191 # we can have fewer threads and use a high '-j' value for make. 192 if not options.threads: 193 options.threads = min(multiprocessing.cpu_count(), len(selected)) 194 if not options.jobs: 195 options.jobs = max(1, (multiprocessing.cpu_count() + 196 len(selected) - 1) / len(selected)) 197 198 if not options.step: 199 options.step = len(series.commits) - 1 200 201 gnu_make = command.Output(os.path.join(options.git, 202 'scripts/show-gnu-make')).rstrip() 203 if not gnu_make: 204 sys.exit('GNU Make not found') 205 206 # Create a new builder with the selected options 207 if options.branch: 208 dirname = options.branch 209 else: 210 dirname = 'current' 211 output_dir = os.path.join(options.output_dir, dirname) 212 builder = Builder(toolchains, output_dir, options.git_dir, 213 options.threads, options.jobs, gnu_make=gnu_make, checkout=True, 214 show_unknown=options.show_unknown, step=options.step) 215 builder.force_config_on_failure = not options.quick 216 if make_func: 217 builder.do_make = make_func 218 219 # For a dry run, just show our actions as a sanity check 220 if options.dry_run: 221 ShowActions(series, why_selected, selected, builder, options) 222 else: 223 builder.force_build = options.force_build 224 builder.force_build_failures = options.force_build_failures 225 builder.force_reconfig = options.force_reconfig 226 builder.in_tree = options.in_tree 227 228 # Work out which boards to build 229 board_selected = boards.GetSelectedDict() 230 231 if series: 232 commits = series.commits 233 else: 234 commits = None 235 236 Print(GetActionSummary(options.summary, commits, board_selected, 237 options)) 238 239 builder.SetDisplayOptions(options.show_errors, options.show_sizes, 240 options.show_detail, options.show_bloat, 241 options.list_error_boards) 242 if options.summary: 243 # We can't show function sizes without board details at present 244 if options.show_bloat: 245 options.show_detail = True 246 builder.ShowSummary(commits, board_selected) 247 else: 248 fail, warned = builder.BuildBoards(commits, board_selected, 249 options.keep_outputs, options.verbose) 250 if fail: 251 return 128 252 elif warned: 253 return 129 254 return 0 255