1fc3fe1c2SSimon Glass# Copyright (c) 2013 The Chromium OS Authors. 2fc3fe1c2SSimon Glass# 31a459660SWolfgang Denk# SPDX-License-Identifier: GPL-2.0+ 4fc3fe1c2SSimon Glass# 5fc3fe1c2SSimon Glass 6fc3fe1c2SSimon Glassimport multiprocessing 7fc3fe1c2SSimon Glassimport os 8883a321aSSimon Glassimport shutil 9fc3fe1c2SSimon Glassimport sys 10fc3fe1c2SSimon Glass 11fc3fe1c2SSimon Glassimport board 12fc3fe1c2SSimon Glassimport bsettings 13fc3fe1c2SSimon Glassfrom builder import Builder 14fc3fe1c2SSimon Glassimport gitutil 15fc3fe1c2SSimon Glassimport patchstream 16fc3fe1c2SSimon Glassimport terminal 17d4144e45SSimon Glassfrom terminal import Print 18fc3fe1c2SSimon Glassimport toolchain 1999796923SMasahiro Yamadaimport command 2073f30b9bSMasahiro Yamadaimport subprocess 21fc3fe1c2SSimon Glass 22fc3fe1c2SSimon Glassdef GetPlural(count): 23fc3fe1c2SSimon Glass """Returns a plural 's' if count is not 1""" 24fc3fe1c2SSimon Glass return 's' if count != 1 else '' 25fc3fe1c2SSimon Glass 26fea5858eSSimon Glassdef GetActionSummary(is_summary, commits, selected, options): 27fc3fe1c2SSimon Glass """Return a string summarising the intended action. 28fc3fe1c2SSimon Glass 29fc3fe1c2SSimon Glass Returns: 30fc3fe1c2SSimon Glass Summary string. 31fc3fe1c2SSimon Glass """ 32fea5858eSSimon Glass if commits: 33fea5858eSSimon Glass count = len(commits) 34fc3fe1c2SSimon Glass count = (count + options.step - 1) / options.step 35fea5858eSSimon Glass commit_str = '%d commit%s' % (count, GetPlural(count)) 36fea5858eSSimon Glass else: 37fea5858eSSimon Glass commit_str = 'current source' 38fea5858eSSimon Glass str = '%s %s for %d boards' % ( 39fea5858eSSimon Glass 'Summary of' if is_summary else 'Building', commit_str, 40fc3fe1c2SSimon Glass len(selected)) 41fc3fe1c2SSimon Glass str += ' (%d thread%s, %d job%s per thread)' % (options.threads, 42fc3fe1c2SSimon Glass GetPlural(options.threads), options.jobs, GetPlural(options.jobs)) 43fc3fe1c2SSimon Glass return str 44fc3fe1c2SSimon Glass 45fc3fe1c2SSimon Glassdef ShowActions(series, why_selected, boards_selected, builder, options): 46fc3fe1c2SSimon Glass """Display a list of actions that we would take, if not a dry run. 47fc3fe1c2SSimon Glass 48fc3fe1c2SSimon Glass Args: 49fc3fe1c2SSimon Glass series: Series object 50fc3fe1c2SSimon Glass why_selected: Dictionary where each key is a buildman argument 51fc3fe1c2SSimon Glass provided by the user, and the value is the boards brought 52fc3fe1c2SSimon Glass in by that argument. For example, 'arm' might bring in 53fc3fe1c2SSimon Glass 400 boards, so in this case the key would be 'arm' and 54fc3fe1c2SSimon Glass the value would be a list of board names. 55fc3fe1c2SSimon Glass boards_selected: Dict of selected boards, key is target name, 56fc3fe1c2SSimon Glass value is Board object 57fc3fe1c2SSimon Glass builder: The builder that will be used to build the commits 58fc3fe1c2SSimon Glass options: Command line options object 59fc3fe1c2SSimon Glass """ 60fc3fe1c2SSimon Glass col = terminal.Color() 61fc3fe1c2SSimon Glass print 'Dry run, so not doing much. But I would do this:' 62fc3fe1c2SSimon Glass print 63fea5858eSSimon Glass if series: 64fea5858eSSimon Glass commits = series.commits 65fea5858eSSimon Glass else: 66fea5858eSSimon Glass commits = None 67fea5858eSSimon Glass print GetActionSummary(False, commits, boards_selected, 68fc3fe1c2SSimon Glass options) 69fc3fe1c2SSimon Glass print 'Build directory: %s' % builder.base_dir 70fea5858eSSimon Glass if commits: 71fc3fe1c2SSimon Glass for upto in range(0, len(series.commits), options.step): 72fc3fe1c2SSimon Glass commit = series.commits[upto] 731ddda1b3SSimon Glass print ' ', col.Color(col.YELLOW, commit.hash[:8], bright=False), 74fc3fe1c2SSimon Glass print commit.subject 75fc3fe1c2SSimon Glass print 76fc3fe1c2SSimon Glass for arg in why_selected: 77fc3fe1c2SSimon Glass if arg != 'all': 78fc3fe1c2SSimon Glass print arg, ': %d boards' % why_selected[arg] 79fc3fe1c2SSimon Glass print ('Total boards to build for each commit: %d\n' % 80fc3fe1c2SSimon Glass why_selected['all']) 81fc3fe1c2SSimon Glass 82883a321aSSimon Glassdef DoBuildman(options, args, toolchains=None, make_func=None, boards=None, 83883a321aSSimon Glass clean_dir=False): 84fc3fe1c2SSimon Glass """The main control code for buildman 85fc3fe1c2SSimon Glass 86fc3fe1c2SSimon Glass Args: 87fc3fe1c2SSimon Glass options: Command line options object 88fc3fe1c2SSimon Glass args: Command line arguments (list of strings) 89d4144e45SSimon Glass toolchains: Toolchains to use - this should be a Toolchains() 90d4144e45SSimon Glass object. If None, then it will be created and scanned 91d4144e45SSimon Glass make_func: Make function to use for the builder. This is called 92d4144e45SSimon Glass to execute 'make'. If this is None, the normal function 93d4144e45SSimon Glass will be used, which calls the 'make' tool with suitable 94d4144e45SSimon Glass arguments. This setting is useful for tests. 95823e60b6SSimon Glass board: Boards() object to use, containing a list of available 96823e60b6SSimon Glass boards. If this is None it will be created and scanned. 97fc3fe1c2SSimon Glass """ 98883a321aSSimon Glass global builder 99883a321aSSimon Glass 10048ba5856SSimon Glass if options.full_help: 10148ba5856SSimon Glass pager = os.getenv('PAGER') 10248ba5856SSimon Glass if not pager: 10348ba5856SSimon Glass pager = 'more' 10448ba5856SSimon Glass fname = os.path.join(os.path.dirname(sys.argv[0]), 'README') 10548ba5856SSimon Glass command.Run(pager, fname) 10648ba5856SSimon Glass return 0 10748ba5856SSimon Glass 108fc3fe1c2SSimon Glass gitutil.Setup() 109fc3fe1c2SSimon Glass 110fc3fe1c2SSimon Glass options.git_dir = os.path.join(options.git, '.git') 111fc3fe1c2SSimon Glass 112d4144e45SSimon Glass if not toolchains: 113fc3fe1c2SSimon Glass toolchains = toolchain.Toolchains() 114d4144e45SSimon Glass toolchains.GetSettings() 115fc3fe1c2SSimon Glass toolchains.Scan(options.list_tool_chains) 116fc3fe1c2SSimon Glass if options.list_tool_chains: 117fc3fe1c2SSimon Glass toolchains.List() 118fc3fe1c2SSimon Glass print 1192c3deb97SSimon Glass return 0 120fc3fe1c2SSimon Glass 121fc3fe1c2SSimon Glass # Work out how many commits to build. We want to build everything on the 122fc3fe1c2SSimon Glass # branch. We also build the upstream commit as a control so we can see 123fc3fe1c2SSimon Glass # problems introduced by the first commit on the branch. 124fc3fe1c2SSimon Glass col = terminal.Color() 125fc3fe1c2SSimon Glass count = options.count 126*5abab20dSSimon Glass has_range = options.branch and '..' in options.branch 127fc3fe1c2SSimon Glass if count == -1: 128fc3fe1c2SSimon Glass if not options.branch: 129fea5858eSSimon Glass count = 1 130fea5858eSSimon Glass else: 131*5abab20dSSimon Glass if has_range: 132*5abab20dSSimon Glass count, msg = gitutil.CountCommitsInRange(options.git_dir, 133*5abab20dSSimon Glass options.branch) 134*5abab20dSSimon Glass else: 1352a9e2c6aSSimon Glass count, msg = gitutil.CountCommitsInBranch(options.git_dir, 136fea5858eSSimon Glass options.branch) 137cce717a9SSimon Glass if count is None: 1382a9e2c6aSSimon Glass sys.exit(col.Color(col.RED, msg)) 139*5abab20dSSimon Glass elif count == 0: 140*5abab20dSSimon Glass sys.exit(col.Color(col.RED, "Range '%s' has no commits" % 141*5abab20dSSimon Glass options.branch)) 1422a9e2c6aSSimon Glass if msg: 1432a9e2c6aSSimon Glass print col.Color(col.YELLOW, msg) 144fc3fe1c2SSimon Glass count += 1 # Build upstream commit also 145fc3fe1c2SSimon Glass 146fc3fe1c2SSimon Glass if not count: 147fc3fe1c2SSimon Glass str = ("No commits found to process in branch '%s': " 148fc3fe1c2SSimon Glass "set branch's upstream or use -c flag" % options.branch) 14931e2141dSMasahiro Yamada sys.exit(col.Color(col.RED, str)) 150fc3fe1c2SSimon Glass 151fc3fe1c2SSimon Glass # Work out what subset of the boards we are building 152823e60b6SSimon Glass if not boards: 15373f30b9bSMasahiro Yamada board_file = os.path.join(options.git, 'boards.cfg') 15473f30b9bSMasahiro Yamada status = subprocess.call([os.path.join(options.git, 15573f30b9bSMasahiro Yamada 'tools/genboardscfg.py')]) 15673f30b9bSMasahiro Yamada if status != 0: 15731e2141dSMasahiro Yamada sys.exit("Failed to generate boards.cfg") 15873f30b9bSMasahiro Yamada 159fc3fe1c2SSimon Glass boards = board.Boards() 160fc3fe1c2SSimon Glass boards.ReadBoards(os.path.join(options.git, 'boards.cfg')) 1613cf4ae6fSSimon Glass 1623cf4ae6fSSimon Glass exclude = [] 1633cf4ae6fSSimon Glass if options.exclude: 1643cf4ae6fSSimon Glass for arg in options.exclude: 1653cf4ae6fSSimon Glass exclude += arg.split(',') 1663cf4ae6fSSimon Glass 1673cf4ae6fSSimon Glass why_selected = boards.SelectBoards(args, exclude) 168fc3fe1c2SSimon Glass selected = boards.GetSelected() 169fc3fe1c2SSimon Glass if not len(selected): 17031e2141dSMasahiro Yamada sys.exit(col.Color(col.RED, 'No matching boards found')) 171fc3fe1c2SSimon Glass 172fc3fe1c2SSimon Glass # Read the metadata from the commits. First look at the upstream commit, 173fc3fe1c2SSimon Glass # then the ones in the branch. We would like to do something like 174fc3fe1c2SSimon Glass # upstream/master~..branch but that isn't possible if upstream/master is 175fc3fe1c2SSimon Glass # a merge commit (it will list all the commits that form part of the 176fc3fe1c2SSimon Glass # merge) 177950a2313SSimon Glass # Conflicting tags are not a problem for buildman, since it does not use 178950a2313SSimon Glass # them. For example, Series-version is not useful for buildman. On the 179950a2313SSimon Glass # other hand conflicting tags will cause an error. So allow later tags 180950a2313SSimon Glass # to overwrite earlier ones by setting allow_overwrite=True 181fea5858eSSimon Glass if options.branch: 1823b74ba5fSSimon Glass if count == -1: 183*5abab20dSSimon Glass if has_range: 184*5abab20dSSimon Glass range_expr = options.branch 185*5abab20dSSimon Glass else: 1863b74ba5fSSimon Glass range_expr = gitutil.GetRangeInBranch(options.git_dir, 1873b74ba5fSSimon Glass options.branch) 1883b74ba5fSSimon Glass upstream_commit = gitutil.GetUpstream(options.git_dir, 1893b74ba5fSSimon Glass options.branch) 190fea5858eSSimon Glass series = patchstream.GetMetaDataForList(upstream_commit, 191950a2313SSimon Glass options.git_dir, 1, series=None, allow_overwrite=True) 192fea5858eSSimon Glass 1933b74ba5fSSimon Glass series = patchstream.GetMetaDataForList(range_expr, 194950a2313SSimon Glass options.git_dir, None, series, allow_overwrite=True) 1953b74ba5fSSimon Glass else: 1963b74ba5fSSimon Glass # Honour the count 1973b74ba5fSSimon Glass series = patchstream.GetMetaDataForList(options.branch, 198950a2313SSimon Glass options.git_dir, count, series=None, allow_overwrite=True) 199fea5858eSSimon Glass else: 200fea5858eSSimon Glass series = None 201e5a0e5d8SSimon Glass options.verbose = True 20258d818f1SSimon Glass if not options.summary: 203e5a0e5d8SSimon Glass options.show_errors = True 204fc3fe1c2SSimon Glass 205fc3fe1c2SSimon Glass # By default we have one thread per CPU. But if there are not enough jobs 206fc3fe1c2SSimon Glass # we can have fewer threads and use a high '-j' value for make. 207fc3fe1c2SSimon Glass if not options.threads: 208fc3fe1c2SSimon Glass options.threads = min(multiprocessing.cpu_count(), len(selected)) 209fc3fe1c2SSimon Glass if not options.jobs: 210fc3fe1c2SSimon Glass options.jobs = max(1, (multiprocessing.cpu_count() + 211fc3fe1c2SSimon Glass len(selected) - 1) / len(selected)) 212fc3fe1c2SSimon Glass 213fc3fe1c2SSimon Glass if not options.step: 214fc3fe1c2SSimon Glass options.step = len(series.commits) - 1 215fc3fe1c2SSimon Glass 21699796923SMasahiro Yamada gnu_make = command.Output(os.path.join(options.git, 21799796923SMasahiro Yamada 'scripts/show-gnu-make')).rstrip() 21899796923SMasahiro Yamada if not gnu_make: 21931e2141dSMasahiro Yamada sys.exit('GNU Make not found') 22099796923SMasahiro Yamada 22105c96b18SSimon Glass # Create a new builder with the selected options. 22205c96b18SSimon Glass output_dir = options.output_dir 223fea5858eSSimon Glass if options.branch: 224f7582ce8SSimon Glass dirname = options.branch.replace('/', '_') 2255971ab5cSSimon Glass # As a special case allow the board directory to be placed in the 2265971ab5cSSimon Glass # output directory itself rather than any subdirectory. 2275971ab5cSSimon Glass if not options.no_subdirs: 228fea5858eSSimon Glass output_dir = os.path.join(options.output_dir, dirname) 2290740127fSSimon Glass if (clean_dir and output_dir != options.output_dir and 2300740127fSSimon Glass os.path.exists(output_dir)): 231883a321aSSimon Glass shutil.rmtree(output_dir) 232fc3fe1c2SSimon Glass builder = Builder(toolchains, output_dir, options.git_dir, 23399796923SMasahiro Yamada options.threads, options.jobs, gnu_make=gnu_make, checkout=True, 2345971ab5cSSimon Glass show_unknown=options.show_unknown, step=options.step, 2355971ab5cSSimon Glass no_subdirs=options.no_subdirs) 236fc3fe1c2SSimon Glass builder.force_config_on_failure = not options.quick 237d4144e45SSimon Glass if make_func: 238d4144e45SSimon Glass builder.do_make = make_func 239fc3fe1c2SSimon Glass 240fc3fe1c2SSimon Glass # For a dry run, just show our actions as a sanity check 241fc3fe1c2SSimon Glass if options.dry_run: 242fc3fe1c2SSimon Glass ShowActions(series, why_selected, selected, builder, options) 243fc3fe1c2SSimon Glass else: 244fc3fe1c2SSimon Glass builder.force_build = options.force_build 2454266dc28SSimon Glass builder.force_build_failures = options.force_build_failures 24697e91526SSimon Glass builder.force_reconfig = options.force_reconfig 247189a4968SSimon Glass builder.in_tree = options.in_tree 248fc3fe1c2SSimon Glass 249fc3fe1c2SSimon Glass # Work out which boards to build 250fc3fe1c2SSimon Glass board_selected = boards.GetSelectedDict() 251fc3fe1c2SSimon Glass 252fea5858eSSimon Glass if series: 253fea5858eSSimon Glass commits = series.commits 254883a321aSSimon Glass # Number the commits for test purposes 255883a321aSSimon Glass for commit in range(len(commits)): 256883a321aSSimon Glass commits[commit].sequence = commit 257fea5858eSSimon Glass else: 258fea5858eSSimon Glass commits = None 259fea5858eSSimon Glass 260d4144e45SSimon Glass Print(GetActionSummary(options.summary, commits, board_selected, 261d4144e45SSimon Glass options)) 262fc3fe1c2SSimon Glass 2637798e228SSimon Glass # We can't show function sizes without board details at present 2647798e228SSimon Glass if options.show_bloat: 2657798e228SSimon Glass options.show_detail = True 266b2ea7ab2SSimon Glass builder.SetDisplayOptions(options.show_errors, options.show_sizes, 267ed966657SSimon Glass options.show_detail, options.show_bloat, 268ed966657SSimon Glass options.list_error_boards) 269fc3fe1c2SSimon Glass if options.summary: 270b2ea7ab2SSimon Glass builder.ShowSummary(commits, board_selected) 271fc3fe1c2SSimon Glass else: 2722c3deb97SSimon Glass fail, warned = builder.BuildBoards(commits, board_selected, 273e5a0e5d8SSimon Glass options.keep_outputs, options.verbose) 2742c3deb97SSimon Glass if fail: 2752c3deb97SSimon Glass return 128 2762c3deb97SSimon Glass elif warned: 2772c3deb97SSimon Glass return 129 2782c3deb97SSimon Glass return 0 279