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 from portage.versions import pkgsplit, pkgsplit, vercmp |
17 | 18 |
18 sys.path.append(os.path.join(os.path.dirname(__file__), 'lib')) | 19 sys.path.append(os.path.join(os.path.dirname(__file__), 'lib')) |
19 from cros_build_lib import Info, Warning, Die | 20 from cros_build_lib import Info, RunCommand, Warning, Die |
20 | 21 |
21 | 22 |
22 gflags.DEFINE_string('board', 'x86-generic', | 23 gflags.DEFINE_string('board', 'x86-generic', |
23 'Board for which the package belongs.', short_name='b') | 24 'Board for which the package belongs.', short_name='b') |
24 gflags.DEFINE_string('commit_ids', '', | 25 gflags.DEFINE_string('commit_ids', '', |
25 """Optional list of commit ids for each package. | 26 """Optional list of commit ids for each package. |
26 This list must either be empty or have the same length as | 27 This list must either be empty or have the same length as |
27 the packages list. If not set all rev'd ebuilds will have | 28 the packages list. If not set all rev'd ebuilds will have |
28 empty commit id's.""", | 29 empty commit id's.""", |
29 short_name='i') | 30 short_name='i') |
30 gflags.DEFINE_string('packages', '', | 31 gflags.DEFINE_string('packages', '', |
31 'Space separated list of packages to mark as stable.', | 32 'Space separated list of packages to mark as stable.', |
32 short_name='p') | 33 short_name='p') |
33 gflags.DEFINE_string('push_options', '', | 34 gflags.DEFINE_string('push_options', '', |
34 'Options to use with git-cl push using push command.') | 35 'Options to use with git-cl push using push command.') |
35 gflags.DEFINE_string('srcroot', '%s/trunk/src' % os.environ['HOME'], | 36 gflags.DEFINE_string('srcroot', '%s/trunk/src' % os.environ['HOME'], |
36 'Path to root src directory.', | 37 'Path to root src directory.', |
37 short_name='r') | 38 short_name='r') |
38 gflags.DEFINE_string('tracking_branch', 'cros/master', | 39 gflags.DEFINE_string('tracking_branch', 'cros/master', |
39 'Used with commit to specify branch to track against.', | 40 'Used with commit to specify branch to track against.', |
40 short_name='t') | 41 short_name='t') |
| 42 gflags.DEFINE_boolean('all', False, |
| 43 'Mark all packages as stable.') |
41 gflags.DEFINE_boolean('verbose', False, | 44 gflags.DEFINE_boolean('verbose', False, |
42 'Prints out verbose information about what is going on.', | 45 'Prints out verbose information about what is going on.', |
43 short_name='v') | 46 short_name='v') |
44 | 47 |
45 | 48 |
46 # Takes two strings, package_name and commit_id. | 49 # Takes two strings, package_name and commit_id. |
47 _GIT_COMMIT_MESSAGE = \ | 50 _GIT_COMMIT_MESSAGE = \ |
48 'Marking 9999 ebuild for %s with commit %s as stable.' | 51 'Marking 9999 ebuild for %s with commit %s as stable.' |
49 | 52 |
50 # Dictionary of valid commands with usage information. | 53 # Dictionary of valid commands with usage information. |
(...skipping 11 matching lines...) Expand all Loading... |
62 | 65 |
63 # ======================= Global Helper Functions ======================== | 66 # ======================= Global Helper Functions ======================== |
64 | 67 |
65 | 68 |
66 def _Print(message): | 69 def _Print(message): |
67 """Verbose print function.""" | 70 """Verbose print function.""" |
68 if gflags.FLAGS.verbose: | 71 if gflags.FLAGS.verbose: |
69 Info(message) | 72 Info(message) |
70 | 73 |
71 | 74 |
72 def _BuildEBuildDictionary(overlays, package_list, commit_id_list): | 75 def _BestEBuild(ebuilds): |
73 for index in range(len(package_list)): | 76 """Returns the newest EBuild from a list of EBuild objects.""" |
74 package = package_list[index] | 77 winner = ebuilds[0] |
75 commit_id = '' | 78 for ebuild in ebuilds[1:]: |
76 if commit_id_list: | 79 if vercmp(winner.version, ebuild.version) < 0: |
77 commit_id = commit_id_list[index] | 80 winner = ebuild |
78 ebuild = _EBuild(package, commit_id) | 81 return winner |
79 if ebuild.ebuild_path: | 82 |
80 for overlay in overlays: | 83 |
81 if ebuild.ebuild_path.startswith(overlay): | 84 def _FindStableEBuilds(files): |
82 overlays[overlay].append(ebuild) | 85 """Return a list of stable ebuilds from specified list of files. |
83 break | 86 |
84 else: | 87 Args: |
85 Die('No overlay found for %s' % ebuild.ebuild_path) | 88 files: List of files. |
86 else: | 89 """ |
87 Die('No ebuild found for %s' % package) | 90 workon_dir = False |
| 91 stable_ebuilds = [] |
| 92 unstable_ebuilds = [] |
| 93 for path in files: |
| 94 if path.endswith('.ebuild') and not os.path.islink(path): |
| 95 ebuild = _EBuild(path) |
| 96 if ebuild.is_workon: |
| 97 workon_dir = True |
| 98 if ebuild.is_stable: |
| 99 stable_ebuilds.append(ebuild) |
| 100 else: |
| 101 unstable_ebuilds.append(ebuild) |
| 102 |
| 103 # If we found a workon ebuild in this directory, apply some sanity checks. |
| 104 if workon_dir: |
| 105 if len(unstable_ebuilds) > 1: |
| 106 Die('Found multiple unstable ebuilds in %s' % root) |
| 107 if len(stable_ebuilds) > 1: |
| 108 stable_ebuilds = [_BestEBuild(stable_ebuilds)] |
| 109 |
| 110 # Print a warning if multiple stable ebuilds are found in the same |
| 111 # directory. Storing multiple stable ebuilds is error-prone because |
| 112 # the older ebuilds will not get rev'd. |
| 113 # |
| 114 # We make a special exception for x11-drivers/xf86-video-msm for legacy |
| 115 # reasons. |
| 116 if stable_ebuilds[0].package != 'x11-drivers/xf86-video-msm': |
| 117 Warning('Found multiple stable ebuilds in %s' % root) |
| 118 |
| 119 if not unstable_ebuilds: |
| 120 Die('Missing 9999 ebuild in %s' % root) |
| 121 if not stable_ebuilds: |
| 122 Die('Missing stable ebuild in %s' % root) |
| 123 |
| 124 if stable_ebuilds: |
| 125 return stable_ebuilds[0] |
| 126 else: |
| 127 return None |
| 128 |
| 129 |
| 130 def _BuildEBuildDictionary(overlays, all, packages): |
| 131 """Build a dictionary of the ebuilds in the specified overlays. |
| 132 |
| 133 overlays: A map which maps overlay directories to arrays of stable EBuilds |
| 134 inside said directories. |
| 135 all: Whether to include all ebuilds in the specified directories. If true, |
| 136 then we gather all packages in the directories regardless of whether |
| 137 they are in our set of packages. |
| 138 packages: A set of the packages we want to gather. |
| 139 """ |
| 140 for overlay in overlays: |
| 141 for root_dir, dirs, files in os.walk(overlay): |
| 142 # Add stable ebuilds to overlays[overlay]. |
| 143 paths = [os.path.join(root_dir, path) for path in files] |
| 144 ebuild = _FindStableEBuilds(paths) |
| 145 |
| 146 # If the --all option isn't used, we only want to update packages that |
| 147 # are in packages. |
| 148 if ebuild and (all or ebuild.package in packages): |
| 149 overlays[overlay].append(ebuild) |
88 | 150 |
89 | 151 |
90 def _CheckOnStabilizingBranch(): | 152 def _CheckOnStabilizingBranch(): |
91 """Returns true if the git branch is on the stabilizing branch.""" | 153 """Returns true if the git branch is on the stabilizing branch.""" |
92 current_branch = _SimpleRunCommand('git branch | grep \*').split()[1] | 154 current_branch = _SimpleRunCommand('git branch | grep \*').split()[1] |
93 return current_branch == _STABLE_BRANCH_NAME | 155 return current_branch == _STABLE_BRANCH_NAME |
94 | 156 |
95 | 157 |
96 def _CheckSaneArguments(package_list, commit_id_list, command): | 158 def _CheckSaneArguments(package_list, command): |
97 """Checks to make sure the flags are sane. Dies if arguments are not sane.""" | 159 """Checks to make sure the flags are sane. Dies if arguments are not sane.""" |
98 if not command in _COMMAND_DICTIONARY.keys(): | 160 if not command in _COMMAND_DICTIONARY.keys(): |
99 _PrintUsageAndDie('%s is not a valid command' % command) | 161 _PrintUsageAndDie('%s is not a valid command' % command) |
100 if not gflags.FLAGS.packages and command == 'commit': | 162 if not gflags.FLAGS.packages and command == 'commit' and not gflags.FLAGS.all: |
101 _PrintUsageAndDie('Please specify at least one package') | 163 _PrintUsageAndDie('Please specify at least one package') |
102 if not gflags.FLAGS.board and command == 'commit': | 164 if not gflags.FLAGS.board and command == 'commit': |
103 _PrintUsageAndDie('Please specify a board') | 165 _PrintUsageAndDie('Please specify a board') |
104 if not os.path.isdir(gflags.FLAGS.srcroot): | 166 if not os.path.isdir(gflags.FLAGS.srcroot): |
105 _PrintUsageAndDie('srcroot is not a valid path') | 167 _PrintUsageAndDie('srcroot is not a valid path') |
106 if commit_id_list and (len(package_list) != len(commit_id_list)): | |
107 _PrintUsageAndDie( | |
108 'Package list is not the same length as the commit id list') | |
109 | 168 |
110 | 169 |
111 def _Clean(): | 170 def _Clean(): |
112 """Cleans up uncommitted changes on either stabilizing branch or master.""" | 171 """Cleans up uncommitted changes on either stabilizing branch or master.""" |
113 _SimpleRunCommand('git reset HEAD --hard') | 172 _SimpleRunCommand('git reset HEAD --hard') |
114 _SimpleRunCommand('git checkout %s' % gflags.FLAGS.tracking_branch) | 173 _SimpleRunCommand('git checkout %s' % gflags.FLAGS.tracking_branch) |
115 | 174 |
116 | 175 |
117 def _PrintUsageAndDie(error_message=''): | 176 def _PrintUsageAndDie(error_message=''): |
118 """Prints optional error_message the usage and returns an error exit code.""" | 177 """Prints optional error_message the usage and returns an error exit code.""" |
(...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
207 Returns True on success. | 266 Returns True on success. |
208 """ | 267 """ |
209 self._Checkout(gflags.FLAGS.tracking_branch, create=False) | 268 self._Checkout(gflags.FLAGS.tracking_branch, create=False) |
210 delete_cmd = 'git branch -D %s' % self.branch_name | 269 delete_cmd = 'git branch -D %s' % self.branch_name |
211 _SimpleRunCommand(delete_cmd) | 270 _SimpleRunCommand(delete_cmd) |
212 | 271 |
213 | 272 |
214 class _EBuild(object): | 273 class _EBuild(object): |
215 """Wrapper class for an ebuild.""" | 274 """Wrapper class for an ebuild.""" |
216 | 275 |
217 def __init__(self, package, commit_id=None): | 276 def __init__(self, path): |
218 """Initializes all data about an ebuild. | 277 """Initializes all data about an ebuild. |
219 | 278 |
220 Uses equery to find the ebuild path and sets data about an ebuild for | 279 Uses equery to find the ebuild path and sets data about an ebuild for |
221 easy reference. | 280 easy reference. |
222 """ | 281 """ |
223 self.package = package | 282 self.ebuild_path = path |
224 self.ebuild_path = self._FindEBuildPath(package) | |
225 (self.ebuild_path_no_revision, | 283 (self.ebuild_path_no_revision, |
226 self.ebuild_path_no_version, | 284 self.ebuild_path_no_version, |
227 self.current_revision) = self._ParseEBuildPath(self.ebuild_path) | 285 self.current_revision) = self._ParseEBuildPath(self.ebuild_path) |
228 self.commit_id = commit_id | 286 _, self.category, pkgpath, filename = path.rsplit('/', 3) |
| 287 filename_no_suffix = os.path.join(filename.replace('.ebuild', '')) |
| 288 self.pkgname, version_no_rev, rev = pkgsplit(filename_no_suffix) |
| 289 self.version = '%s-%s' % (version_no_rev, rev) |
| 290 self.package = '%s/%s' % (self.category, self.pkgname) |
| 291 self.is_workon = False |
| 292 self.is_stable = False |
229 | 293 |
230 @classmethod | 294 for line in fileinput.input(path): |
231 def _FindEBuildPath(cls, package): | 295 if line.startswith('inherit ') and 'cros-workon' in line: |
232 """Static method that returns the full path of an ebuild.""" | 296 self.is_workon = True |
233 _Print('Looking for unstable ebuild for %s' % package) | 297 elif (line.startswith('KEYWORDS=') and '~' not in line and |
234 equery_cmd = ( | 298 ('amd64' in line or 'x86' in line or 'arm' in line)): |
235 'ACCEPT_KEYWORDS="x86 arm amd64" equery-%s which %s 2> /dev/null' | 299 self.is_stable = True |
236 % (gflags.FLAGS.board, package)) | 300 fileinput.close() |
237 path = _SimpleRunCommand(equery_cmd) | 301 |
238 if path: | 302 def GetCommitId(self): |
239 _Print('Unstable ebuild found at %s' % path) | 303 """Get the commit id for this ebuild.""" |
240 return path.rstrip() | 304 |
| 305 # Grab and evaluate CROS_WORKON variables from this ebuild. |
| 306 unstable_ebuild = '%s-9999.ebuild' % self.ebuild_path_no_version |
| 307 cmd = ('CROS_WORKON_LOCALNAME="%s" CROS_WORKON_PROJECT="%s" ' |
| 308 'eval $(grep -E "^CROS_WORKON" %s) && ' |
| 309 'echo $CROS_WORKON_PROJECT ' |
| 310 '$CROS_WORKON_LOCALNAME/$CROS_WORKON_SUBDIR' |
| 311 % (self.pkgname, self.pkgname, unstable_ebuild)) |
| 312 project, subdir = _SimpleRunCommand(cmd).split() |
| 313 |
| 314 # Calculate srcdir. |
| 315 srcroot = gflags.FLAGS.srcroot |
| 316 if self.category == 'chromeos-base': |
| 317 dir = 'platform' |
| 318 else: |
| 319 dir = 'third_party' |
| 320 srcdir = os.path.join(srcroot, dir, subdir) |
| 321 |
| 322 # TODO(anush): This hack is only necessary because the kernel ebuild has |
| 323 # 'if' statements, so we can't grab the CROS_WORKON_LOCALNAME properly. |
| 324 # We should clean up the kernel ebuild and remove this hack. |
| 325 if not os.path.exists(srcdir) and subdir == 'kernel/': |
| 326 srcdir = os.path.join(srcroot, 'third_party/kernel/files') |
| 327 |
| 328 if not os.path.exists(srcdir): |
| 329 Die('Cannot find commit id for %s' % self.ebuild_path) |
| 330 |
| 331 # Verify that we're grabbing the commit id from the right project name. |
| 332 # NOTE: chromeos-kernel has the wrong project name, so it fails this |
| 333 # check. |
| 334 # TODO(davidjames): Fix the project name in the chromeos-kernel ebuild. |
| 335 cmd = 'cd %s && git config --get remote.cros.projectname' % srcdir |
| 336 actual_project =_SimpleRunCommand(cmd).rstrip() |
| 337 if project not in (actual_project, 'chromeos-kernel'): |
| 338 Die('Project name mismatch for %s (%s != %s)' % (unstable_ebuild, project, |
| 339 actual_project)) |
| 340 |
| 341 # Get commit id. |
| 342 output = _SimpleRunCommand('cd %s && git rev-parse HEAD' % srcdir) |
| 343 if not output: |
| 344 Die('Missing commit id for %s' % self.ebuild_path) |
| 345 return output.rstrip() |
241 | 346 |
242 @classmethod | 347 @classmethod |
243 def _ParseEBuildPath(cls, ebuild_path): | 348 def _ParseEBuildPath(cls, ebuild_path): |
244 """Static method that parses the path of an ebuild | 349 """Static method that parses the path of an ebuild |
245 | 350 |
246 Returns a tuple containing the (ebuild path without the revision | 351 Returns a tuple containing the (ebuild path without the revision |
247 string, without the version string, and the current revision number for | 352 string, without the version string, and the current revision number for |
248 the ebuild). | 353 the ebuild). |
249 """ | 354 """ |
250 # Get the ebuild name without the revision string. | 355 # Get the ebuild name without the revision string. |
(...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
310 redirect_file.write(line.replace("~", "")) | 415 redirect_file.write(line.replace("~", "")) |
311 elif line.startswith('EAPI'): | 416 elif line.startswith('EAPI'): |
312 # Always add new commit_id after EAPI definition. | 417 # Always add new commit_id after EAPI definition. |
313 redirect_file.write(line) | 418 redirect_file.write(line) |
314 redirect_file.write('CROS_WORKON_COMMIT="%s"\n' % commit_id) | 419 redirect_file.write('CROS_WORKON_COMMIT="%s"\n' % commit_id) |
315 elif not line.startswith('CROS_WORKON_COMMIT'): | 420 elif not line.startswith('CROS_WORKON_COMMIT'): |
316 # Skip old CROS_WORKON_COMMIT definition. | 421 # Skip old CROS_WORKON_COMMIT definition. |
317 redirect_file.write(line) | 422 redirect_file.write(line) |
318 fileinput.close() | 423 fileinput.close() |
319 | 424 |
320 _Print('Adding new stable ebuild to git') | 425 # If the new ebuild is identical to the old ebuild, return False and |
321 _SimpleRunCommand('git add %s' % new_ebuild_path) | 426 # delete our changes. |
| 427 old_ebuild_path = self._ebuild.ebuild_path |
| 428 diff_cmd = ['diff', '-Bu', old_ebuild_path, new_ebuild_path] |
| 429 if 0 == RunCommand(diff_cmd, exit_code=True, |
| 430 print_cmd=gflags.FLAGS.verbose): |
| 431 os.unlink(new_ebuild_path) |
| 432 return False |
| 433 else: |
| 434 _Print('Adding new stable ebuild to git') |
| 435 _SimpleRunCommand('git add %s' % new_ebuild_path) |
322 | 436 |
323 _Print('Removing old ebuild from git') | 437 _Print('Removing old ebuild from git') |
324 _SimpleRunCommand('git rm %s' % self._ebuild.ebuild_path) | 438 _SimpleRunCommand('git rm %s' % old_ebuild_path) |
| 439 return True |
325 | 440 |
326 def CommitChange(self, message): | 441 def CommitChange(self, message): |
327 """Commits current changes in git locally. | 442 """Commits current changes in git locally. |
328 | 443 |
329 This method will take any changes from invocations to RevEBuild | 444 This method will take any changes from invocations to RevEBuild |
330 and commits them locally in the git repository that contains os.pwd. | 445 and commits them locally in the git repository that contains os.pwd. |
331 | 446 |
332 Args: | 447 Args: |
333 message: the commit string to write when committing to git. | 448 message: the commit string to write when committing to git. |
334 | 449 |
(...skipping 10 matching lines...) Expand all Loading... |
345 try: | 460 try: |
346 argv = gflags.FLAGS(argv) | 461 argv = gflags.FLAGS(argv) |
347 if len(argv) != 2: | 462 if len(argv) != 2: |
348 _PrintUsageAndDie('Must specify a valid command') | 463 _PrintUsageAndDie('Must specify a valid command') |
349 else: | 464 else: |
350 command = argv[1] | 465 command = argv[1] |
351 except gflags.FlagsError, e : | 466 except gflags.FlagsError, e : |
352 _PrintUsageAndDie(str(e)) | 467 _PrintUsageAndDie(str(e)) |
353 | 468 |
354 package_list = gflags.FLAGS.packages.split() | 469 package_list = gflags.FLAGS.packages.split() |
355 if gflags.FLAGS.commit_ids: | 470 _CheckSaneArguments(package_list, command) |
356 commit_id_list = gflags.FLAGS.commit_ids.split() | |
357 else: | |
358 commit_id_list = None | |
359 _CheckSaneArguments(package_list, commit_id_list, command) | |
360 | 471 |
361 overlays = { | 472 overlays = { |
362 '%s/private-overlays/chromeos-overlay' % gflags.FLAGS.srcroot: [], | 473 '%s/private-overlays/chromeos-overlay' % gflags.FLAGS.srcroot: [], |
363 '%s/third_party/chromiumos-overlay' % gflags.FLAGS.srcroot: [] | 474 '%s/third_party/chromiumos-overlay' % gflags.FLAGS.srcroot: [] |
364 } | 475 } |
365 _BuildEBuildDictionary(overlays, package_list, commit_id_list) | 476 all = gflags.FLAGS.all |
| 477 |
| 478 if command == 'commit': |
| 479 _BuildEBuildDictionary(overlays, all, package_list) |
366 | 480 |
367 for overlay, ebuilds in overlays.items(): | 481 for overlay, ebuilds in overlays.items(): |
368 if not os.path.exists(overlay): | 482 if not os.path.exists(overlay): |
369 continue | 483 continue |
370 os.chdir(overlay) | 484 os.chdir(overlay) |
371 | 485 |
372 if command == 'clean': | 486 if command == 'clean': |
373 _Clean() | 487 _Clean() |
374 elif command == 'push': | 488 elif command == 'push': |
375 _PushChange() | 489 _PushChange() |
376 elif command == 'commit' and ebuilds: | 490 elif command == 'commit' and ebuilds: |
377 work_branch = _GitBranch(_STABLE_BRANCH_NAME) | |
378 work_branch.CreateBranch() | |
379 if not work_branch.Exists(): | |
380 Die('Unable to create stabilizing branch in %s' % overlay) | |
381 for ebuild in ebuilds: | 491 for ebuild in ebuilds: |
382 try: | 492 try: |
383 _Print('Working on %s' % ebuild.package) | 493 _Print('Working on %s' % ebuild.package) |
384 worker = EBuildStableMarker(ebuild) | 494 worker = EBuildStableMarker(ebuild) |
385 worker.RevEBuild(ebuild.commit_id) | 495 commit_id = ebuild.GetCommitId() |
386 message = _GIT_COMMIT_MESSAGE % (ebuild.package, ebuild.commit_id) | 496 if worker.RevEBuild(commit_id): |
387 worker.CommitChange(message) | 497 if not _CheckOnStabilizingBranch(): |
| 498 work_branch = _GitBranch(_STABLE_BRANCH_NAME) |
| 499 work_branch.CreateBranch() |
| 500 if not work_branch.Exists(): |
| 501 Die('Unable to create stabilizing branch in %s' % overlay) |
| 502 message = _GIT_COMMIT_MESSAGE % (ebuild.package, commit_id) |
| 503 worker.CommitChange(message) |
388 except (OSError, IOError): | 504 except (OSError, IOError): |
389 Warning('Cannot rev %s\n' % ebuild.package, | 505 Warning('Cannot rev %s\n' % ebuild.package, |
390 'Note you will have to go into %s ' | 506 'Note you will have to go into %s ' |
391 'and reset the git repo yourself.' % overlay) | 507 'and reset the git repo yourself.' % overlay) |
392 raise | 508 raise |
393 | 509 |
394 | 510 |
395 if __name__ == '__main__': | 511 if __name__ == '__main__': |
396 main(sys.argv) | 512 main(sys.argv) |
397 | 513 |
OLD | NEW |