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, 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) 100 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) 104 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 113 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 122 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) 139 # 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 144 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 158 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 164 # Create a new builder with the selected options 165 output_dir = os.path.join(options.output_dir, options.branch) 166 builder = Builder(toolchains, output_dir, options.git_dir, 167 options.threads, options.jobs, gnu_make=gnu_make, checkout=True, 168 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 176 builder.force_build_failures = options.force_build_failures 177 builder.force_reconfig = options.force_reconfig 178 builder.in_tree = options.in_tree 179 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) 195