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 |