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