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