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', '', | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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) |
OLD | NEW |