Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/python | 1 #!/usr/bin/python |
| 2 | 2 |
| 3 # Copyright (c) 2010 The Chromium OS Authors. All rights reserved. | 3 # Copyright (c) 2010 The Chromium OS Authors. All rights reserved. |
| 4 # Use of this source code is governed by a BSD-style license that can be | 4 # Use of this source code is governed by a BSD-style license that can be |
| 5 # found in the LICENSE file. | 5 # found in the LICENSE file. |
| 6 | 6 |
| 7 """This module uprevs a given package's ebuild to the next revision.""" | 7 """This module uprevs a given package's ebuild to the next revision.""" |
| 8 | 8 |
| 9 | 9 |
| 10 import fileinput | 10 import fileinput |
| 11 import gflags | 11 import gflags |
| 12 import os | 12 import os |
| 13 import re | 13 import re |
| 14 import shutil | 14 import shutil |
| 15 import subprocess | 15 import subprocess |
| 16 import sys | 16 import sys |
| 17 | 17 |
| 18 sys.path.append(os.path.join(os.path.dirname(__file__), 'lib')) | 18 sys.path.append(os.path.join(os.path.dirname(__file__), 'lib')) |
| 19 from cros_build_lib import Info, RunCommand, Warning, Die | 19 from cros_build_lib import Info, RunCommand, Warning, Die |
| 20 | 20 |
| 21 | 21 gflags.DEFINE_boolean('all', False, |
| 22 'Mark all packages as stable.') | |
| 22 gflags.DEFINE_string('board', '', | 23 gflags.DEFINE_string('board', '', |
| 23 'Board for which the package belongs.', short_name='b') | 24 'Board for which the package belongs.', short_name='b') |
| 25 gflags.DEFINE_boolean('dryrun', False, | |
| 26 'Passes dry-run to git push if pushing a change.') | |
| 24 gflags.DEFINE_string('overlays', '', | 27 gflags.DEFINE_string('overlays', '', |
| 25 'Colon-separated list of overlays to modify.', | 28 'Colon-separated list of overlays to modify.', |
| 26 short_name='o') | 29 short_name='o') |
| 27 gflags.DEFINE_string('packages', '', | 30 gflags.DEFINE_string('packages', '', |
| 28 'Colon-separated list of packages to mark as stable.', | 31 'Colon-separated list of packages to mark as stable.', |
| 29 short_name='p') | 32 short_name='p') |
| 30 gflags.DEFINE_string('push_options', '', | 33 gflags.DEFINE_string('push_options', '', |
| 31 'Options to use with git-cl push using push command.') | 34 'Options to use with git-cl push using push command.') |
|
davidjames
2010/11/19 18:17:28
Thanks! I know this is already submitted but I hav
| |
| 32 gflags.DEFINE_string('srcroot', '%s/trunk/src' % os.environ['HOME'], | 35 gflags.DEFINE_string('srcroot', '%s/trunk/src' % os.environ['HOME'], |
| 33 'Path to root src directory.', | 36 'Path to root src directory.', |
| 34 short_name='r') | 37 short_name='r') |
| 35 gflags.DEFINE_string('tracking_branch', 'cros/master', | 38 gflags.DEFINE_string('tracking_branch', 'cros/master', |
| 36 'Used with commit to specify branch to track against.', | 39 'Used with commit to specify branch to track against.', |
| 37 short_name='t') | 40 short_name='t') |
| 38 gflags.DEFINE_boolean('all', False, | |
| 39 'Mark all packages as stable.') | |
| 40 gflags.DEFINE_boolean('verbose', False, | 41 gflags.DEFINE_boolean('verbose', False, |
| 41 'Prints out verbose information about what is going on.', | 42 'Prints out verbose information about what is going on.', |
| 42 short_name='v') | 43 short_name='v') |
| 43 | 44 |
| 44 | 45 |
| 45 # Takes two strings, package_name and commit_id. | 46 # Takes two strings, package_name and commit_id. |
| 46 _GIT_COMMIT_MESSAGE = \ | 47 _GIT_COMMIT_MESSAGE = 'Marking 9999 ebuild for %s with commit %s as stable.' |
| 47 'Marking 9999 ebuild for %s with commit %s as stable.' | |
| 48 | 48 |
| 49 # Dictionary of valid commands with usage information. | 49 # Dictionary of valid commands with usage information. |
| 50 _COMMAND_DICTIONARY = { | 50 COMMAND_DICTIONARY = { |
| 51 'clean': | 51 'clean': |
| 52 'Cleans up previous calls to either commit or push', | 52 'Cleans up previous calls to either commit or push', |
| 53 'commit': | 53 'commit': |
| 54 'Marks given ebuilds as stable locally', | 54 'Marks given ebuilds as stable locally', |
| 55 'push': | 55 'push': |
| 56 'Pushes previous marking of ebuilds to remote repo', | 56 'Pushes previous marking of ebuilds to remote repo', |
| 57 } | 57 } |
| 58 | 58 |
| 59 # Name used for stabilizing branch. | 59 # Name used for stabilizing branch. |
| 60 _STABLE_BRANCH_NAME = 'stabilizing_branch' | 60 _STABLE_BRANCH_NAME = 'stabilizing_branch' |
| 61 | 61 |
| 62 | |
| 63 def BestEBuild(ebuilds): | |
| 64 """Returns the newest EBuild from a list of EBuild objects.""" | |
| 65 from portage.versions import vercmp | |
| 66 winner = ebuilds[0] | |
| 67 for ebuild in ebuilds[1:]: | |
| 68 if vercmp(winner.version, ebuild.version) < 0: | |
| 69 winner = ebuild | |
| 70 return winner | |
| 71 | |
| 62 # ======================= Global Helper Functions ======================== | 72 # ======================= Global Helper Functions ======================== |
| 63 | 73 |
| 64 | 74 |
| 65 def _Print(message): | 75 def _Print(message): |
| 66 """Verbose print function.""" | 76 """Verbose print function.""" |
| 67 if gflags.FLAGS.verbose: | 77 if gflags.FLAGS.verbose: |
| 68 Info(message) | 78 Info(message) |
| 69 | 79 |
| 70 | 80 |
| 71 def _CleanStalePackages(board, package_array): | 81 def _CleanStalePackages(board, package_array): |
| 72 """Cleans up stale package info from a previous build.""" | 82 """Cleans up stale package info from a previous build.""" |
| 73 Info('Cleaning up stale packages %s.' % package_array) | 83 Info('Cleaning up stale packages %s.' % package_array) |
| 74 unmerge_board_cmd = ['emerge-%s' % board, '--unmerge'] | 84 unmerge_board_cmd = ['emerge-%s' % board, '--unmerge'] |
| 75 unmerge_board_cmd.extend(package_array) | 85 unmerge_board_cmd.extend(package_array) |
| 76 RunCommand(unmerge_board_cmd) | 86 RunCommand(unmerge_board_cmd) |
| 77 | 87 |
| 78 unmerge_host_cmd = ['sudo', 'emerge', '--unmerge'] | 88 unmerge_host_cmd = ['sudo', 'emerge', '--unmerge'] |
| 79 unmerge_host_cmd.extend(package_array) | 89 unmerge_host_cmd.extend(package_array) |
| 80 RunCommand(unmerge_host_cmd) | 90 RunCommand(unmerge_host_cmd) |
| 81 | 91 |
| 82 RunCommand(['eclean-%s' % board, '-d', 'packages'], redirect_stderr=True) | 92 RunCommand(['eclean-%s' % board, '-d', 'packages'], redirect_stderr=True) |
| 83 RunCommand(['sudo', 'eclean', '-d', 'packages'], redirect_stderr=True) | 93 RunCommand(['sudo', 'eclean', '-d', 'packages'], redirect_stderr=True) |
| 84 | 94 |
| 85 | 95 |
| 86 def _BestEBuild(ebuilds): | |
| 87 """Returns the newest EBuild from a list of EBuild objects.""" | |
| 88 from portage.versions import vercmp | |
| 89 winner = ebuilds[0] | |
| 90 for ebuild in ebuilds[1:]: | |
| 91 if vercmp(winner.version, ebuild.version) < 0: | |
| 92 winner = ebuild | |
| 93 return winner | |
| 94 | |
| 95 | |
| 96 def _FindUprevCandidates(files): | 96 def _FindUprevCandidates(files): |
| 97 """Return a list of uprev candidates from specified list of files. | 97 """Return a list of uprev candidates from specified list of files. |
| 98 | 98 |
| 99 Usually an uprev candidate is a the stable ebuild in a cros_workon directory. | 99 Usually an uprev candidate is a the stable ebuild in a cros_workon directory. |
| 100 However, if no such stable ebuild exists (someone just checked in the 9999 | 100 However, if no such stable ebuild exists (someone just checked in the 9999 |
| 101 ebuild), this is the unstable ebuild. | 101 ebuild), this is the unstable ebuild. |
| 102 | 102 |
| 103 Args: | 103 Args: |
| 104 files: List of files. | 104 files: List of files. |
| 105 """ | 105 """ |
| 106 workon_dir = False | 106 workon_dir = False |
| 107 stable_ebuilds = [] | 107 stable_ebuilds = [] |
| 108 unstable_ebuilds = [] | 108 unstable_ebuilds = [] |
| 109 for path in files: | 109 for path in files: |
| 110 if path.endswith('.ebuild') and not os.path.islink(path): | 110 if path.endswith('.ebuild') and not os.path.islink(path): |
| 111 ebuild = _EBuild(path) | 111 ebuild = EBuild(path) |
| 112 if ebuild.is_workon: | 112 if ebuild.is_workon: |
| 113 workon_dir = True | 113 workon_dir = True |
| 114 if ebuild.is_stable: | 114 if ebuild.is_stable: |
| 115 stable_ebuilds.append(ebuild) | 115 stable_ebuilds.append(ebuild) |
| 116 else: | 116 else: |
| 117 unstable_ebuilds.append(ebuild) | 117 unstable_ebuilds.append(ebuild) |
| 118 | 118 |
| 119 # If we found a workon ebuild in this directory, apply some sanity checks. | 119 # If we found a workon ebuild in this directory, apply some sanity checks. |
| 120 if workon_dir: | 120 if workon_dir: |
| 121 if len(unstable_ebuilds) > 1: | 121 if len(unstable_ebuilds) > 1: |
| 122 Die('Found multiple unstable ebuilds in %s' % os.path.dirname(path)) | 122 Die('Found multiple unstable ebuilds in %s' % os.path.dirname(path)) |
| 123 if len(stable_ebuilds) > 1: | 123 if len(stable_ebuilds) > 1: |
| 124 stable_ebuilds = [_BestEBuild(stable_ebuilds)] | 124 stable_ebuilds = [BestEBuild(stable_ebuilds)] |
| 125 | 125 |
| 126 # Print a warning if multiple stable ebuilds are found in the same | 126 # Print a warning if multiple stable ebuilds are found in the same |
| 127 # directory. Storing multiple stable ebuilds is error-prone because | 127 # directory. Storing multiple stable ebuilds is error-prone because |
| 128 # the older ebuilds will not get rev'd. | 128 # the older ebuilds will not get rev'd. |
| 129 # | 129 # |
| 130 # We make a special exception for x11-drivers/xf86-video-msm for legacy | 130 # We make a special exception for x11-drivers/xf86-video-msm for legacy |
| 131 # reasons. | 131 # reasons. |
| 132 if stable_ebuilds[0].package != 'x11-drivers/xf86-video-msm': | 132 if stable_ebuilds[0].package != 'x11-drivers/xf86-video-msm': |
| 133 Warning('Found multiple stable ebuilds in %s' % os.path.dirname(path)) | 133 Warning('Found multiple stable ebuilds in %s' % os.path.dirname(path)) |
| 134 | 134 |
| (...skipping 24 matching lines...) Expand all Loading... | |
| 159 # Add stable ebuilds to overlays[overlay]. | 159 # Add stable ebuilds to overlays[overlay]. |
| 160 paths = [os.path.join(package_dir, path) for path in files] | 160 paths = [os.path.join(package_dir, path) for path in files] |
| 161 ebuild = _FindUprevCandidates(paths) | 161 ebuild = _FindUprevCandidates(paths) |
| 162 | 162 |
| 163 # If the --all option isn't used, we only want to update packages that | 163 # If the --all option isn't used, we only want to update packages that |
| 164 # are in packages. | 164 # are in packages. |
| 165 if ebuild and (all or ebuild.package in packages): | 165 if ebuild and (all or ebuild.package in packages): |
| 166 overlays[overlay].append(ebuild) | 166 overlays[overlay].append(ebuild) |
| 167 | 167 |
| 168 | 168 |
| 169 def _CheckOnStabilizingBranch(): | 169 def _CheckOnStabilizingBranch(stable_branch): |
| 170 """Returns true if the git branch is on the stabilizing branch.""" | 170 """Returns true if the git branch is on the stabilizing branch.""" |
| 171 current_branch = _SimpleRunCommand('git branch | grep \*').split()[1] | 171 current_branch = _SimpleRunCommand('git branch | grep \*').split()[1] |
| 172 return current_branch == _STABLE_BRANCH_NAME | 172 return current_branch == stable_branch |
| 173 | 173 |
| 174 | 174 |
| 175 def _CheckSaneArguments(package_list, command): | 175 def _CheckSaneArguments(package_list, command): |
| 176 """Checks to make sure the flags are sane. Dies if arguments are not sane.""" | 176 """Checks to make sure the flags are sane. Dies if arguments are not sane.""" |
| 177 if not command in _COMMAND_DICTIONARY.keys(): | 177 if not command in COMMAND_DICTIONARY.keys(): |
| 178 _PrintUsageAndDie('%s is not a valid command' % command) | 178 _PrintUsageAndDie('%s is not a valid command' % command) |
| 179 if not gflags.FLAGS.packages and command == 'commit' and not gflags.FLAGS.all: | 179 if not gflags.FLAGS.packages and command == 'commit' and not gflags.FLAGS.all: |
| 180 _PrintUsageAndDie('Please specify at least one package') | 180 _PrintUsageAndDie('Please specify at least one package') |
| 181 if not gflags.FLAGS.board and command == 'commit': | 181 if not gflags.FLAGS.board and command == 'commit': |
| 182 _PrintUsageAndDie('Please specify a board') | 182 _PrintUsageAndDie('Please specify a board') |
| 183 if not os.path.isdir(gflags.FLAGS.srcroot): | 183 if not os.path.isdir(gflags.FLAGS.srcroot): |
| 184 _PrintUsageAndDie('srcroot is not a valid path') | 184 _PrintUsageAndDie('srcroot is not a valid path') |
| 185 gflags.FLAGS.srcroot = os.path.abspath(gflags.FLAGS.srcroot) | 185 gflags.FLAGS.srcroot = os.path.abspath(gflags.FLAGS.srcroot) |
| 186 | 186 |
| 187 | 187 |
| 188 def _Clean(): | |
| 189 """Cleans up uncommitted changes on either stabilizing branch or master.""" | |
| 190 _SimpleRunCommand('git reset HEAD --hard') | |
| 191 _SimpleRunCommand('git checkout %s' % gflags.FLAGS.tracking_branch) | |
| 192 | |
| 193 | |
| 194 def _PrintUsageAndDie(error_message=''): | 188 def _PrintUsageAndDie(error_message=''): |
| 195 """Prints optional error_message the usage and returns an error exit code.""" | 189 """Prints optional error_message the usage and returns an error exit code.""" |
| 196 command_usage = 'Commands: \n' | 190 command_usage = 'Commands: \n' |
| 197 # Add keys and usage information from dictionary. | 191 # Add keys and usage information from dictionary. |
| 198 commands = sorted(_COMMAND_DICTIONARY.keys()) | 192 commands = sorted(COMMAND_DICTIONARY.keys()) |
| 199 for command in commands: | 193 for command in commands: |
| 200 command_usage += ' %s: %s\n' % (command, _COMMAND_DICTIONARY[command]) | 194 command_usage += ' %s: %s\n' % (command, COMMAND_DICTIONARY[command]) |
| 201 commands_str = '|'.join(commands) | 195 commands_str = '|'.join(commands) |
| 202 Warning('Usage: %s FLAGS [%s]\n\n%s\nFlags:%s' % (sys.argv[0], commands_str, | 196 Warning('Usage: %s FLAGS [%s]\n\n%s\nFlags:%s' % (sys.argv[0], commands_str, |
| 203 command_usage, gflags.FLAGS)) | 197 command_usage, gflags.FLAGS)) |
| 204 if error_message: | 198 if error_message: |
| 205 Die(error_message) | 199 Die(error_message) |
| 206 else: | 200 else: |
| 207 sys.exit(1) | 201 sys.exit(1) |
| 208 | 202 |
| 209 def _PushChange(): | 203 |
| 210 """Pushes changes to the git repository. | 204 def _SimpleRunCommand(command): |
| 205 """Runs a shell command and returns stdout back to caller.""" | |
| 206 _Print(' + %s' % command) | |
| 207 proc_handle = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True) | |
| 208 stdout = proc_handle.communicate()[0] | |
| 209 retcode = proc_handle.wait() | |
| 210 if retcode != 0: | |
| 211 _Print(stdout) | |
| 212 raise subprocess.CalledProcessError(retcode, command) | |
| 213 return stdout | |
| 214 | |
| 215 | |
| 216 # ======================= End Global Helper Functions ======================== | |
| 217 | |
| 218 | |
| 219 def Clean(tracking_branch): | |
| 220 """Cleans up uncommitted changes. | |
| 221 | |
| 222 Args: | |
| 223 tracking_branch: The tracking branch we want to return to after the call. | |
| 224 """ | |
| 225 _SimpleRunCommand('git reset HEAD --hard') | |
| 226 _SimpleRunCommand('git checkout %s' % tracking_branch) | |
| 227 | |
| 228 | |
| 229 def PushChange(stable_branch, tracking_branch): | |
| 230 """Pushes commits in the stable_branch to the remote git repository. | |
| 211 | 231 |
| 212 Pushes locals commits from calls to CommitChange to the remote git | 232 Pushes locals commits from calls to CommitChange to the remote git |
| 213 repository specified by os.pwd. | 233 repository specified by current working directory. |
| 214 | 234 |
| 235 Args: | |
| 236 stable_branch: The local branch with commits we want to push. | |
| 237 tracking_branch: The tracking branch of the local branch. | |
| 215 Raises: | 238 Raises: |
| 216 OSError: Error occurred while pushing. | 239 OSError: Error occurred while pushing. |
| 217 """ | 240 """ |
| 218 num_retries = 5 | 241 num_retries = 5 |
| 219 | 242 |
| 220 # TODO(sosa) - Add logic for buildbot to check whether other slaves have | |
| 221 # completed and push this change only if they have. | |
| 222 | |
| 223 # Sanity check to make sure we're on a stabilizing branch before pushing. | 243 # Sanity check to make sure we're on a stabilizing branch before pushing. |
| 224 if not _CheckOnStabilizingBranch(): | 244 if not _CheckOnStabilizingBranch(stable_branch): |
| 225 Info('Not on branch %s so no work found to push. Exiting' % \ | 245 Info('Not on branch %s so no work found to push. Exiting' % stable_branch) |
| 226 _STABLE_BRANCH_NAME) | |
| 227 return | 246 return |
| 228 | 247 |
| 229 description = _SimpleRunCommand('git log --format=format:%s%n%n%b ' + | 248 description = _SimpleRunCommand('git log --format=format:%s%n%n%b ' + |
| 230 gflags.FLAGS.tracking_branch + '..') | 249 tracking_branch + '..') |
| 231 description = 'Marking set of ebuilds as stable\n\n%s' % description | 250 description = 'Marking set of ebuilds as stable\n\n%s' % description |
| 251 Info('Using description %s' % description) | |
| 232 merge_branch_name = 'merge_branch' | 252 merge_branch_name = 'merge_branch' |
| 233 for push_try in range(num_retries + 1): | 253 for push_try in range(num_retries + 1): |
| 234 try: | 254 try: |
| 235 _SimpleRunCommand('git remote update') | 255 _SimpleRunCommand('git remote update') |
| 236 merge_branch = _GitBranch(merge_branch_name) | 256 merge_branch = GitBranch(merge_branch_name, tracking_branch) |
| 237 merge_branch.CreateBranch() | 257 merge_branch.CreateBranch() |
| 238 if not merge_branch.Exists(): | 258 if not merge_branch.Exists(): |
| 239 Die('Unable to create merge branch.') | 259 Die('Unable to create merge branch.') |
| 240 _SimpleRunCommand('git merge --squash %s' % _STABLE_BRANCH_NAME) | 260 _SimpleRunCommand('git merge --squash %s' % stable_branch) |
| 241 _SimpleRunCommand('git commit -m "%s"' % description) | 261 _SimpleRunCommand('git commit -m "%s"' % description) |
| 242 # Ugh. There has got to be an easier way to push to a tracking branch | |
| 243 _SimpleRunCommand('git config push.default tracking') | 262 _SimpleRunCommand('git config push.default tracking') |
| 244 _SimpleRunCommand('git push') | 263 if gflags.FLAGS.dryrun: |
| 264 _SimpleRunCommand('git push --dry-run') | |
| 265 else: | |
| 266 _SimpleRunCommand('git push') | |
| 267 | |
| 245 break | 268 break |
| 246 except: | 269 except: |
| 247 if push_try < num_retries: | 270 if push_try < num_retries: |
| 248 Warning('Failed to push change, performing retry (%s/%s)' % ( | 271 Warning('Failed to push change, performing retry (%s/%s)' % ( |
| 249 push_try + 1, num_retries)) | 272 push_try + 1, num_retries)) |
| 250 else: | 273 else: |
| 251 raise | 274 raise |
| 252 | 275 |
| 253 | 276 |
| 254 def _SimpleRunCommand(command): | 277 class GitBranch(object): |
| 255 """Runs a shell command and returns stdout back to caller.""" | |
| 256 _Print(' + %s' % command) | |
| 257 proc_handle = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True) | |
| 258 stdout = proc_handle.communicate()[0] | |
| 259 retcode = proc_handle.wait() | |
| 260 if retcode != 0: | |
| 261 _Print(stdout) | |
| 262 raise subprocess.CalledProcessError(retcode, command) | |
| 263 return stdout | |
| 264 | |
| 265 | |
| 266 # ======================= End Global Helper Functions ======================== | |
| 267 | |
| 268 | |
| 269 class _GitBranch(object): | |
| 270 """Wrapper class for a git branch.""" | 278 """Wrapper class for a git branch.""" |
| 271 | 279 |
| 272 def __init__(self, branch_name): | 280 def __init__(self, branch_name, tracking_branch): |
| 273 """Sets up variables but does not create the branch.""" | 281 """Sets up variables but does not create the branch.""" |
| 274 self.branch_name = branch_name | 282 self.branch_name = branch_name |
| 283 self.tracking_branch = tracking_branch | |
| 275 | 284 |
| 276 def CreateBranch(self): | 285 def CreateBranch(self): |
| 277 """Creates a new git branch or replaces an existing one.""" | 286 """Creates a new git branch or replaces an existing one.""" |
| 278 if self.Exists(): | 287 if self.Exists(): |
| 279 self.Delete() | 288 self.Delete() |
| 280 self._Checkout(self.branch_name) | 289 self._Checkout(self.branch_name) |
| 281 | 290 |
| 282 def _Checkout(self, target, create=True): | 291 def _Checkout(self, target, create=True): |
| 283 """Function used internally to create and move between branches.""" | 292 """Function used internally to create and move between branches.""" |
| 284 if create: | 293 if create: |
| 285 git_cmd = 'git checkout -b %s %s' % (target, gflags.FLAGS.tracking_branch) | 294 git_cmd = 'git checkout -b %s %s' % (target, self.tracking_branch) |
| 286 else: | 295 else: |
| 287 git_cmd = 'git checkout %s' % target | 296 git_cmd = 'git checkout %s' % target |
| 288 _SimpleRunCommand(git_cmd) | 297 _SimpleRunCommand(git_cmd) |
| 289 | 298 |
| 290 def Exists(self): | 299 def Exists(self): |
| 291 """Returns True if the branch exists.""" | 300 """Returns True if the branch exists.""" |
| 292 branch_cmd = 'git branch' | 301 branch_cmd = 'git branch' |
| 293 branches = _SimpleRunCommand(branch_cmd) | 302 branches = _SimpleRunCommand(branch_cmd) |
| 294 return self.branch_name in branches.split() | 303 return self.branch_name in branches.split() |
| 295 | 304 |
| 296 def Delete(self): | 305 def Delete(self): |
| 297 """Deletes the branch and returns the user to the master branch. | 306 """Deletes the branch and returns the user to the master branch. |
| 298 | 307 |
| 299 Returns True on success. | 308 Returns True on success. |
| 300 """ | 309 """ |
| 301 self._Checkout(gflags.FLAGS.tracking_branch, create=False) | 310 self._Checkout(self.tracking_branch, create=False) |
| 302 delete_cmd = 'git branch -D %s' % self.branch_name | 311 delete_cmd = 'git branch -D %s' % self.branch_name |
| 303 _SimpleRunCommand(delete_cmd) | 312 _SimpleRunCommand(delete_cmd) |
| 304 | 313 |
| 305 | 314 |
| 306 class _EBuild(object): | 315 class EBuild(object): |
| 307 """Wrapper class for an ebuild.""" | 316 """Wrapper class for information about an ebuild.""" |
| 308 | 317 |
| 309 def __init__(self, path): | 318 def __init__(self, path): |
| 310 """Initializes all data about an ebuild. | 319 """Sets up data about an ebuild from its path.""" |
| 320 from portage.versions import pkgsplit | |
| 321 unused_path, self.category, self.pkgname, filename = path.rsplit('/', 3) | |
| 322 unused_pkgname, version_no_rev, rev = pkgsplit( | |
| 323 filename.replace('.ebuild', '')) | |
| 311 | 324 |
| 312 Uses equery to find the ebuild path and sets data about an ebuild for | 325 self.ebuild_path_no_version = os.path.join( |
| 313 easy reference. | 326 os.path.dirname(path), self.pkgname) |
| 314 """ | 327 self.ebuild_path_no_revision = '%s-%s' % (self.ebuild_path_no_version, |
| 315 from portage.versions import pkgsplit | 328 version_no_rev) |
| 316 self.ebuild_path = path | 329 self.current_revision = int(rev.replace('r', '')) |
| 317 (self.ebuild_path_no_revision, | |
| 318 self.ebuild_path_no_version, | |
| 319 self.current_revision) = self._ParseEBuildPath(self.ebuild_path) | |
| 320 _, self.category, pkgpath, filename = path.rsplit('/', 3) | |
| 321 filename_no_suffix = os.path.join(filename.replace('.ebuild', '')) | |
| 322 self.pkgname, version_no_rev, rev = pkgsplit(filename_no_suffix) | |
| 323 self.version = '%s-%s' % (version_no_rev, rev) | 330 self.version = '%s-%s' % (version_no_rev, rev) |
| 324 self.package = '%s/%s' % (self.category, self.pkgname) | 331 self.package = '%s/%s' % (self.category, self.pkgname) |
| 332 self.ebuild_path = path | |
| 333 | |
| 325 self.is_workon = False | 334 self.is_workon = False |
| 326 self.is_stable = False | 335 self.is_stable = False |
| 327 | 336 |
| 328 for line in fileinput.input(path): | 337 for line in fileinput.input(path): |
| 329 if line.startswith('inherit ') and 'cros-workon' in line: | 338 if line.startswith('inherit ') and 'cros-workon' in line: |
| 330 self.is_workon = True | 339 self.is_workon = True |
| 331 elif (line.startswith('KEYWORDS=') and '~' not in line and | 340 elif (line.startswith('KEYWORDS=') and '~' not in line and |
| 332 ('amd64' in line or 'x86' in line or 'arm' in line)): | 341 ('amd64' in line or 'x86' in line or 'arm' in line)): |
| 333 self.is_stable = True | 342 self.is_stable = True |
| 334 fileinput.close() | 343 fileinput.close() |
| 335 | 344 |
| 336 def GetCommitId(self): | 345 def GetCommitId(self): |
| 337 """Get the commit id for this ebuild.""" | 346 """Get the commit id for this ebuild.""" |
| 338 | |
| 339 # Grab and evaluate CROS_WORKON variables from this ebuild. | 347 # Grab and evaluate CROS_WORKON variables from this ebuild. |
| 340 unstable_ebuild = '%s-9999.ebuild' % self.ebuild_path_no_version | 348 unstable_ebuild = '%s-9999.ebuild' % self.ebuild_path_no_version |
| 341 cmd = ('export CROS_WORKON_LOCALNAME="%s" CROS_WORKON_PROJECT="%s"; ' | 349 cmd = ('export CROS_WORKON_LOCALNAME="%s" CROS_WORKON_PROJECT="%s"; ' |
| 342 'eval $(grep -E "^CROS_WORKON" %s) && ' | 350 'eval $(grep -E "^CROS_WORKON" %s) && ' |
| 343 'echo $CROS_WORKON_PROJECT ' | 351 'echo $CROS_WORKON_PROJECT ' |
| 344 '$CROS_WORKON_LOCALNAME/$CROS_WORKON_SUBDIR' | 352 '$CROS_WORKON_LOCALNAME/$CROS_WORKON_SUBDIR' |
| 345 % (self.pkgname, self.pkgname, unstable_ebuild)) | 353 % (self.pkgname, self.pkgname, unstable_ebuild)) |
| 346 project, subdir = _SimpleRunCommand(cmd).split() | 354 project, subdir = _SimpleRunCommand(cmd).split() |
| 347 | 355 |
| 348 # Calculate srcdir. | 356 # Calculate srcdir. |
| (...skipping 22 matching lines...) Expand all Loading... | |
| 371 if project not in (actual_project, 'chromeos-kernel'): | 379 if project not in (actual_project, 'chromeos-kernel'): |
| 372 Die('Project name mismatch for %s (%s != %s)' % (unstable_ebuild, project, | 380 Die('Project name mismatch for %s (%s != %s)' % (unstable_ebuild, project, |
| 373 actual_project)) | 381 actual_project)) |
| 374 | 382 |
| 375 # Get commit id. | 383 # Get commit id. |
| 376 output = _SimpleRunCommand('cd %s && git rev-parse HEAD' % srcdir) | 384 output = _SimpleRunCommand('cd %s && git rev-parse HEAD' % srcdir) |
| 377 if not output: | 385 if not output: |
| 378 Die('Missing commit id for %s' % self.ebuild_path) | 386 Die('Missing commit id for %s' % self.ebuild_path) |
| 379 return output.rstrip() | 387 return output.rstrip() |
| 380 | 388 |
| 381 @classmethod | |
| 382 def _ParseEBuildPath(cls, ebuild_path): | |
| 383 """Static method that parses the path of an ebuild | |
| 384 | |
| 385 Returns a tuple containing the (ebuild path without the revision | |
| 386 string, without the version string, and the current revision number for | |
| 387 the ebuild). | |
| 388 """ | |
| 389 # Get the ebuild name without the revision string. | |
| 390 (ebuild_no_rev, _, rev_string) = ebuild_path.rpartition('-') | |
| 391 | |
| 392 # Verify the revision string starts with the revision character. | |
| 393 if rev_string.startswith('r'): | |
| 394 # Get the ebuild name without the revision and version strings. | |
| 395 ebuild_no_version = ebuild_no_rev.rpartition('-')[0] | |
| 396 rev_string = rev_string[1:].rpartition('.ebuild')[0] | |
| 397 else: | |
| 398 # Has no revision so we stripped the version number instead. | |
| 399 ebuild_no_version = ebuild_no_rev | |
| 400 ebuild_no_rev = ebuild_path.rpartition('9999.ebuild')[0] + '0.0.1' | |
| 401 rev_string = '0' | |
| 402 revision = int(rev_string) | |
| 403 return (ebuild_no_rev, ebuild_no_version, revision) | |
| 404 | |
| 405 | 389 |
| 406 class EBuildStableMarker(object): | 390 class EBuildStableMarker(object): |
| 407 """Class that revs the ebuild and commits locally or pushes the change.""" | 391 """Class that revs the ebuild and commits locally or pushes the change.""" |
| 408 | 392 |
| 409 def __init__(self, ebuild): | 393 def __init__(self, ebuild): |
| 394 assert ebuild | |
| 410 self._ebuild = ebuild | 395 self._ebuild = ebuild |
| 411 | 396 |
| 412 def RevEBuild(self, commit_id='', redirect_file=None): | 397 @classmethod |
| 413 """Revs an ebuild given the git commit id. | 398 def MarkAsStable(cls, unstable_ebuild_path, new_stable_ebuild_path, |
| 399 commit_keyword, commit_value, redirect_file=None): | |
| 400 """Static function that creates a revved stable ebuild. | |
| 401 | |
| 402 This function assumes you have already figured out the name of the new | |
| 403 stable ebuild path and then creates that file from the given unstable | |
| 404 ebuild and marks it as stable. If the commit_value is set, it also | |
| 405 set the commit_keyword=commit_value pair in the ebuild. | |
| 406 | |
| 407 Args: | |
| 408 unstable_ebuild_path: The path to the unstable ebuild. | |
| 409 new_stable_ebuild_path: The path you want to use for the new stable | |
| 410 ebuild. | |
| 411 commit_keyword: Optional keyword to set in the ebuild to mark it as | |
| 412 stable. | |
| 413 commit_value: Value to set the above keyword to. | |
| 414 redirect_file: Optionally redirect output of new ebuild somewhere else. | |
| 415 """ | |
| 416 shutil.copyfile(unstable_ebuild_path, new_stable_ebuild_path) | |
| 417 for line in fileinput.input(new_stable_ebuild_path, inplace=1): | |
| 418 # Has to be done here to get changes to sys.stdout from fileinput.input. | |
| 419 if not redirect_file: | |
| 420 redirect_file = sys.stdout | |
| 421 if line.startswith('KEYWORDS'): | |
| 422 # Actually mark this file as stable by removing ~'s. | |
| 423 redirect_file.write(line.replace('~', '')) | |
| 424 elif line.startswith('EAPI'): | |
| 425 # Always add new commit_id after EAPI definition. | |
| 426 redirect_file.write(line) | |
| 427 if commit_keyword and commit_value: | |
| 428 redirect_file.write('%s="%s"\n' % (commit_keyword, commit_value)) | |
| 429 elif not line.startswith(commit_keyword): | |
| 430 # Skip old commit_keyword definition. | |
| 431 redirect_file.write(line) | |
| 432 fileinput.close() | |
| 433 | |
| 434 def RevWorkOnEBuild(self, commit_id, redirect_file=None): | |
| 435 """Revs a workon ebuild given the git commit hash. | |
| 414 | 436 |
| 415 By default this class overwrites a new ebuild given the normal | 437 By default this class overwrites a new ebuild given the normal |
| 416 ebuild rev'ing logic. However, a user can specify a redirect_file | 438 ebuild rev'ing logic. However, a user can specify a redirect_file |
| 417 to redirect the new stable ebuild to another file. | 439 to redirect the new stable ebuild to another file. |
| 418 | 440 |
| 419 Args: | 441 Args: |
| 420 commit_id: String corresponding to the commit hash of the developer | 442 commit_id: String corresponding to the commit hash of the developer |
| 421 package to rev. | 443 package to rev. |
| 422 redirect_file: Optional file to write the new ebuild. By default | 444 redirect_file: Optional file to write the new ebuild. By default |
| 423 it is written using the standard rev'ing logic. This file must be | 445 it is written using the standard rev'ing logic. This file must be |
| 424 opened and closed by the caller. | 446 opened and closed by the caller. |
| 425 | 447 |
| 426 Raises: | 448 Raises: |
| 427 OSError: Error occurred while creating a new ebuild. | 449 OSError: Error occurred while creating a new ebuild. |
| 428 IOError: Error occurred while writing to the new revved ebuild file. | 450 IOError: Error occurred while writing to the new revved ebuild file. |
| 429 Returns: | 451 Returns: |
| 430 True if the revved package is different than the old ebuild. | 452 True if the revved package is different than the old ebuild. |
| 431 """ | 453 """ |
| 432 # TODO(sosa): Change to a check. | 454 if self._ebuild.is_stable: |
| 433 if not self._ebuild: | 455 new_stable_ebuild_path = '%s-r%d.ebuild' % ( |
| 434 Die('Invalid ebuild given to EBuildStableMarker') | 456 self._ebuild.ebuild_path_no_revision, |
| 457 self._ebuild.current_revision + 1) | |
| 458 else: | |
| 459 # If given unstable ebuild, use 0.0.1 rather than 9999. | |
| 460 new_stable_ebuild_path = '%s-0.0.1-r%d.ebuild' % ( | |
| 461 self._ebuild.ebuild_path_no_version, | |
| 462 self._ebuild.current_revision + 1) | |
| 435 | 463 |
| 436 new_ebuild_path = '%s-r%d.ebuild' % (self._ebuild.ebuild_path_no_revision, | 464 _Print('Creating new stable ebuild %s' % new_stable_ebuild_path) |
| 437 self._ebuild.current_revision + 1) | 465 unstable_ebuild_path = ('%s-9999.ebuild' % |
| 466 self._ebuild.ebuild_path_no_version) | |
| 467 if not os.path.exists(unstable_ebuild_path): | |
| 468 Die('Missing unstable ebuild: %s' % unstable_ebuild_path) | |
| 438 | 469 |
| 439 _Print('Creating new stable ebuild %s' % new_ebuild_path) | 470 self.MarkAsStable(unstable_ebuild_path, new_stable_ebuild_path, |
| 440 workon_ebuild = '%s-9999.ebuild' % self._ebuild.ebuild_path_no_version | 471 'CROS_WORKON_COMMIT', commit_id, redirect_file) |
| 441 if not os.path.exists(workon_ebuild): | |
| 442 Die('Missing 9999 ebuild: %s' % workon_ebuild) | |
| 443 shutil.copyfile(workon_ebuild, new_ebuild_path) | |
| 444 | |
| 445 for line in fileinput.input(new_ebuild_path, inplace=1): | |
| 446 # Has to be done here to get changes to sys.stdout from fileinput.input. | |
| 447 if not redirect_file: | |
| 448 redirect_file = sys.stdout | |
| 449 if line.startswith('KEYWORDS'): | |
| 450 # Actually mark this file as stable by removing ~'s. | |
| 451 redirect_file.write(line.replace('~', '')) | |
| 452 elif line.startswith('EAPI'): | |
| 453 # Always add new commit_id after EAPI definition. | |
| 454 redirect_file.write(line) | |
| 455 redirect_file.write('CROS_WORKON_COMMIT="%s"\n' % commit_id) | |
| 456 elif not line.startswith('CROS_WORKON_COMMIT'): | |
| 457 # Skip old CROS_WORKON_COMMIT definition. | |
| 458 redirect_file.write(line) | |
| 459 fileinput.close() | |
| 460 | 472 |
| 461 old_ebuild_path = self._ebuild.ebuild_path | 473 old_ebuild_path = self._ebuild.ebuild_path |
| 462 diff_cmd = ['diff', '-Bu', old_ebuild_path, new_ebuild_path] | 474 diff_cmd = ['diff', '-Bu', old_ebuild_path, new_stable_ebuild_path] |
| 463 if 0 == RunCommand(diff_cmd, exit_code=True, redirect_stdout=True, | 475 if 0 == RunCommand(diff_cmd, exit_code=True, redirect_stdout=True, |
| 464 redirect_stderr=True, print_cmd=gflags.FLAGS.verbose): | 476 redirect_stderr=True, print_cmd=gflags.FLAGS.verbose): |
| 465 os.unlink(new_ebuild_path) | 477 os.unlink(new_stable_ebuild_path) |
| 466 return False | 478 return False |
| 467 else: | 479 else: |
| 468 _Print('Adding new stable ebuild to git') | 480 _Print('Adding new stable ebuild to git') |
| 469 _SimpleRunCommand('git add %s' % new_ebuild_path) | 481 _SimpleRunCommand('git add %s' % new_stable_ebuild_path) |
| 470 | 482 |
| 471 if self._ebuild.is_stable: | 483 if self._ebuild.is_stable: |
| 472 _Print('Removing old ebuild from git') | 484 _Print('Removing old ebuild from git') |
| 473 _SimpleRunCommand('git rm %s' % old_ebuild_path) | 485 _SimpleRunCommand('git rm %s' % old_ebuild_path) |
| 474 | 486 |
| 475 return True | 487 return True |
| 476 | 488 |
| 477 def CommitChange(self, message): | 489 @classmethod |
| 478 """Commits current changes in git locally. | 490 def CommitChange(cls, message): |
| 479 | 491 """Commits current changes in git locally with given commit message. |
| 480 This method will take any changes from invocations to RevEBuild | |
| 481 and commits them locally in the git repository that contains os.pwd. | |
| 482 | 492 |
| 483 Args: | 493 Args: |
| 484 message: the commit string to write when committing to git. | 494 message: the commit string to write when committing to git. |
| 485 | 495 |
| 486 Raises: | 496 Raises: |
| 487 OSError: Error occurred while committing. | 497 OSError: Error occurred while committing. |
| 488 """ | 498 """ |
| 489 _Print('Committing changes for %s with commit message %s' % \ | 499 Info('Committing changes with commit message: %s' % message) |
| 490 (self._ebuild.package, message)) | |
| 491 git_commit_cmd = 'git commit -am "%s"' % message | 500 git_commit_cmd = 'git commit -am "%s"' % message |
| 492 _SimpleRunCommand(git_commit_cmd) | 501 _SimpleRunCommand(git_commit_cmd) |
| 493 | 502 |
| 494 | 503 |
| 495 def main(argv): | 504 def main(argv): |
| 496 try: | 505 try: |
| 497 argv = gflags.FLAGS(argv) | 506 argv = gflags.FLAGS(argv) |
| 498 if len(argv) != 2: | 507 if len(argv) != 2: |
| 499 _PrintUsageAndDie('Must specify a valid command') | 508 _PrintUsageAndDie('Must specify a valid command') |
| 500 else: | 509 else: |
| (...skipping 23 matching lines...) Expand all Loading... | |
| 524 if not os.path.isdir(overlay): | 533 if not os.path.isdir(overlay): |
| 525 Warning("Skipping %s" % overlay) | 534 Warning("Skipping %s" % overlay) |
| 526 continue | 535 continue |
| 527 | 536 |
| 528 # TODO(davidjames): Currently, all code that interacts with git depends on | 537 # TODO(davidjames): Currently, all code that interacts with git depends on |
| 529 # the cwd being set to the overlay directory. We should instead pass in | 538 # the cwd being set to the overlay directory. We should instead pass in |
| 530 # this parameter so that we don't need to modify the cwd globally. | 539 # this parameter so that we don't need to modify the cwd globally. |
| 531 os.chdir(overlay) | 540 os.chdir(overlay) |
| 532 | 541 |
| 533 if command == 'clean': | 542 if command == 'clean': |
| 534 _Clean() | 543 Clean(gflags.FLAGS.tracking_branch) |
| 535 elif command == 'push': | 544 elif command == 'push': |
| 536 _PushChange() | 545 PushChange(_STABLE_BRANCH_NAME, gflags.FLAGS.tracking_branch) |
| 537 elif command == 'commit' and ebuilds: | 546 elif command == 'commit' and ebuilds: |
| 538 work_branch = _GitBranch(_STABLE_BRANCH_NAME) | 547 work_branch = GitBranch(_STABLE_BRANCH_NAME, gflags.FLAGS.tracking_branch) |
| 539 work_branch.CreateBranch() | 548 work_branch.CreateBranch() |
| 540 if not work_branch.Exists(): | 549 if not work_branch.Exists(): |
| 541 Die('Unable to create stabilizing branch in %s' % overlay) | 550 Die('Unable to create stabilizing branch in %s' % overlay) |
| 542 | 551 |
| 543 # Contains the array of packages we actually revved. | 552 # Contains the array of packages we actually revved. |
| 544 revved_packages = [] | 553 revved_packages = [] |
| 545 for ebuild in ebuilds: | 554 for ebuild in ebuilds: |
| 546 try: | 555 try: |
| 547 _Print('Working on %s' % ebuild.package) | 556 _Print('Working on %s' % ebuild.package) |
| 548 worker = EBuildStableMarker(ebuild) | 557 worker = EBuildStableMarker(ebuild) |
| 549 commit_id = ebuild.GetCommitId() | 558 commit_id = ebuild.GetCommitId() |
| 550 if worker.RevEBuild(commit_id): | 559 if worker.RevWorkOnEBuild(commit_id): |
| 551 message = _GIT_COMMIT_MESSAGE % (ebuild.package, commit_id) | 560 message = _GIT_COMMIT_MESSAGE % (ebuild.package, commit_id) |
| 552 worker.CommitChange(message) | 561 worker.CommitChange(message) |
| 553 revved_packages.append(ebuild.package) | 562 revved_packages.append(ebuild.package) |
| 554 | 563 |
| 555 except (OSError, IOError): | 564 except (OSError, IOError): |
| 556 Warning('Cannot rev %s\n' % ebuild.package, | 565 Warning('Cannot rev %s\n' % ebuild.package, |
| 557 'Note you will have to go into %s ' | 566 'Note you will have to go into %s ' |
| 558 'and reset the git repo yourself.' % overlay) | 567 'and reset the git repo yourself.' % overlay) |
| 559 raise | 568 raise |
| 560 | 569 |
| 561 if revved_packages: | 570 if revved_packages: |
| 562 _CleanStalePackages(gflags.FLAGS.board, revved_packages) | 571 _CleanStalePackages(gflags.FLAGS.board, revved_packages) |
| 563 else: | 572 else: |
| 564 work_branch.Delete() | 573 work_branch.Delete() |
| 565 | 574 |
| 566 | 575 |
| 567 if __name__ == '__main__': | 576 if __name__ == '__main__': |
| 568 main(sys.argv) | 577 main(sys.argv) |
| OLD | NEW |