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

Side by Side Diff: cros_mark_as_stable.py

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

Powered by Google App Engine
This is Rietveld 408576698