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

Side by Side Diff: cros_mark_as_stable.py

Issue 2873016: First cut at stable script (Closed) Base URL: ssh://git@chromiumos-git//crosutils.git
Patch Set: Nits Created 10 years, 5 months 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
« no previous file with comments | « cros_mark_as_stable ('k') | 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
(Empty)
1 #!/usr/bin/python
2
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
5 # found in the LICENSE file.
6
7 """This module uprevs a given package's ebuild to the next revision."""
8
9
10 import fileinput
11 import gflags
12 import os
13 import re
14 import shutil
15 import subprocess
16 import sys
17
18 # TODO(sosa): Refactor Die into common library.
19 sys.path.append(os.path.dirname(__file__))
20 import generate_test_report
21
22
23 gflags.DEFINE_string('board', 'x86-generic',
24 'Board for which the package belongs.', short_name='b')
25 gflags.DEFINE_string('commit_ids', '',
26 '''Optional list of commit ids for each package.
27 This list must either be empty or have the same length as
28 the packages list. If not set all rev'd ebuilds will have
29 empty commit id's.''',
30 short_name='i')
31 gflags.DEFINE_string('packages', '',
32 'Space separated list of packages to mark as stable.',
33 short_name='p')
34 gflags.DEFINE_boolean('push', False,
35 'Creates, commits and pushes the stable ebuild.')
36 gflags.DEFINE_boolean('verbose', False,
37 'Prints out verbose information about what is going on.',
38 short_name='v')
39
40
41 # TODO(sosa): Remove hard-coding of overlays directory once there is a better
42 # way.
43 _CHROMIUMOS_OVERLAYS_DIRECTORY = \
44 '%s/trunk/src/third_party/chromiumos-overlay' % os.environ['HOME']
45
46 # Takes two strings, package_name and commit_id.
47 _GIT_COMMIT_MESSAGE = \
48 'Marking 9999 ebuild for %s with commit %s as stable.'
49
50
51 # ======================= Global Helper Functions ========================
52
53
54 def _Print(message):
55 """Verbose print function."""
56 if gflags.FLAGS.verbose:
57 print message
58
59
60 def _CheckSaneArguments(package_list, commit_id_list):
61 """Checks to make sure the flags are sane. Dies if arguments are not sane"""
62 if not gflags.FLAGS.packages:
63 generate_test_report.Die('Please specify at least one package')
64 if not gflags.FLAGS.board:
65 generate_test_report.Die('Please specify a board')
66 if commit_id_list and (len(package_list) != len(commit_id_list)):
67 print commit_id_list
68 print len(commit_id_list)
69 generate_test_report.Die(
70 'Package list is not the same length as the commit id list')
71
72
73 def _PrintUsageAndDie():
74 """Prints the usage and returns an error exit code."""
75 generate_test_report.Die('Usage: %s ARGS\n%s' % (sys.argv[0], gflags.FLAGS))
76
77
78 def _RunCommand(command):
79 """Runs a shell command and returns stdout back to caller."""
80 _Print(' + %s' % command)
81 proc_handle = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True)
82 return proc_handle.communicate()[0]
83
84
85 # ======================= End Global Helper Functions ========================
86
87
88 class _GitBranch(object):
89 """Wrapper class for a git branch."""
90
91 def __init__(self, branch_name):
92 """Sets up variables but does not create the branch."""
93 self.branch_name = branch_name
94 self._cleaned_up = False
95
96 def __del__(self):
97 """Ensures we're checked back out to the master branch."""
98 if not self._cleaned_up:
99 self.CleanUp()
100
101 def CreateBranch(self):
102 """Creates a new git branch or replaces an existing one."""
103 if self.Exists():
104 self.Delete()
105 self._Checkout(self.branch_name)
106
107 def CleanUp(self):
108 """Does a git checkout back to the master branch."""
109 self._Checkout('master', create=False)
110 self._cleaned_up = True
111
112 def _Checkout(self, target, create=True):
113 """Function used internally to create and move between branches."""
114 if create:
115 git_cmd = 'git checkout -b %s origin' % target
116 else:
117 git_cmd = 'git checkout %s' % target
118 _RunCommand(git_cmd)
119
120 def Exists(self):
121 """Returns True if the branch exists."""
122 branch_cmd = 'git branch'
123 branches = _RunCommand(branch_cmd)
124 return self.branch_name in branches.split()
125
126 def Delete(self):
127 """Deletes the branch and returns the user to the master branch.
128
129 Returns True on success.
130 """
131 self._Checkout('master', create=False)
132 delete_cmd = 'git branch -D %s' % self.branch_name
133 _RunCommand(delete_cmd)
134
135
136 class _EBuild(object):
137 """Wrapper class for an ebuild."""
138
139 def __init__(self, package, commit_id=None):
140 """Initializes all data about an ebuild.
141
142 Uses equery to find the ebuild path and sets data about an ebuild for
143 easy reference.
144 """
145 self.package = package
146 self.ebuild_path = self._FindEBuildPath(package)
147 (self.ebuild_path_no_revision,
148 self.ebuild_path_no_version,
149 self.current_revision) = self._ParseEBuildPath(self.ebuild_path)
150 self.commit_id = commit_id
151
152 @classmethod
153 def _FindEBuildPath(cls, package):
154 """Static method that returns the full path of an ebuild."""
155 _Print('Looking for unstable ebuild for %s' % package)
156 equery_cmd = 'equery-%s which %s 2> /dev/null' \
157 % (gflags.FLAGS.board, package)
158 path = _RunCommand(equery_cmd)
159 if path:
160 _Print('Unstable ebuild found at %s' % path)
161 return path
162
163 @classmethod
164 def _ParseEBuildPath(cls, ebuild_path):
165 """Static method that parses the path of an ebuild
166
167 Returns a tuple containing the (ebuild path without the revision
168 string, without the version string, and the current revision number for
169 the ebuild).
170 """
171 # Get the ebuild name without the revision string.
172 (ebuild_no_rev, _, rev_string) = ebuild_path.rpartition('-')
173
174 # Verify the revision string starts with the revision character.
175 if rev_string.startswith('r'):
176 # Get the ebuild name without the revision and version strings.
177 ebuild_no_version = ebuild_no_rev.rpartition('-')[0]
178 rev_string = rev_string[1:].rpartition('.ebuild')[0]
179 else:
180 # Has no revision so we stripped the version number instead.
181 ebuild_no_version = ebuild_no_rev
182 ebuild_no_rev = ebuild_path.rpartition('.ebuild')[0]
183 rev_string = "0"
184 revision = int(rev_string)
185 return (ebuild_no_rev, ebuild_no_version, revision)
186
187
188 class EBuildStableMarker(object):
189 """Class that revs the ebuild and commits locally or pushes the change."""
190
191 def __init__(self, ebuild):
192 self._ebuild = ebuild
193
194 def RevEBuild(self, commit_id="", redirect_file=None):
195 """Revs an ebuild given the git commit id.
196
197 By default this class overwrites a new ebuild given the normal
198 ebuild rev'ing logic. However, a user can specify a redirect_file
199 to redirect the new stable ebuild to another file.
200
201 Args:
202 commit_id: String corresponding to the commit hash of the developer
203 package to rev.
204 redirect_file: Optional file to write the new ebuild. By default
205 it is written using the standard rev'ing logic. This file must be
206 opened and closed by the caller.
207
208 Raises:
209 OSError: Error occurred while creating a new ebuild.
210 IOError: Error occurred while writing to the new revved ebuild file.
211 """
212 # TODO(sosa): Change to a check.
213 if not self._ebuild:
214 generate_test_report.Die('Invalid ebuild given to EBuildStableMarker')
215
216 new_ebuild_path = '%s-r%d.ebuild' % (self._ebuild.ebuild_path_no_revision,
217 self._ebuild.current_revision + 1)
218
219 _Print('Creating new stable ebuild %s' % new_ebuild_path)
220 shutil.copyfile('%s-9999.ebuild' % self._ebuild.ebuild_path_no_version,
221 new_ebuild_path)
222
223 for line in fileinput.input(new_ebuild_path, inplace=1):
224 # Has to be done here to get changes to sys.stdout from fileinput.input.
225 if not redirect_file:
226 redirect_file = sys.stdout
227 if line.startswith('KEYWORDS'):
228 # Actually mark this file as stable by removing ~'s.
229 redirect_file.write(line.replace("~", ""))
230 elif line.startswith('EAPI'):
231 # Always add new commit_id after EAPI definition.
232 redirect_file.write(line)
233 redirect_file.write('EGIT_COMMIT="%s"' % commit_id)
234 elif not line.startswith('EGIT_COMMIT'):
235 # Skip old EGIT_COMMIT definition.
236 redirect_file.write(line)
237 fileinput.close()
238
239 _Print('Adding new stable ebuild to git')
240 _RunCommand('git add %s' % new_ebuild_path)
241
242 _Print('Removing old ebuild from git')
243 _RunCommand('git rm %s' % self._ebuild.ebuild_path)
244
245 def CommitChange(self, message):
246 """Commits current changes in git locally.
247
248 This method will take any changes from invocations to RevEBuild
249 and commits them locally in the git repository that contains os.pwd.
250
251 Args:
252 message: the commit string to write when committing to git.
253
254 Raises:
255 OSError: Error occurred while committing.
256 """
257 _Print('Committing changes for %s with commit message %s' % \
258 (self._ebuild.package, message))
259 git_commit_cmd = 'git commit -am "%s"' % message
260 _RunCommand(git_commit_cmd)
261
262 # TODO(sosa): This doesn't work yet. Want to directly push without a prompt.
263 def PushChange(self):
264 """Pushes changes to the git repository.
265
266 Pushes locals commits from calls to CommitChange to the remote git
267 repository specified by os.pwd.
268
269 Raises:
270 OSError: Error occurred while pushing.
271 """
272 print 'Push currently not implemented'
273 # TODO(sosa): Un-comment once PushChange works.
274 # _Print('Pushing changes for %s' % self._ebuild.package)
275 # git_commit_cmd = 'git push'
276 # _RunCommand(git_commit_cmd)
277
278
279 def main(argv):
280 try:
281 argv = gflags.FLAGS(argv)
282 except gflags.FlagsError:
283 _PrintUsageAndDie()
284
285 package_list = gflags.FLAGS.packages.split(' ')
286 if gflags.FLAGS.commit_ids:
287 commit_id_list = gflags.FLAGS.commit_ids.split(' ')
288 else:
289 commit_id_list = None
290 _CheckSaneArguments(package_list, commit_id_list)
291
292 pwd = os.curdir
293 os.chdir(_CHROMIUMOS_OVERLAYS_DIRECTORY)
294
295 work_branch = _GitBranch('stabilizing_branch')
296 work_branch.CreateBranch()
297 if not work_branch.Exists():
298 generate_test_report.Die('Unable to create stabilizing branch')
299 index = 0
300 try:
301 for index in range(len(package_list)):
302 # Gather the package and optional commit id to work on.
303 package = package_list[index]
304 commit_id = ""
305 if commit_id_list:
306 commit_id = commit_id_list[index]
307
308 _Print('Working on %s' % package)
309 worker = EBuildStableMarker(_EBuild(package, commit_id))
310 worker.RevEBuild(commit_id)
311 worker.CommitChange(_GIT_COMMIT_MESSAGE % (package, commit_id))
312 if gflags.FLAGS.push:
313 worker.PushChange()
314
315 except (OSError, IOError):
316 print 'An exception occurred %s' % sys.exc_info()[0]
317 print 'Only the following packages were revved: %s' % package_list[:index]
318 print '''Note you will have to go into the chromiumos-overlay directory and
319 reset the git repo yourself.
320 '''
321 finally:
322 # Always run the last two cleanup functions.
323 work_branch.CleanUp()
324 os.chdir(pwd)
325
326
327 if __name__ == '__main__':
328 main(sys.argv)
329
OLDNEW
« no previous file with comments | « cros_mark_as_stable ('k') | cros_mark_as_stable_unittest.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698