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 51*8d7523c5SSimon Glass provided by the user, and the value is the list of boards 52*8d7523c5SSimon Glass brought in by that argument. For example, 'arm' might bring 53*8d7523c5SSimon Glass in 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': 78*8d7523c5SSimon Glass print arg, ': %d boards' % len(why_selected[arg]) 79*8d7523c5SSimon Glass if options.verbose: 80*8d7523c5SSimon Glass print ' %s' % ' '.join(why_selected[arg]) 81fc3fe1c2SSimon Glass print ('Total boards to build for each commit: %d\n' % 82*8d7523c5SSimon Glass len(why_selected['all'])) 83fc3fe1c2SSimon Glass 84883a321aSSimon Glassdef DoBuildman(options, args, toolchains=None, make_func=None, boards=None, 85883a321aSSimon Glass clean_dir=False): 86fc3fe1c2SSimon Glass """The main control code for buildman 87fc3fe1c2SSimon Glass 88fc3fe1c2SSimon Glass Args: 89fc3fe1c2SSimon Glass options: Command line options object 90fc3fe1c2SSimon Glass args: Command line arguments (list of strings) 91d4144e45SSimon Glass toolchains: Toolchains to use - this should be a Toolchains() 92d4144e45SSimon Glass object. If None, then it will be created and scanned 93d4144e45SSimon Glass make_func: Make function to use for the builder. This is called 94d4144e45SSimon Glass to execute 'make'. If this is None, the normal function 95d4144e45SSimon Glass will be used, which calls the 'make' tool with suitable 96d4144e45SSimon Glass arguments. This setting is useful for tests. 97823e60b6SSimon Glass board: Boards() object to use, containing a list of available 98823e60b6SSimon Glass boards. If this is None it will be created and scanned. 99fc3fe1c2SSimon Glass """ 100883a321aSSimon Glass global builder 101883a321aSSimon Glass 10248ba5856SSimon Glass if options.full_help: 10348ba5856SSimon Glass pager = os.getenv('PAGER') 10448ba5856SSimon Glass if not pager: 10548ba5856SSimon Glass pager = 'more' 1062bdeade0SSimon Glass fname = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])), 1072bdeade0SSimon Glass 'README') 10848ba5856SSimon Glass command.Run(pager, fname) 10948ba5856SSimon Glass return 0 11048ba5856SSimon Glass 111fc3fe1c2SSimon Glass gitutil.Setup() 112713bea38SSimon Glass col = terminal.Color() 113fc3fe1c2SSimon Glass 114fc3fe1c2SSimon Glass options.git_dir = os.path.join(options.git, '.git') 115fc3fe1c2SSimon Glass 1167e92e46eSSimon Glass no_toolchains = toolchains is None 1177e92e46eSSimon Glass if no_toolchains: 118fc3fe1c2SSimon Glass toolchains = toolchain.Toolchains() 119fc3fe1c2SSimon Glass 120827e37b5SSimon Glass if options.fetch_arch: 121827e37b5SSimon Glass if options.fetch_arch == 'list': 122827e37b5SSimon Glass sorted_list = toolchains.ListArchs() 123713bea38SSimon Glass print col.Color(col.BLUE, 'Available architectures: %s\n' % 124713bea38SSimon Glass ' '.join(sorted_list)) 125827e37b5SSimon Glass return 0 126827e37b5SSimon Glass else: 127827e37b5SSimon Glass fetch_arch = options.fetch_arch 128827e37b5SSimon Glass if fetch_arch == 'all': 129827e37b5SSimon Glass fetch_arch = ','.join(toolchains.ListArchs()) 130713bea38SSimon Glass print col.Color(col.CYAN, '\nDownloading toolchains: %s' % 131713bea38SSimon Glass fetch_arch) 132827e37b5SSimon Glass for arch in fetch_arch.split(','): 133713bea38SSimon Glass print 134827e37b5SSimon Glass ret = toolchains.FetchAndInstall(arch) 135827e37b5SSimon Glass if ret: 136827e37b5SSimon Glass return ret 137827e37b5SSimon Glass return 0 138827e37b5SSimon Glass 1397e92e46eSSimon Glass if no_toolchains: 1407e92e46eSSimon Glass toolchains.GetSettings() 1417e92e46eSSimon Glass toolchains.Scan(options.list_tool_chains) 1427e92e46eSSimon Glass if options.list_tool_chains: 1437e92e46eSSimon Glass toolchains.List() 1447e92e46eSSimon Glass print 1457e92e46eSSimon Glass return 0 1467e92e46eSSimon Glass 147fc3fe1c2SSimon Glass # Work out how many commits to build. We want to build everything on the 148fc3fe1c2SSimon Glass # branch. We also build the upstream commit as a control so we can see 149fc3fe1c2SSimon Glass # problems introduced by the first commit on the branch. 150fc3fe1c2SSimon Glass count = options.count 1515abab20dSSimon Glass has_range = options.branch and '..' in options.branch 152fc3fe1c2SSimon Glass if count == -1: 153fc3fe1c2SSimon Glass if not options.branch: 154fea5858eSSimon Glass count = 1 155fea5858eSSimon Glass else: 1565abab20dSSimon Glass if has_range: 1575abab20dSSimon Glass count, msg = gitutil.CountCommitsInRange(options.git_dir, 1585abab20dSSimon Glass options.branch) 1595abab20dSSimon Glass else: 1602a9e2c6aSSimon Glass count, msg = gitutil.CountCommitsInBranch(options.git_dir, 161fea5858eSSimon Glass options.branch) 162cce717a9SSimon Glass if count is None: 1632a9e2c6aSSimon Glass sys.exit(col.Color(col.RED, msg)) 1645abab20dSSimon Glass elif count == 0: 1655abab20dSSimon Glass sys.exit(col.Color(col.RED, "Range '%s' has no commits" % 1665abab20dSSimon Glass options.branch)) 1672a9e2c6aSSimon Glass if msg: 1682a9e2c6aSSimon Glass print col.Color(col.YELLOW, msg) 169fc3fe1c2SSimon Glass count += 1 # Build upstream commit also 170fc3fe1c2SSimon Glass 171fc3fe1c2SSimon Glass if not count: 172fc3fe1c2SSimon Glass str = ("No commits found to process in branch '%s': " 173fc3fe1c2SSimon Glass "set branch's upstream or use -c flag" % options.branch) 17431e2141dSMasahiro Yamada sys.exit(col.Color(col.RED, str)) 175fc3fe1c2SSimon Glass 176fc3fe1c2SSimon Glass # Work out what subset of the boards we are building 177823e60b6SSimon Glass if not boards: 17873f30b9bSMasahiro Yamada board_file = os.path.join(options.git, 'boards.cfg') 17973f30b9bSMasahiro Yamada status = subprocess.call([os.path.join(options.git, 18073f30b9bSMasahiro Yamada 'tools/genboardscfg.py')]) 18173f30b9bSMasahiro Yamada if status != 0: 18231e2141dSMasahiro Yamada sys.exit("Failed to generate boards.cfg") 18373f30b9bSMasahiro Yamada 184fc3fe1c2SSimon Glass boards = board.Boards() 185fc3fe1c2SSimon Glass boards.ReadBoards(os.path.join(options.git, 'boards.cfg')) 1863cf4ae6fSSimon Glass 1873cf4ae6fSSimon Glass exclude = [] 1883cf4ae6fSSimon Glass if options.exclude: 1893cf4ae6fSSimon Glass for arg in options.exclude: 1903cf4ae6fSSimon Glass exclude += arg.split(',') 1913cf4ae6fSSimon Glass 1923cf4ae6fSSimon Glass why_selected = boards.SelectBoards(args, exclude) 193fc3fe1c2SSimon Glass selected = boards.GetSelected() 194fc3fe1c2SSimon Glass if not len(selected): 19531e2141dSMasahiro Yamada sys.exit(col.Color(col.RED, 'No matching boards found')) 196fc3fe1c2SSimon Glass 197fc3fe1c2SSimon Glass # Read the metadata from the commits. First look at the upstream commit, 198fc3fe1c2SSimon Glass # then the ones in the branch. We would like to do something like 199fc3fe1c2SSimon Glass # upstream/master~..branch but that isn't possible if upstream/master is 200fc3fe1c2SSimon Glass # a merge commit (it will list all the commits that form part of the 201fc3fe1c2SSimon Glass # merge) 202950a2313SSimon Glass # Conflicting tags are not a problem for buildman, since it does not use 203950a2313SSimon Glass # them. For example, Series-version is not useful for buildman. On the 204950a2313SSimon Glass # other hand conflicting tags will cause an error. So allow later tags 205950a2313SSimon Glass # to overwrite earlier ones by setting allow_overwrite=True 206fea5858eSSimon Glass if options.branch: 2073b74ba5fSSimon Glass if count == -1: 2085abab20dSSimon Glass if has_range: 2095abab20dSSimon Glass range_expr = options.branch 2105abab20dSSimon Glass else: 2113b74ba5fSSimon Glass range_expr = gitutil.GetRangeInBranch(options.git_dir, 2123b74ba5fSSimon Glass options.branch) 2133b74ba5fSSimon Glass upstream_commit = gitutil.GetUpstream(options.git_dir, 2143b74ba5fSSimon Glass options.branch) 215fea5858eSSimon Glass series = patchstream.GetMetaDataForList(upstream_commit, 216950a2313SSimon Glass options.git_dir, 1, series=None, allow_overwrite=True) 217fea5858eSSimon Glass 2183b74ba5fSSimon Glass series = patchstream.GetMetaDataForList(range_expr, 219950a2313SSimon Glass options.git_dir, None, series, allow_overwrite=True) 2203b74ba5fSSimon Glass else: 2213b74ba5fSSimon Glass # Honour the count 2223b74ba5fSSimon Glass series = patchstream.GetMetaDataForList(options.branch, 223950a2313SSimon Glass options.git_dir, count, series=None, allow_overwrite=True) 224fea5858eSSimon Glass else: 225fea5858eSSimon Glass series = None 226*8d7523c5SSimon Glass if not options.dry_run: 227e5a0e5d8SSimon Glass options.verbose = True 22858d818f1SSimon Glass if not options.summary: 229e5a0e5d8SSimon Glass options.show_errors = True 230fc3fe1c2SSimon Glass 231fc3fe1c2SSimon Glass # By default we have one thread per CPU. But if there are not enough jobs 232fc3fe1c2SSimon Glass # we can have fewer threads and use a high '-j' value for make. 233fc3fe1c2SSimon Glass if not options.threads: 234fc3fe1c2SSimon Glass options.threads = min(multiprocessing.cpu_count(), len(selected)) 235fc3fe1c2SSimon Glass if not options.jobs: 236fc3fe1c2SSimon Glass options.jobs = max(1, (multiprocessing.cpu_count() + 237fc3fe1c2SSimon Glass len(selected) - 1) / len(selected)) 238fc3fe1c2SSimon Glass 239fc3fe1c2SSimon Glass if not options.step: 240fc3fe1c2SSimon Glass options.step = len(series.commits) - 1 241fc3fe1c2SSimon Glass 24299796923SMasahiro Yamada gnu_make = command.Output(os.path.join(options.git, 243785f1548SSimon Glass 'scripts/show-gnu-make'), raise_on_error=False).rstrip() 24499796923SMasahiro Yamada if not gnu_make: 24531e2141dSMasahiro Yamada sys.exit('GNU Make not found') 24699796923SMasahiro Yamada 24705c96b18SSimon Glass # Create a new builder with the selected options. 24805c96b18SSimon Glass output_dir = options.output_dir 249fea5858eSSimon Glass if options.branch: 250f7582ce8SSimon Glass dirname = options.branch.replace('/', '_') 2515971ab5cSSimon Glass # As a special case allow the board directory to be placed in the 2525971ab5cSSimon Glass # output directory itself rather than any subdirectory. 2535971ab5cSSimon Glass if not options.no_subdirs: 254fea5858eSSimon Glass output_dir = os.path.join(options.output_dir, dirname) 2550740127fSSimon Glass if (clean_dir and output_dir != options.output_dir and 2560740127fSSimon Glass os.path.exists(output_dir)): 257883a321aSSimon Glass shutil.rmtree(output_dir) 258fc3fe1c2SSimon Glass builder = Builder(toolchains, output_dir, options.git_dir, 25999796923SMasahiro Yamada options.threads, options.jobs, gnu_make=gnu_make, checkout=True, 2605971ab5cSSimon Glass show_unknown=options.show_unknown, step=options.step, 261d2ce658dSSimon Glass no_subdirs=options.no_subdirs, full_path=options.full_path, 262f79f1e0cSStephen Warren verbose_build=options.verbose_build, 263f79f1e0cSStephen Warren incremental=options.incremental, 264b50113f3SSimon Glass per_board_out_dir=options.per_board_out_dir, 265b464f8e7SSimon Glass config_only=options.config_only, 266b464f8e7SSimon Glass squash_config_y=not options.preserve_config_y) 267fc3fe1c2SSimon Glass builder.force_config_on_failure = not options.quick 268d4144e45SSimon Glass if make_func: 269d4144e45SSimon Glass builder.do_make = make_func 270fc3fe1c2SSimon Glass 271fc3fe1c2SSimon Glass # For a dry run, just show our actions as a sanity check 272fc3fe1c2SSimon Glass if options.dry_run: 273fc3fe1c2SSimon Glass ShowActions(series, why_selected, selected, builder, options) 274fc3fe1c2SSimon Glass else: 275fc3fe1c2SSimon Glass builder.force_build = options.force_build 2764266dc28SSimon Glass builder.force_build_failures = options.force_build_failures 27797e91526SSimon Glass builder.force_reconfig = options.force_reconfig 278189a4968SSimon Glass builder.in_tree = options.in_tree 279fc3fe1c2SSimon Glass 280fc3fe1c2SSimon Glass # Work out which boards to build 281fc3fe1c2SSimon Glass board_selected = boards.GetSelectedDict() 282fc3fe1c2SSimon Glass 283fea5858eSSimon Glass if series: 284fea5858eSSimon Glass commits = series.commits 285883a321aSSimon Glass # Number the commits for test purposes 286883a321aSSimon Glass for commit in range(len(commits)): 287883a321aSSimon Glass commits[commit].sequence = commit 288fea5858eSSimon Glass else: 289fea5858eSSimon Glass commits = None 290fea5858eSSimon Glass 291d4144e45SSimon Glass Print(GetActionSummary(options.summary, commits, board_selected, 292d4144e45SSimon Glass options)) 293fc3fe1c2SSimon Glass 2947798e228SSimon Glass # We can't show function sizes without board details at present 2957798e228SSimon Glass if options.show_bloat: 2967798e228SSimon Glass options.show_detail = True 297b2ea7ab2SSimon Glass builder.SetDisplayOptions(options.show_errors, options.show_sizes, 298ed966657SSimon Glass options.show_detail, options.show_bloat, 299843312dcSSimon Glass options.list_error_boards, 300843312dcSSimon Glass options.show_config) 301fc3fe1c2SSimon Glass if options.summary: 302b2ea7ab2SSimon Glass builder.ShowSummary(commits, board_selected) 303fc3fe1c2SSimon Glass else: 3042c3deb97SSimon Glass fail, warned = builder.BuildBoards(commits, board_selected, 305e5a0e5d8SSimon Glass options.keep_outputs, options.verbose) 3062c3deb97SSimon Glass if fail: 3072c3deb97SSimon Glass return 128 3082c3deb97SSimon Glass elif warned: 3092c3deb97SSimon Glass return 129 3102c3deb97SSimon Glass return 0 311