Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(202)

Unified Diff: cros_mark_as_stable.py

Issue 2873016: First cut at stable script (Closed) Base URL: ssh://git@chromiumos-git//crosutils.git
Patch Set: Nits Created 10 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « cros_mark_as_stable ('k') | cros_mark_as_stable_unittest.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: cros_mark_as_stable.py
diff --git a/cros_mark_as_stable.py b/cros_mark_as_stable.py
new file mode 100755
index 0000000000000000000000000000000000000000..56978f95a2902a701d846177b8a8cc63dac725ed
--- /dev/null
+++ b/cros_mark_as_stable.py
@@ -0,0 +1,329 @@
+#!/usr/bin/python
+
+# Copyright (c) 2010 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 fileinput
+import gflags
+import os
+import re
+import shutil
+import subprocess
+import sys
+
+# TODO(sosa): Refactor Die into common library.
+sys.path.append(os.path.dirname(__file__))
+import generate_test_report
+
+
+gflags.DEFINE_string('board', 'x86-generic',
+ 'Board for which the package belongs.', short_name='b')
+gflags.DEFINE_string('commit_ids', '',
+ '''Optional list of commit ids for each package.
+ This list must either be empty or have the same length as
+ the packages list. If not set all rev'd ebuilds will have
+ empty commit id's.''',
+ short_name='i')
+gflags.DEFINE_string('packages', '',
+ 'Space separated list of packages to mark as stable.',
+ short_name='p')
+gflags.DEFINE_boolean('push', False,
+ 'Creates, commits and pushes the stable ebuild.')
+gflags.DEFINE_boolean('verbose', False,
+ 'Prints out verbose information about what is going on.',
+ short_name='v')
+
+
+# TODO(sosa): Remove hard-coding of overlays directory once there is a better
+# way.
+_CHROMIUMOS_OVERLAYS_DIRECTORY = \
+ '%s/trunk/src/third_party/chromiumos-overlay' % os.environ['HOME']
+
+# Takes two strings, package_name and commit_id.
+_GIT_COMMIT_MESSAGE = \
+ 'Marking 9999 ebuild for %s with commit %s as stable.'
+
+
+# ======================= Global Helper Functions ========================
+
+
+def _Print(message):
+ """Verbose print function."""
+ if gflags.FLAGS.verbose:
+ print message
+
+
+def _CheckSaneArguments(package_list, commit_id_list):
+ """Checks to make sure the flags are sane. Dies if arguments are not sane"""
+ if not gflags.FLAGS.packages:
+ generate_test_report.Die('Please specify at least one package')
+ if not gflags.FLAGS.board:
+ generate_test_report.Die('Please specify a board')
+ if commit_id_list and (len(package_list) != len(commit_id_list)):
+ print commit_id_list
+ print len(commit_id_list)
+ generate_test_report.Die(
+ 'Package list is not the same length as the commit id list')
+
+
+def _PrintUsageAndDie():
+ """Prints the usage and returns an error exit code."""
+ generate_test_report.Die('Usage: %s ARGS\n%s' % (sys.argv[0], gflags.FLAGS))
+
+
+def _RunCommand(command):
+ """Runs a shell command and returns stdout back to caller."""
+ _Print(' + %s' % command)
+ proc_handle = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True)
+ return proc_handle.communicate()[0]
+
+
+# ======================= End Global Helper Functions ========================
+
+
+class _GitBranch(object):
+ """Wrapper class for a git branch."""
+
+ def __init__(self, branch_name):
+ """Sets up variables but does not create the branch."""
+ self.branch_name = branch_name
+ self._cleaned_up = False
+
+ def __del__(self):
+ """Ensures we're checked back out to the master branch."""
+ if not self._cleaned_up:
+ self.CleanUp()
+
+ def CreateBranch(self):
+ """Creates a new git branch or replaces an existing one."""
+ if self.Exists():
+ self.Delete()
+ self._Checkout(self.branch_name)
+
+ def CleanUp(self):
+ """Does a git checkout back to the master branch."""
+ self._Checkout('master', create=False)
+ self._cleaned_up = True
+
+ def _Checkout(self, target, create=True):
+ """Function used internally to create and move between branches."""
+ if create:
+ git_cmd = 'git checkout -b %s origin' % target
+ else:
+ git_cmd = 'git checkout %s' % target
+ _RunCommand(git_cmd)
+
+ def Exists(self):
+ """Returns True if the branch exists."""
+ branch_cmd = 'git branch'
+ branches = _RunCommand(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.
+ """
+ self._Checkout('master', create=False)
+ delete_cmd = 'git branch -D %s' % self.branch_name
+ _RunCommand(delete_cmd)
+
+
+class _EBuild(object):
+ """Wrapper class for an ebuild."""
+
+ def __init__(self, package, commit_id=None):
+ """Initializes all data about an ebuild.
+
+ Uses equery to find the ebuild path and sets data about an ebuild for
+ easy reference.
+ """
+ self.package = package
+ self.ebuild_path = self._FindEBuildPath(package)
+ (self.ebuild_path_no_revision,
+ self.ebuild_path_no_version,
+ self.current_revision) = self._ParseEBuildPath(self.ebuild_path)
+ self.commit_id = commit_id
+
+ @classmethod
+ def _FindEBuildPath(cls, package):
+ """Static method that returns the full path of an ebuild."""
+ _Print('Looking for unstable ebuild for %s' % package)
+ equery_cmd = 'equery-%s which %s 2> /dev/null' \
+ % (gflags.FLAGS.board, package)
+ path = _RunCommand(equery_cmd)
+ if path:
+ _Print('Unstable ebuild found at %s' % path)
+ return path
+
+ @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('.ebuild')[0]
+ 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):
+ self._ebuild = ebuild
+
+ def RevEBuild(self, commit_id="", redirect_file=None):
+ """Revs an ebuild given the git commit id.
+
+ By default this class overwrites a new ebuild given the normal
+ ebuild rev'ing logic. However, a user can specify a redirect_file
+ to redirect the new stable ebuild to another file.
+
+ Args:
+ commit_id: String corresponding to the commit hash of the developer
+ package to rev.
+ redirect_file: Optional file to write the new ebuild. By default
+ it is written using the standard rev'ing logic. This file must be
+ opened and closed by the caller.
+
+ Raises:
+ OSError: Error occurred while creating a new ebuild.
+ IOError: Error occurred while writing to the new revved ebuild file.
+ """
+ # TODO(sosa): Change to a check.
+ if not self._ebuild:
+ generate_test_report.Die('Invalid ebuild given to EBuildStableMarker')
+
+ 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_ebuild_path)
+ shutil.copyfile('%s-9999.ebuild' % self._ebuild.ebuild_path_no_version,
+ 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('EGIT_COMMIT="%s"' % commit_id)
+ elif not line.startswith('EGIT_COMMIT'):
+ # Skip old EGIT_COMMIT definition.
+ redirect_file.write(line)
+ fileinput.close()
+
+ _Print('Adding new stable ebuild to git')
+ _RunCommand('git add %s' % new_ebuild_path)
+
+ _Print('Removing old ebuild from git')
+ _RunCommand('git rm %s' % self._ebuild.ebuild_path)
+
+ 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.
+
+ Args:
+ message: the commit string to write when committing to git.
+
+ Raises:
+ OSError: Error occurred while committing.
+ """
+ _Print('Committing changes for %s with commit message %s' % \
+ (self._ebuild.package, message))
+ git_commit_cmd = 'git commit -am "%s"' % message
+ _RunCommand(git_commit_cmd)
+
+ # TODO(sosa): This doesn't work yet. Want to directly push without a prompt.
+ def PushChange(self):
+ """Pushes changes to the git repository.
+
+ Pushes locals commits from calls to CommitChange to the remote git
+ repository specified by os.pwd.
+
+ Raises:
+ OSError: Error occurred while pushing.
+ """
+ print 'Push currently not implemented'
+ # TODO(sosa): Un-comment once PushChange works.
+ # _Print('Pushing changes for %s' % self._ebuild.package)
+ # git_commit_cmd = 'git push'
+ # _RunCommand(git_commit_cmd)
+
+
+def main(argv):
+ try:
+ argv = gflags.FLAGS(argv)
+ except gflags.FlagsError:
+ _PrintUsageAndDie()
+
+ package_list = gflags.FLAGS.packages.split(' ')
+ if gflags.FLAGS.commit_ids:
+ commit_id_list = gflags.FLAGS.commit_ids.split(' ')
+ else:
+ commit_id_list = None
+ _CheckSaneArguments(package_list, commit_id_list)
+
+ pwd = os.curdir
+ os.chdir(_CHROMIUMOS_OVERLAYS_DIRECTORY)
+
+ work_branch = _GitBranch('stabilizing_branch')
+ work_branch.CreateBranch()
+ if not work_branch.Exists():
+ generate_test_report.Die('Unable to create stabilizing branch')
+ index = 0
+ try:
+ for index in range(len(package_list)):
+ # Gather the package and optional commit id to work on.
+ package = package_list[index]
+ commit_id = ""
+ if commit_id_list:
+ commit_id = commit_id_list[index]
+
+ _Print('Working on %s' % package)
+ worker = EBuildStableMarker(_EBuild(package, commit_id))
+ worker.RevEBuild(commit_id)
+ worker.CommitChange(_GIT_COMMIT_MESSAGE % (package, commit_id))
+ if gflags.FLAGS.push:
+ worker.PushChange()
+
+ except (OSError, IOError):
+ print 'An exception occurred %s' % sys.exc_info()[0]
+ print 'Only the following packages were revved: %s' % package_list[:index]
+ print '''Note you will have to go into the chromiumos-overlay directory and
+ reset the git repo yourself.
+ '''
+ finally:
+ # Always run the last two cleanup functions.
+ work_branch.CleanUp()
+ os.chdir(pwd)
+
+
+if __name__ == '__main__':
+ main(sys.argv)
+
« no previous file with comments | « cros_mark_as_stable ('k') | cros_mark_as_stable_unittest.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698