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

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: Re-add option 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', '', 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
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
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
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)
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