| #!/usr/bin/python |
| |
| # Copyright (c) 2011 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """This module uprevs a given package's ebuild to the next revision.""" |
| |
| import optparse |
| import os |
| import subprocess |
| import sys |
| |
| import constants |
| if __name__ == '__main__': |
| sys.path.insert(0, constants.SOURCE_ROOT) |
| |
| from chromite.buildbot import portage_utilities |
| from chromite.lib import cros_build_lib |
| |
| |
| # TODO(sosa): Remove during OO refactor. |
| VERBOSE = False |
| |
| # Dictionary of valid commands with usage information. |
| COMMAND_DICTIONARY = { |
| 'commit': |
| 'Marks given ebuilds as stable locally', |
| 'push': |
| 'Pushes previous marking of ebuilds to remote repo', |
| } |
| |
| |
| # ======================= Global Helper Functions ======================== |
| |
| |
| def _Print(message): |
| """Verbose print function.""" |
| if VERBOSE: |
| cros_build_lib.Info(message) |
| |
| |
| def CleanStalePackages(boards, package_atoms): |
| """Cleans up stale package info from a previous build. |
| Args: |
| boards: Boards to clean the packages from. |
| package_atoms: The actual package atom to unmerge. |
| """ |
| if package_atoms: |
| cros_build_lib.Info('Cleaning up stale packages %s.' % package_atoms) |
| for board in boards: |
| unmerge_board_cmd = ['emerge-%s' % board, '--unmerge'] |
| unmerge_board_cmd.extend(package_atoms) |
| cros_build_lib.RunCommand(unmerge_board_cmd) |
| |
| unmerge_host_cmd = ['emerge', '--unmerge'] |
| unmerge_host_cmd.extend(package_atoms) |
| cros_build_lib.SudoRunCommand(unmerge_host_cmd) |
| |
| for board in boards: |
| cros_build_lib.RunCommand(['eclean-%s' % board, '-d', 'packages'], |
| redirect_stderr=True) |
| cros_build_lib.SudoRunCommand(['eclean', '-d', 'packages'], |
| redirect_stderr=True) |
| |
| |
| def _DoWeHaveLocalCommits(stable_branch, tracking_branch): |
| """Returns true if there are local commits.""" |
| current_branch = _SimpleRunCommand('git branch | grep \*').split()[1] |
| if current_branch == stable_branch: |
| current_commit_id = _SimpleRunCommand('git rev-parse HEAD') |
| tracking_commit_id = _SimpleRunCommand('git rev-parse %s' % tracking_branch) |
| return current_commit_id != tracking_commit_id |
| else: |
| return False |
| |
| |
| def _CheckSaneArguments(package_list, command, options): |
| """Checks to make sure the flags are sane. Dies if arguments are not sane.""" |
| if not command in COMMAND_DICTIONARY.keys(): |
| _PrintUsageAndDie('%s is not a valid command' % command) |
| if not options.packages and command == 'commit' and not options.all: |
| _PrintUsageAndDie('Please specify at least one package') |
| if not options.boards and command == 'commit': |
| _PrintUsageAndDie('Please specify a board') |
| if not os.path.isdir(options.srcroot): |
| _PrintUsageAndDie('srcroot is not a valid path') |
| options.srcroot = os.path.abspath(options.srcroot) |
| |
| |
| def _PrintUsageAndDie(error_message=''): |
| """Prints optional error_message the usage and returns an error exit code.""" |
| command_usage = 'Commands: \n' |
| # Add keys and usage information from dictionary. |
| commands = sorted(COMMAND_DICTIONARY.keys()) |
| for command in commands: |
| command_usage += ' %s: %s\n' % (command, COMMAND_DICTIONARY[command]) |
| commands_str = '|'.join(commands) |
| cros_build_lib.Warning('Usage: %s FLAGS [%s]\n\n%s' % ( |
| sys.argv[0], commands_str, command_usage)) |
| if error_message: |
| cros_build_lib.Die(error_message) |
| else: |
| sys.exit(1) |
| |
| |
| def _SimpleRunCommand(command): |
| """Runs a shell command and returns stdout back to caller.""" |
| _Print(' + %s' % command) |
| proc_handle = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True) |
| stdout = proc_handle.communicate()[0] |
| retcode = proc_handle.wait() |
| if retcode != 0: |
| _Print(stdout) |
| raise subprocess.CalledProcessError(retcode, command) |
| return stdout |
| |
| |
| # ======================= End Global Helper Functions ======================== |
| |
| |
| def PushChange(stable_branch, tracking_branch, dryrun, cwd='.'): |
| """Pushes commits in the stable_branch to the remote git repository. |
| |
| Pushes local commits from calls to CommitChange to the remote git |
| repository specified by current working directory. If changes are |
| found to commit, they will be merged to the merge branch and pushed. |
| In that case, the local repository will be left on the merge branch. |
| |
| Args: |
| stable_branch: The local branch with commits we want to push. |
| tracking_branch: The tracking branch of the local branch. |
| dryrun: Use git push --dryrun to emulate a push. |
| Raises: |
| OSError: Error occurred while pushing. |
| """ |
| if not _DoWeHaveLocalCommits(stable_branch, tracking_branch): |
| cros_build_lib.Info('No work found to push. Exiting') |
| return |
| |
| # For the commit queue, our local branch may contain commits that were |
| # just tested and pushed during the CommitQueueCompletion stage. Sync |
| # and rebase our local branch on top of the remote commits. |
| remote, push_branch = cros_build_lib.GetPushBranch(cwd) |
| cros_build_lib.SyncPushBranch(cwd, remote, push_branch) |
| |
| # Check whether any local changes remain after the sync. |
| if not _DoWeHaveLocalCommits(stable_branch, '%s/%s' % (remote, push_branch)): |
| cros_build_lib.Info('All changes already pushed. Exiting') |
| return |
| |
| description = _SimpleRunCommand('git log --format=format:%s%n%n%b ' + |
| '%s/%s..%s' % (remote, push_branch, stable_branch)) |
| description = 'Marking set of ebuilds as stable\n\n%s' % description |
| cros_build_lib.Info('Using description %s' % description) |
| cros_build_lib.CreatePushBranch(constants.MERGE_BRANCH, cwd) |
| _SimpleRunCommand('git merge --squash %s' % stable_branch) |
| cros_build_lib.RunCommand(['git', 'commit', '-m', description]) |
| _SimpleRunCommand('git config push.default tracking') |
| cros_build_lib.GitPushWithRetry(constants.MERGE_BRANCH, cwd=cwd, |
| dryrun=dryrun) |
| |
| |
| class GitBranch(object): |
| """Wrapper class for a git branch.""" |
| |
| def __init__(self, branch_name, tracking_branch): |
| """Sets up variables but does not create the branch.""" |
| self.branch_name = branch_name |
| self.tracking_branch = tracking_branch |
| |
| def CreateBranch(self): |
| GitBranch.Checkout(self) |
| |
| @classmethod |
| def Checkout(cls, target): |
| """Function used to check out to another GitBranch.""" |
| if target.branch_name == target.tracking_branch or target.Exists(): |
| git_cmd = 'git checkout %s -f' % target.branch_name |
| else: |
| git_cmd = 'repo start %s .' % target.branch_name |
| _SimpleRunCommand(git_cmd) |
| |
| def Exists(self): |
| """Returns True if the branch exists.""" |
| branch_cmd = 'git branch' |
| branches = _SimpleRunCommand(branch_cmd) |
| return self.branch_name in branches.split() |
| |
| def Delete(self): |
| """Deletes the branch and returns the user to the master branch. |
| |
| Returns True on success. |
| """ |
| tracking_branch = GitBranch(self.tracking_branch, self.tracking_branch) |
| GitBranch.Checkout(tracking_branch) |
| delete_cmd = 'repo abandon %s' % self.branch_name |
| _SimpleRunCommand(delete_cmd) |
| |
| |
| def main(): |
| parser = optparse.OptionParser('cros_mark_as_stable OPTIONS packages') |
| parser.add_option('--all', action='store_true', |
| help='Mark all packages as stable.') |
| parser.add_option('-b', '--boards', |
| help='Colon-separated list of boards') |
| parser.add_option('--drop_file', |
| help='File to list packages that were revved.') |
| parser.add_option('--dryrun', action='store_true', |
| help='Passes dry-run to git push if pushing a change.') |
| parser.add_option('-o', '--overlays', |
| help='Colon-separated list of overlays to modify.') |
| parser.add_option('-p', '--packages', |
| help='Colon separated list of packages to rev.') |
| parser.add_option('-r', '--srcroot', |
| default='%s/trunk/src' % os.environ['HOME'], |
| help='Path to root src directory.') |
| parser.add_option('--verbose', action='store_true', |
| help='Prints out debug info.') |
| (options, args) = parser.parse_args() |
| |
| global VERBOSE |
| VERBOSE = options.verbose |
| portage_utilities.EBuild.VERBOSE = options.verbose |
| |
| if len(args) != 1: |
| _PrintUsageAndDie('Must specify a valid command [commit, push]') |
| |
| command = args[0] |
| package_list = None |
| if options.packages: |
| package_list = options.packages.split(':') |
| |
| _CheckSaneArguments(package_list, command, options) |
| if options.overlays: |
| overlays = {} |
| for path in options.overlays.split(':'): |
| if not os.path.isdir(path): |
| cros_build_lib.Die('Cannot find overlay: %s' % path) |
| overlays[path] = [] |
| else: |
| cros_build_lib.Warning('Missing --overlays argument') |
| overlays = { |
| '%s/private-overlays/chromeos-overlay' % options.srcroot: [], |
| '%s/third_party/chromiumos-overlay' % options.srcroot: [] |
| } |
| |
| if command == 'commit': |
| portage_utilities.BuildEBuildDictionary( |
| overlays, options.all, package_list) |
| |
| tracking_branch = cros_build_lib.GetManifestDefaultBranch(options.srcroot) |
| tracking_branch = 'remotes/m/' + tracking_branch |
| |
| # Contains the array of packages we actually revved. |
| revved_packages = [] |
| new_package_atoms = [] |
| |
| for overlay, ebuilds in overlays.items(): |
| if not os.path.isdir(overlay): |
| cros_build_lib.Warning("Skipping %s" % overlay) |
| continue |
| |
| # TODO(davidjames): Currently, all code that interacts with git depends on |
| # the cwd being set to the overlay directory. We should instead pass in |
| # this parameter so that we don't need to modify the cwd globally. |
| os.chdir(overlay) |
| |
| if command == 'push': |
| PushChange(constants.STABLE_EBUILD_BRANCH, tracking_branch, |
| options.dryrun) |
| elif command == 'commit' and ebuilds: |
| existing_branch = cros_build_lib.GetCurrentBranch('.') |
| work_branch = GitBranch(constants.STABLE_EBUILD_BRANCH, tracking_branch) |
| work_branch.CreateBranch() |
| if not work_branch.Exists(): |
| cros_build_lib.Die('Unable to create stabilizing branch in %s' % |
| overlay) |
| |
| # In the case of uprevving overlays that have patches applied to them, |
| # include the patched changes in the stabilizing branch. |
| if existing_branch: |
| _SimpleRunCommand('git rebase %s' % existing_branch) |
| |
| for ebuild in ebuilds: |
| try: |
| _Print('Working on %s' % ebuild.package) |
| new_package = ebuild.RevWorkOnEBuild(options.srcroot) |
| if new_package: |
| revved_packages.append(ebuild.package) |
| new_package_atoms.append('=%s' % new_package) |
| except (OSError, IOError): |
| cros_build_lib.Warning('Cannot rev %s\n' % ebuild.package + |
| 'Note you will have to go into %s ' |
| 'and reset the git repo yourself.' % overlay) |
| raise |
| |
| if command == 'commit': |
| CleanStalePackages(options.boards.split(':'), new_package_atoms) |
| if options.drop_file: |
| fh = open(options.drop_file, 'w') |
| fh.write(' '.join(revved_packages)) |
| fh.close() |
| |
| |
| if __name__ == '__main__': |
| main() |