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