| Index: cros_mark_as_stable.py
|
| diff --git a/cros_mark_as_stable.py b/cros_mark_as_stable.py
|
| index 2425ff2b02b84519dcc91e2c8ce859afcf558141..6c4a6fa51a7cb0448ace4a91ec795d028d825a31 100755
|
| --- a/cros_mark_as_stable.py
|
| +++ b/cros_mark_as_stable.py
|
| @@ -18,9 +18,12 @@ import sys
|
| sys.path.append(os.path.join(os.path.dirname(__file__), 'lib'))
|
| from cros_build_lib import Info, RunCommand, Warning, Die
|
|
|
| -
|
| +gflags.DEFINE_boolean('all', False,
|
| + 'Mark all packages as stable.')
|
| gflags.DEFINE_string('board', '',
|
| 'Board for which the package belongs.', short_name='b')
|
| +gflags.DEFINE_boolean('dryrun', False,
|
| + 'Passes dry-run to git push if pushing a change.')
|
| gflags.DEFINE_string('overlays', '',
|
| 'Colon-separated list of overlays to modify.',
|
| short_name='o')
|
| @@ -35,19 +38,16 @@ gflags.DEFINE_string('srcroot', '%s/trunk/src' % os.environ['HOME'],
|
| gflags.DEFINE_string('tracking_branch', 'cros/master',
|
| 'Used with commit to specify branch to track against.',
|
| short_name='t')
|
| -gflags.DEFINE_boolean('all', False,
|
| - 'Mark all packages as stable.')
|
| gflags.DEFINE_boolean('verbose', False,
|
| 'Prints out verbose information about what is going on.',
|
| short_name='v')
|
|
|
|
|
| # Takes two strings, package_name and commit_id.
|
| -_GIT_COMMIT_MESSAGE = \
|
| - 'Marking 9999 ebuild for %s with commit %s as stable.'
|
| +_GIT_COMMIT_MESSAGE = 'Marking 9999 ebuild for %s with commit %s as stable.'
|
|
|
| # Dictionary of valid commands with usage information.
|
| -_COMMAND_DICTIONARY = {
|
| +COMMAND_DICTIONARY = {
|
| 'clean':
|
| 'Cleans up previous calls to either commit or push',
|
| 'commit':
|
| @@ -59,6 +59,16 @@ _COMMAND_DICTIONARY = {
|
| # Name used for stabilizing branch.
|
| _STABLE_BRANCH_NAME = 'stabilizing_branch'
|
|
|
| +
|
| +def BestEBuild(ebuilds):
|
| + """Returns the newest EBuild from a list of EBuild objects."""
|
| + from portage.versions import vercmp
|
| + winner = ebuilds[0]
|
| + for ebuild in ebuilds[1:]:
|
| + if vercmp(winner.version, ebuild.version) < 0:
|
| + winner = ebuild
|
| + return winner
|
| +
|
| # ======================= Global Helper Functions ========================
|
|
|
|
|
| @@ -83,16 +93,6 @@ def _CleanStalePackages(board, package_array):
|
| RunCommand(['sudo', 'eclean', '-d', 'packages'], redirect_stderr=True)
|
|
|
|
|
| -def _BestEBuild(ebuilds):
|
| - """Returns the newest EBuild from a list of EBuild objects."""
|
| - from portage.versions import vercmp
|
| - winner = ebuilds[0]
|
| - for ebuild in ebuilds[1:]:
|
| - if vercmp(winner.version, ebuild.version) < 0:
|
| - winner = ebuild
|
| - return winner
|
| -
|
| -
|
| def _FindUprevCandidates(files):
|
| """Return a list of uprev candidates from specified list of files.
|
|
|
| @@ -108,7 +108,7 @@ def _FindUprevCandidates(files):
|
| unstable_ebuilds = []
|
| for path in files:
|
| if path.endswith('.ebuild') and not os.path.islink(path):
|
| - ebuild = _EBuild(path)
|
| + ebuild = EBuild(path)
|
| if ebuild.is_workon:
|
| workon_dir = True
|
| if ebuild.is_stable:
|
| @@ -121,7 +121,7 @@ def _FindUprevCandidates(files):
|
| if len(unstable_ebuilds) > 1:
|
| Die('Found multiple unstable ebuilds in %s' % os.path.dirname(path))
|
| if len(stable_ebuilds) > 1:
|
| - stable_ebuilds = [_BestEBuild(stable_ebuilds)]
|
| + stable_ebuilds = [BestEBuild(stable_ebuilds)]
|
|
|
| # Print a warning if multiple stable ebuilds are found in the same
|
| # directory. Storing multiple stable ebuilds is error-prone because
|
| @@ -166,15 +166,15 @@ def _BuildEBuildDictionary(overlays, all, packages):
|
| overlays[overlay].append(ebuild)
|
|
|
|
|
| -def _CheckOnStabilizingBranch():
|
| +def _CheckOnStabilizingBranch(stable_branch):
|
| """Returns true if the git branch is on the stabilizing branch."""
|
| current_branch = _SimpleRunCommand('git branch | grep \*').split()[1]
|
| - return current_branch == _STABLE_BRANCH_NAME
|
| + return current_branch == stable_branch
|
|
|
|
|
| def _CheckSaneArguments(package_list, command):
|
| """Checks to make sure the flags are sane. Dies if arguments are not sane."""
|
| - if not command in _COMMAND_DICTIONARY.keys():
|
| + if not command in COMMAND_DICTIONARY.keys():
|
| _PrintUsageAndDie('%s is not a valid command' % command)
|
| if not gflags.FLAGS.packages and command == 'commit' and not gflags.FLAGS.all:
|
| _PrintUsageAndDie('Please specify at least one package')
|
| @@ -185,19 +185,13 @@ def _CheckSaneArguments(package_list, command):
|
| gflags.FLAGS.srcroot = os.path.abspath(gflags.FLAGS.srcroot)
|
|
|
|
|
| -def _Clean():
|
| - """Cleans up uncommitted changes on either stabilizing branch or master."""
|
| - _SimpleRunCommand('git reset HEAD --hard')
|
| - _SimpleRunCommand('git checkout %s' % gflags.FLAGS.tracking_branch)
|
| -
|
| -
|
| 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())
|
| + commands = sorted(COMMAND_DICTIONARY.keys())
|
| for command in commands:
|
| - command_usage += ' %s: %s\n' % (command, _COMMAND_DICTIONARY[command])
|
| + command_usage += ' %s: %s\n' % (command, COMMAND_DICTIONARY[command])
|
| commands_str = '|'.join(commands)
|
| Warning('Usage: %s FLAGS [%s]\n\n%s\nFlags:%s' % (sys.argv[0], commands_str,
|
| command_usage, gflags.FLAGS))
|
| @@ -206,42 +200,71 @@ def _PrintUsageAndDie(error_message=''):
|
| else:
|
| sys.exit(1)
|
|
|
| -def _PushChange():
|
| - """Pushes changes to the git repository.
|
| +
|
| +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 Clean(tracking_branch):
|
| + """Cleans up uncommitted changes.
|
| +
|
| + Args:
|
| + tracking_branch: The tracking branch we want to return to after the call.
|
| + """
|
| + _SimpleRunCommand('git reset HEAD --hard')
|
| + _SimpleRunCommand('git checkout %s' % tracking_branch)
|
| +
|
| +
|
| +def PushChange(stable_branch, tracking_branch):
|
| + """Pushes commits in the stable_branch to the remote git repository.
|
|
|
| Pushes locals commits from calls to CommitChange to the remote git
|
| - repository specified by os.pwd.
|
| + repository specified by current working directory.
|
|
|
| + Args:
|
| + stable_branch: The local branch with commits we want to push.
|
| + tracking_branch: The tracking branch of the local branch.
|
| Raises:
|
| OSError: Error occurred while pushing.
|
| """
|
| num_retries = 5
|
|
|
| - # TODO(sosa) - Add logic for buildbot to check whether other slaves have
|
| - # completed and push this change only if they have.
|
| -
|
| # Sanity check to make sure we're on a stabilizing branch before pushing.
|
| - if not _CheckOnStabilizingBranch():
|
| - Info('Not on branch %s so no work found to push. Exiting' % \
|
| - _STABLE_BRANCH_NAME)
|
| + if not _CheckOnStabilizingBranch(stable_branch):
|
| + Info('Not on branch %s so no work found to push. Exiting' % stable_branch)
|
| return
|
|
|
| description = _SimpleRunCommand('git log --format=format:%s%n%n%b ' +
|
| - gflags.FLAGS.tracking_branch + '..')
|
| + tracking_branch + '..')
|
| description = 'Marking set of ebuilds as stable\n\n%s' % description
|
| + Info('Using description %s' % description)
|
| merge_branch_name = 'merge_branch'
|
| for push_try in range(num_retries + 1):
|
| try:
|
| _SimpleRunCommand('git remote update')
|
| - merge_branch = _GitBranch(merge_branch_name)
|
| + merge_branch = GitBranch(merge_branch_name, tracking_branch)
|
| merge_branch.CreateBranch()
|
| if not merge_branch.Exists():
|
| Die('Unable to create merge branch.')
|
| - _SimpleRunCommand('git merge --squash %s' % _STABLE_BRANCH_NAME)
|
| + _SimpleRunCommand('git merge --squash %s' % stable_branch)
|
| _SimpleRunCommand('git commit -m "%s"' % description)
|
| - # Ugh. There has got to be an easier way to push to a tracking branch
|
| _SimpleRunCommand('git config push.default tracking')
|
| - _SimpleRunCommand('git push')
|
| + if gflags.FLAGS.dryrun:
|
| + _SimpleRunCommand('git push --dry-run')
|
| + else:
|
| + _SimpleRunCommand('git push')
|
| +
|
| break
|
| except:
|
| if push_try < num_retries:
|
| @@ -251,27 +274,13 @@ def _PushChange():
|
| raise
|
|
|
|
|
| -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 ========================
|
| -
|
| -
|
| -class _GitBranch(object):
|
| +class GitBranch(object):
|
| """Wrapper class for a git branch."""
|
|
|
| - def __init__(self, branch_name):
|
| + 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):
|
| """Creates a new git branch or replaces an existing one."""
|
| @@ -282,7 +291,7 @@ class _GitBranch(object):
|
| def _Checkout(self, target, create=True):
|
| """Function used internally to create and move between branches."""
|
| if create:
|
| - git_cmd = 'git checkout -b %s %s' % (target, gflags.FLAGS.tracking_branch)
|
| + git_cmd = 'git checkout -b %s %s' % (target, self.tracking_branch)
|
| else:
|
| git_cmd = 'git checkout %s' % target
|
| _SimpleRunCommand(git_cmd)
|
| @@ -298,30 +307,30 @@ class _GitBranch(object):
|
|
|
| Returns True on success.
|
| """
|
| - self._Checkout(gflags.FLAGS.tracking_branch, create=False)
|
| + self._Checkout(self.tracking_branch, create=False)
|
| delete_cmd = 'git branch -D %s' % self.branch_name
|
| _SimpleRunCommand(delete_cmd)
|
|
|
|
|
| -class _EBuild(object):
|
| - """Wrapper class for an ebuild."""
|
| +class EBuild(object):
|
| + """Wrapper class for information about an ebuild."""
|
|
|
| def __init__(self, path):
|
| - """Initializes all data about an ebuild.
|
| -
|
| - Uses equery to find the ebuild path and sets data about an ebuild for
|
| - easy reference.
|
| - """
|
| + """Sets up data about an ebuild from its path."""
|
| from portage.versions import pkgsplit
|
| - self.ebuild_path = path
|
| - (self.ebuild_path_no_revision,
|
| - self.ebuild_path_no_version,
|
| - self.current_revision) = self._ParseEBuildPath(self.ebuild_path)
|
| - _, self.category, pkgpath, filename = path.rsplit('/', 3)
|
| - filename_no_suffix = os.path.join(filename.replace('.ebuild', ''))
|
| - self.pkgname, version_no_rev, rev = pkgsplit(filename_no_suffix)
|
| + unused_path, self.category, self.pkgname, filename = path.rsplit('/', 3)
|
| + unused_pkgname, version_no_rev, rev = pkgsplit(
|
| + filename.replace('.ebuild', ''))
|
| +
|
| + self.ebuild_path_no_version = os.path.join(
|
| + os.path.dirname(path), self.pkgname)
|
| + self.ebuild_path_no_revision = '%s-%s' % (self.ebuild_path_no_version,
|
| + version_no_rev)
|
| + self.current_revision = int(rev.replace('r', ''))
|
| self.version = '%s-%s' % (version_no_rev, rev)
|
| self.package = '%s/%s' % (self.category, self.pkgname)
|
| + self.ebuild_path = path
|
| +
|
| self.is_workon = False
|
| self.is_stable = False
|
|
|
| @@ -335,7 +344,6 @@ class _EBuild(object):
|
|
|
| def GetCommitId(self):
|
| """Get the commit id for this ebuild."""
|
| -
|
| # Grab and evaluate CROS_WORKON variables from this ebuild.
|
| unstable_ebuild = '%s-9999.ebuild' % self.ebuild_path_no_version
|
| cmd = ('export CROS_WORKON_LOCALNAME="%s" CROS_WORKON_PROJECT="%s"; '
|
| @@ -378,39 +386,53 @@ class _EBuild(object):
|
| Die('Missing commit id for %s' % self.ebuild_path)
|
| return output.rstrip()
|
|
|
| - @classmethod
|
| - def _ParseEBuildPath(cls, ebuild_path):
|
| - """Static method that parses the path of an ebuild
|
| -
|
| - Returns a tuple containing the (ebuild path without the revision
|
| - string, without the version string, and the current revision number for
|
| - the ebuild).
|
| - """
|
| - # Get the ebuild name without the revision string.
|
| - (ebuild_no_rev, _, rev_string) = ebuild_path.rpartition('-')
|
| -
|
| - # Verify the revision string starts with the revision character.
|
| - if rev_string.startswith('r'):
|
| - # Get the ebuild name without the revision and version strings.
|
| - ebuild_no_version = ebuild_no_rev.rpartition('-')[0]
|
| - rev_string = rev_string[1:].rpartition('.ebuild')[0]
|
| - else:
|
| - # Has no revision so we stripped the version number instead.
|
| - ebuild_no_version = ebuild_no_rev
|
| - ebuild_no_rev = ebuild_path.rpartition('9999.ebuild')[0] + '0.0.1'
|
| - rev_string = '0'
|
| - revision = int(rev_string)
|
| - return (ebuild_no_rev, ebuild_no_version, revision)
|
| -
|
|
|
| class EBuildStableMarker(object):
|
| """Class that revs the ebuild and commits locally or pushes the change."""
|
|
|
| def __init__(self, ebuild):
|
| + assert ebuild
|
| self._ebuild = ebuild
|
|
|
| - def RevEBuild(self, commit_id='', redirect_file=None):
|
| - """Revs an ebuild given the git commit id.
|
| + @classmethod
|
| + def MarkAsStable(cls, unstable_ebuild_path, new_stable_ebuild_path,
|
| + commit_keyword, commit_value, redirect_file=None):
|
| + """Static function that creates a revved stable ebuild.
|
| +
|
| + This function assumes you have already figured out the name of the new
|
| + stable ebuild path and then creates that file from the given unstable
|
| + ebuild and marks it as stable. If the commit_value is set, it also
|
| + set the commit_keyword=commit_value pair in the ebuild.
|
| +
|
| + Args:
|
| + unstable_ebuild_path: The path to the unstable ebuild.
|
| + new_stable_ebuild_path: The path you want to use for the new stable
|
| + ebuild.
|
| + commit_keyword: Optional keyword to set in the ebuild to mark it as
|
| + stable.
|
| + commit_value: Value to set the above keyword to.
|
| + redirect_file: Optionally redirect output of new ebuild somewhere else.
|
| + """
|
| + shutil.copyfile(unstable_ebuild_path, new_stable_ebuild_path)
|
| + for line in fileinput.input(new_stable_ebuild_path, inplace=1):
|
| + # Has to be done here to get changes to sys.stdout from fileinput.input.
|
| + if not redirect_file:
|
| + redirect_file = sys.stdout
|
| + if line.startswith('KEYWORDS'):
|
| + # Actually mark this file as stable by removing ~'s.
|
| + redirect_file.write(line.replace('~', ''))
|
| + elif line.startswith('EAPI'):
|
| + # Always add new commit_id after EAPI definition.
|
| + redirect_file.write(line)
|
| + if commit_keyword and commit_value:
|
| + redirect_file.write('%s="%s"\n' % (commit_keyword, commit_value))
|
| + elif not line.startswith(commit_keyword):
|
| + # Skip old commit_keyword definition.
|
| + redirect_file.write(line)
|
| + fileinput.close()
|
| +
|
| + def RevWorkOnEBuild(self, commit_id, redirect_file=None):
|
| + """Revs a workon ebuild given the git commit hash.
|
|
|
| By default this class overwrites a new ebuild given the normal
|
| ebuild rev'ing logic. However, a user can specify a redirect_file
|
| @@ -429,44 +451,34 @@ class EBuildStableMarker(object):
|
| Returns:
|
| True if the revved package is different than the old ebuild.
|
| """
|
| - # TODO(sosa): Change to a check.
|
| - if not self._ebuild:
|
| - Die('Invalid ebuild given to EBuildStableMarker')
|
| + if self._ebuild.is_stable:
|
| + new_stable_ebuild_path = '%s-r%d.ebuild' % (
|
| + self._ebuild.ebuild_path_no_revision,
|
| + self._ebuild.current_revision + 1)
|
| + else:
|
| + # If given unstable ebuild, use 0.0.1 rather than 9999.
|
| + new_stable_ebuild_path = '%s-0.0.1-r%d.ebuild' % (
|
| + self._ebuild.ebuild_path_no_version,
|
| + self._ebuild.current_revision + 1)
|
|
|
| - new_ebuild_path = '%s-r%d.ebuild' % (self._ebuild.ebuild_path_no_revision,
|
| - self._ebuild.current_revision + 1)
|
| + _Print('Creating new stable ebuild %s' % new_stable_ebuild_path)
|
| + unstable_ebuild_path = ('%s-9999.ebuild' %
|
| + self._ebuild.ebuild_path_no_version)
|
| + if not os.path.exists(unstable_ebuild_path):
|
| + Die('Missing unstable ebuild: %s' % unstable_ebuild_path)
|
|
|
| - _Print('Creating new stable ebuild %s' % new_ebuild_path)
|
| - workon_ebuild = '%s-9999.ebuild' % self._ebuild.ebuild_path_no_version
|
| - if not os.path.exists(workon_ebuild):
|
| - Die('Missing 9999 ebuild: %s' % workon_ebuild)
|
| - shutil.copyfile(workon_ebuild, new_ebuild_path)
|
| -
|
| - for line in fileinput.input(new_ebuild_path, inplace=1):
|
| - # Has to be done here to get changes to sys.stdout from fileinput.input.
|
| - if not redirect_file:
|
| - redirect_file = sys.stdout
|
| - if line.startswith('KEYWORDS'):
|
| - # Actually mark this file as stable by removing ~'s.
|
| - redirect_file.write(line.replace('~', ''))
|
| - elif line.startswith('EAPI'):
|
| - # Always add new commit_id after EAPI definition.
|
| - redirect_file.write(line)
|
| - redirect_file.write('CROS_WORKON_COMMIT="%s"\n' % commit_id)
|
| - elif not line.startswith('CROS_WORKON_COMMIT'):
|
| - # Skip old CROS_WORKON_COMMIT definition.
|
| - redirect_file.write(line)
|
| - fileinput.close()
|
| + self.MarkAsStable(unstable_ebuild_path, new_stable_ebuild_path,
|
| + 'CROS_WORKON_COMMIT', commit_id, redirect_file)
|
|
|
| old_ebuild_path = self._ebuild.ebuild_path
|
| - diff_cmd = ['diff', '-Bu', old_ebuild_path, new_ebuild_path]
|
| + diff_cmd = ['diff', '-Bu', old_ebuild_path, new_stable_ebuild_path]
|
| if 0 == RunCommand(diff_cmd, exit_code=True, redirect_stdout=True,
|
| redirect_stderr=True, print_cmd=gflags.FLAGS.verbose):
|
| - os.unlink(new_ebuild_path)
|
| + os.unlink(new_stable_ebuild_path)
|
| return False
|
| else:
|
| _Print('Adding new stable ebuild to git')
|
| - _SimpleRunCommand('git add %s' % new_ebuild_path)
|
| + _SimpleRunCommand('git add %s' % new_stable_ebuild_path)
|
|
|
| if self._ebuild.is_stable:
|
| _Print('Removing old ebuild from git')
|
| @@ -474,11 +486,9 @@ class EBuildStableMarker(object):
|
|
|
| return True
|
|
|
| - def CommitChange(self, message):
|
| - """Commits current changes in git locally.
|
| -
|
| - This method will take any changes from invocations to RevEBuild
|
| - and commits them locally in the git repository that contains os.pwd.
|
| + @classmethod
|
| + def CommitChange(cls, message):
|
| + """Commits current changes in git locally with given commit message.
|
|
|
| Args:
|
| message: the commit string to write when committing to git.
|
| @@ -486,8 +496,7 @@ class EBuildStableMarker(object):
|
| Raises:
|
| OSError: Error occurred while committing.
|
| """
|
| - _Print('Committing changes for %s with commit message %s' % \
|
| - (self._ebuild.package, message))
|
| + Info('Committing changes with commit message: %s' % message)
|
| git_commit_cmd = 'git commit -am "%s"' % message
|
| _SimpleRunCommand(git_commit_cmd)
|
|
|
| @@ -531,11 +540,11 @@ def main(argv):
|
| os.chdir(overlay)
|
|
|
| if command == 'clean':
|
| - _Clean()
|
| + Clean(gflags.FLAGS.tracking_branch)
|
| elif command == 'push':
|
| - _PushChange()
|
| + PushChange(_STABLE_BRANCH_NAME, gflags.FLAGS.tracking_branch)
|
| elif command == 'commit' and ebuilds:
|
| - work_branch = _GitBranch(_STABLE_BRANCH_NAME)
|
| + work_branch = GitBranch(_STABLE_BRANCH_NAME, gflags.FLAGS.tracking_branch)
|
| work_branch.CreateBranch()
|
| if not work_branch.Exists():
|
| Die('Unable to create stabilizing branch in %s' % overlay)
|
| @@ -547,7 +556,7 @@ def main(argv):
|
| _Print('Working on %s' % ebuild.package)
|
| worker = EBuildStableMarker(ebuild)
|
| commit_id = ebuild.GetCommitId()
|
| - if worker.RevEBuild(commit_id):
|
| + if worker.RevWorkOnEBuild(commit_id):
|
| message = _GIT_COMMIT_MESSAGE % (ebuild.package, commit_id)
|
| worker.CommitChange(message)
|
| revved_packages.append(ebuild.package)
|
|
|