Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 1 | #!/usr/bin/python |
| 2 | |
Chris Sosa | c13bba5 | 2011-05-24 15:14:09 -0700 | [diff] [blame] | 3 | # Copyright (c) 2011 The Chromium OS Authors. All rights reserved. |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 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 | |
Ryan Cui | 4656a3c | 2011-05-24 12:30:30 -0700 | [diff] [blame] | 9 | import constants |
Chris Sosa | 8be3913 | 2011-04-14 12:09:24 -0700 | [diff] [blame] | 10 | import filecmp |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 11 | import fileinput |
Chris Sosa | 62ad852 | 2011-03-08 17:46:17 -0800 | [diff] [blame] | 12 | import optparse |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 13 | import os |
| 14 | import re |
| 15 | import shutil |
| 16 | import subprocess |
| 17 | import sys |
| 18 | |
Chris Sosa | 471532a | 2011-02-01 15:10:06 -0800 | [diff] [blame] | 19 | if __name__ == '__main__': |
Ryan Cui | 4656a3c | 2011-05-24 12:30:30 -0700 | [diff] [blame] | 20 | sys.path.append(constants.SOURCE_ROOT) |
Chris Sosa | 471532a | 2011-02-01 15:10:06 -0800 | [diff] [blame] | 21 | |
Chris Sosa | c13bba5 | 2011-05-24 15:14:09 -0700 | [diff] [blame] | 22 | from chromite.lib import cros_build_lib |
| 23 | |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 24 | |
Chris Sosa | 62ad852 | 2011-03-08 17:46:17 -0800 | [diff] [blame] | 25 | # TODO(sosa): Remove during OO refactor. |
| 26 | VERBOSE = False |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 27 | |
| 28 | # Takes two strings, package_name and commit_id. |
| 29 | _GIT_COMMIT_MESSAGE = 'Marking 9999 ebuild for %s with commit %s as stable.' |
| 30 | |
| 31 | # Dictionary of valid commands with usage information. |
| 32 | COMMAND_DICTIONARY = { |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 33 | 'commit': |
| 34 | 'Marks given ebuilds as stable locally', |
| 35 | 'push': |
| 36 | 'Pushes previous marking of ebuilds to remote repo', |
| 37 | } |
| 38 | |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 39 | |
| 40 | def BestEBuild(ebuilds): |
| 41 | """Returns the newest EBuild from a list of EBuild objects.""" |
| 42 | from portage.versions import vercmp |
| 43 | winner = ebuilds[0] |
| 44 | for ebuild in ebuilds[1:]: |
| 45 | if vercmp(winner.version, ebuild.version) < 0: |
| 46 | winner = ebuild |
| 47 | return winner |
| 48 | |
| 49 | # ======================= Global Helper Functions ======================== |
| 50 | |
| 51 | |
| 52 | def _Print(message): |
| 53 | """Verbose print function.""" |
Chris Sosa | c31bd2d | 2011-04-29 17:53:35 -0700 | [diff] [blame] | 54 | if VERBOSE: |
Chris Sosa | c13bba5 | 2011-05-24 15:14:09 -0700 | [diff] [blame] | 55 | cros_build_lib.Info(message) |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 56 | |
| 57 | |
Chris Sosa | bf15387 | 2011-04-28 14:21:09 -0700 | [diff] [blame] | 58 | def CleanStalePackages(board, package_atoms): |
| 59 | """Cleans up stale package info from a previous build. |
| 60 | Args: |
| 61 | board: Board to clean the packages from. |
| 62 | package_atoms: The actual package atom to unmerge. |
| 63 | """ |
| 64 | if package_atoms: |
Chris Sosa | c13bba5 | 2011-05-24 15:14:09 -0700 | [diff] [blame] | 65 | cros_build_lib.Info('Cleaning up stale packages %s.' % package_atoms) |
Chris Sosa | bf15387 | 2011-04-28 14:21:09 -0700 | [diff] [blame] | 66 | unmerge_board_cmd = ['emerge-%s' % board, '--unmerge'] |
| 67 | unmerge_board_cmd.extend(package_atoms) |
Chris Sosa | c13bba5 | 2011-05-24 15:14:09 -0700 | [diff] [blame] | 68 | cros_build_lib.RunCommand(unmerge_board_cmd) |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 69 | |
Chris Sosa | bf15387 | 2011-04-28 14:21:09 -0700 | [diff] [blame] | 70 | unmerge_host_cmd = ['sudo', 'emerge', '--unmerge'] |
| 71 | unmerge_host_cmd.extend(package_atoms) |
Chris Sosa | c13bba5 | 2011-05-24 15:14:09 -0700 | [diff] [blame] | 72 | cros_build_lib.RunCommand(unmerge_host_cmd) |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 73 | |
Chris Sosa | c13bba5 | 2011-05-24 15:14:09 -0700 | [diff] [blame] | 74 | cros_build_lib.RunCommand(['eclean-%s' % board, '-d', 'packages'], |
| 75 | redirect_stderr=True) |
| 76 | cros_build_lib.RunCommand(['sudo', 'eclean', '-d', 'packages'], |
| 77 | redirect_stderr=True) |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 78 | |
| 79 | |
Chris Sosa | 95e85d9 | 2011-05-18 16:15:33 -0700 | [diff] [blame] | 80 | class _BlackListManager(object): |
| 81 | """Small wrapper class to manage black lists for marking all packages.""" |
| 82 | BLACK_LIST_FILE = os.path.join(os.path.dirname(os.path.realpath(__file__)), |
| 83 | 'cros_mark_as_stable_blacklist') |
| 84 | |
| 85 | def __init__(self): |
| 86 | """Initializes the black list manager.""" |
| 87 | self.black_list_re_array = None |
| 88 | self._Initialize() |
| 89 | |
| 90 | def _Initialize(self): |
| 91 | """Initializes the black list manager from a black list file.""" |
| 92 | self.black_list_re_array = [] |
| 93 | with open(self.BLACK_LIST_FILE) as file_handle: |
| 94 | for line in file_handle.readlines(): |
| 95 | line = line.strip() |
| 96 | # Ignore comment lines. |
| 97 | if line and not line.startswith('#'): |
| 98 | line = line.rstrip() |
| 99 | package_array = line.split('/') |
| 100 | assert len(package_array) == 2, \ |
| 101 | 'Line %s does not match package format.' % line |
| 102 | category, package_name = package_array |
| 103 | self.black_list_re_array.append( |
| 104 | re.compile('.*/%s/%s/%s-.*\.ebuild' % (category, package_name, |
| 105 | package_name))) |
| 106 | |
| 107 | def IsPackageBlackListed(self, path_to_ebuild): |
| 108 | """Returns True if the package given by the path is blacklisted.""" |
| 109 | assert self.black_list_re_array != None, 'Black list not initialized.' |
| 110 | |
| 111 | for re in self.black_list_re_array: |
| 112 | if re.match(path_to_ebuild): |
| 113 | return True |
| 114 | |
| 115 | return False |
| 116 | |
| 117 | |
| 118 | def _FindUprevCandidates(files, blacklist): |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 119 | """Return a list of uprev candidates from specified list of files. |
| 120 | |
| 121 | Usually an uprev candidate is a the stable ebuild in a cros_workon directory. |
| 122 | However, if no such stable ebuild exists (someone just checked in the 9999 |
| 123 | ebuild), this is the unstable ebuild. |
| 124 | |
| 125 | Args: |
| 126 | files: List of files. |
| 127 | """ |
| 128 | workon_dir = False |
| 129 | stable_ebuilds = [] |
| 130 | unstable_ebuilds = [] |
| 131 | for path in files: |
| 132 | if path.endswith('.ebuild') and not os.path.islink(path): |
| 133 | ebuild = EBuild(path) |
Chris Sosa | 95e85d9 | 2011-05-18 16:15:33 -0700 | [diff] [blame] | 134 | if ebuild.is_workon and not blacklist.IsPackageBlackListed(path): |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 135 | workon_dir = True |
| 136 | if ebuild.is_stable: |
| 137 | stable_ebuilds.append(ebuild) |
| 138 | else: |
| 139 | unstable_ebuilds.append(ebuild) |
| 140 | |
| 141 | # If we found a workon ebuild in this directory, apply some sanity checks. |
| 142 | if workon_dir: |
| 143 | if len(unstable_ebuilds) > 1: |
Chris Sosa | c13bba5 | 2011-05-24 15:14:09 -0700 | [diff] [blame] | 144 | cros_build_lib.Die('Found multiple unstable ebuilds in %s' % |
| 145 | os.path.dirname(path)) |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 146 | if len(stable_ebuilds) > 1: |
| 147 | stable_ebuilds = [BestEBuild(stable_ebuilds)] |
| 148 | |
| 149 | # Print a warning if multiple stable ebuilds are found in the same |
| 150 | # directory. Storing multiple stable ebuilds is error-prone because |
| 151 | # the older ebuilds will not get rev'd. |
Chris Sosa | c13bba5 | 2011-05-24 15:14:09 -0700 | [diff] [blame] | 152 | cros_build_lib.Warning('Found multiple stable ebuilds in %s' % |
| 153 | os.path.dirname(path)) |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 154 | |
| 155 | if not unstable_ebuilds: |
Chris Sosa | c13bba5 | 2011-05-24 15:14:09 -0700 | [diff] [blame] | 156 | cros_build_lib.Die('Missing 9999 ebuild in %s' % os.path.dirname(path)) |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 157 | if not stable_ebuilds: |
Chris Sosa | c13bba5 | 2011-05-24 15:14:09 -0700 | [diff] [blame] | 158 | cros_build_lib.Warning('Missing stable ebuild in %s' % |
| 159 | os.path.dirname(path)) |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 160 | return unstable_ebuilds[0] |
| 161 | |
| 162 | if stable_ebuilds: |
| 163 | return stable_ebuilds[0] |
| 164 | else: |
| 165 | return None |
| 166 | |
| 167 | |
Chris Sosa | 95e85d9 | 2011-05-18 16:15:33 -0700 | [diff] [blame] | 168 | def _BuildEBuildDictionary(overlays, all, packages, blacklist): |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 169 | """Build a dictionary of the ebuilds in the specified overlays. |
| 170 | |
| 171 | overlays: A map which maps overlay directories to arrays of stable EBuilds |
| 172 | inside said directories. |
| 173 | all: Whether to include all ebuilds in the specified directories. If true, |
| 174 | then we gather all packages in the directories regardless of whether |
| 175 | they are in our set of packages. |
| 176 | packages: A set of the packages we want to gather. |
| 177 | """ |
| 178 | for overlay in overlays: |
Chris Sosa | 95e85d9 | 2011-05-18 16:15:33 -0700 | [diff] [blame] | 179 | for package_dir, unused_dirs, files in os.walk(overlay): |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 180 | # Add stable ebuilds to overlays[overlay]. |
| 181 | paths = [os.path.join(package_dir, path) for path in files] |
Chris Sosa | 95e85d9 | 2011-05-18 16:15:33 -0700 | [diff] [blame] | 182 | ebuild = _FindUprevCandidates(paths, blacklist) |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 183 | |
| 184 | # If the --all option isn't used, we only want to update packages that |
| 185 | # are in packages. |
| 186 | if ebuild and (all or ebuild.package in packages): |
| 187 | overlays[overlay].append(ebuild) |
| 188 | |
| 189 | |
| 190 | def _DoWeHaveLocalCommits(stable_branch, tracking_branch): |
| 191 | """Returns true if there are local commits.""" |
| 192 | current_branch = _SimpleRunCommand('git branch | grep \*').split()[1] |
| 193 | if current_branch == stable_branch: |
| 194 | current_commit_id = _SimpleRunCommand('git rev-parse HEAD') |
| 195 | tracking_commit_id = _SimpleRunCommand('git rev-parse %s' % tracking_branch) |
| 196 | return current_commit_id != tracking_commit_id |
| 197 | else: |
| 198 | return False |
| 199 | |
| 200 | |
Chris Sosa | 62ad852 | 2011-03-08 17:46:17 -0800 | [diff] [blame] | 201 | def _CheckSaneArguments(package_list, command, options): |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 202 | """Checks to make sure the flags are sane. Dies if arguments are not sane.""" |
| 203 | if not command in COMMAND_DICTIONARY.keys(): |
| 204 | _PrintUsageAndDie('%s is not a valid command' % command) |
Chris Sosa | 62ad852 | 2011-03-08 17:46:17 -0800 | [diff] [blame] | 205 | if not options.packages and command == 'commit' and not options.all: |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 206 | _PrintUsageAndDie('Please specify at least one package') |
Chris Sosa | 62ad852 | 2011-03-08 17:46:17 -0800 | [diff] [blame] | 207 | if not options.board and command == 'commit': |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 208 | _PrintUsageAndDie('Please specify a board') |
Chris Sosa | 62ad852 | 2011-03-08 17:46:17 -0800 | [diff] [blame] | 209 | if not os.path.isdir(options.srcroot): |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 210 | _PrintUsageAndDie('srcroot is not a valid path') |
Chris Sosa | 62ad852 | 2011-03-08 17:46:17 -0800 | [diff] [blame] | 211 | options.srcroot = os.path.abspath(options.srcroot) |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 212 | |
| 213 | |
| 214 | def _PrintUsageAndDie(error_message=''): |
| 215 | """Prints optional error_message the usage and returns an error exit code.""" |
| 216 | command_usage = 'Commands: \n' |
| 217 | # Add keys and usage information from dictionary. |
| 218 | commands = sorted(COMMAND_DICTIONARY.keys()) |
| 219 | for command in commands: |
| 220 | command_usage += ' %s: %s\n' % (command, COMMAND_DICTIONARY[command]) |
| 221 | commands_str = '|'.join(commands) |
Chris Sosa | c13bba5 | 2011-05-24 15:14:09 -0700 | [diff] [blame] | 222 | cros_build_lib.Warning('Usage: %s FLAGS [%s]\n\n%s' % ( |
| 223 | sys.argv[0], commands_str, command_usage)) |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 224 | if error_message: |
Chris Sosa | c13bba5 | 2011-05-24 15:14:09 -0700 | [diff] [blame] | 225 | cros_build_lib.Die(error_message) |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 226 | else: |
| 227 | sys.exit(1) |
| 228 | |
| 229 | |
| 230 | def _SimpleRunCommand(command): |
| 231 | """Runs a shell command and returns stdout back to caller.""" |
| 232 | _Print(' + %s' % command) |
| 233 | proc_handle = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True) |
| 234 | stdout = proc_handle.communicate()[0] |
| 235 | retcode = proc_handle.wait() |
| 236 | if retcode != 0: |
| 237 | _Print(stdout) |
| 238 | raise subprocess.CalledProcessError(retcode, command) |
| 239 | return stdout |
| 240 | |
| 241 | |
| 242 | # ======================= End Global Helper Functions ======================== |
| 243 | |
| 244 | |
Chris Sosa | 62ad852 | 2011-03-08 17:46:17 -0800 | [diff] [blame] | 245 | def PushChange(stable_branch, tracking_branch, dryrun): |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 246 | """Pushes commits in the stable_branch to the remote git repository. |
| 247 | |
| 248 | Pushes locals commits from calls to CommitChange to the remote git |
| 249 | repository specified by current working directory. |
| 250 | |
| 251 | Args: |
| 252 | stable_branch: The local branch with commits we want to push. |
| 253 | tracking_branch: The tracking branch of the local branch. |
Chris Sosa | 62ad852 | 2011-03-08 17:46:17 -0800 | [diff] [blame] | 254 | dryrun: Use git push --dryrun to emulate a push. |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 255 | Raises: |
| 256 | OSError: Error occurred while pushing. |
| 257 | """ |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 258 | # Sanity check to make sure we're on a stabilizing branch before pushing. |
| 259 | if not _DoWeHaveLocalCommits(stable_branch, tracking_branch): |
Chris Sosa | c13bba5 | 2011-05-24 15:14:09 -0700 | [diff] [blame] | 260 | cros_build_lib.Info('Not work found to push. Exiting') |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 261 | return |
| 262 | |
| 263 | description = _SimpleRunCommand('git log --format=format:%s%n%n%b ' + |
| 264 | tracking_branch + '..') |
| 265 | description = 'Marking set of ebuilds as stable\n\n%s' % description |
Chris Sosa | c13bba5 | 2011-05-24 15:14:09 -0700 | [diff] [blame] | 266 | cros_build_lib.Info('Using description %s' % description) |
Ryan Cui | 05a31ba | 2011-05-31 17:47:37 -0700 | [diff] [blame] | 267 | merge_branch = GitBranch(constants.MERGE_BRANCH, tracking_branch) |
Chris Sosa | c13bba5 | 2011-05-24 15:14:09 -0700 | [diff] [blame] | 268 | if merge_branch.Exists(): |
| 269 | merge_branch.Delete() |
| 270 | _SimpleRunCommand('repo sync .') |
| 271 | merge_branch.CreateBranch() |
| 272 | if not merge_branch.Exists(): |
| 273 | cros_build_lib.Die('Unable to create merge branch.') |
| 274 | _SimpleRunCommand('git merge --squash %s' % stable_branch) |
| 275 | _SimpleRunCommand('git commit -m "%s"' % description) |
| 276 | _SimpleRunCommand('git config push.default tracking') |
Ryan Cui | 05a31ba | 2011-05-31 17:47:37 -0700 | [diff] [blame] | 277 | cros_build_lib.GitPushWithRetry(constants.MERGE_BRANCH, cwd='.', |
| 278 | dryrun=dryrun) |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 279 | |
| 280 | |
| 281 | class GitBranch(object): |
| 282 | """Wrapper class for a git branch.""" |
| 283 | |
| 284 | def __init__(self, branch_name, tracking_branch): |
| 285 | """Sets up variables but does not create the branch.""" |
| 286 | self.branch_name = branch_name |
| 287 | self.tracking_branch = tracking_branch |
| 288 | |
| 289 | def CreateBranch(self): |
| 290 | GitBranch.Checkout(self) |
| 291 | |
| 292 | @classmethod |
| 293 | def Checkout(cls, target): |
| 294 | """Function used to check out to another GitBranch.""" |
| 295 | if target.branch_name == target.tracking_branch or target.Exists(): |
| 296 | git_cmd = 'git checkout %s -f' % target.branch_name |
| 297 | else: |
Chris Sosa | 8cae72b | 2011-06-07 17:05:03 -0700 | [diff] [blame] | 298 | git_cmd = 'repo start %s .' % target.branch_name |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 299 | _SimpleRunCommand(git_cmd) |
| 300 | |
| 301 | def Exists(self): |
| 302 | """Returns True if the branch exists.""" |
| 303 | branch_cmd = 'git branch' |
| 304 | branches = _SimpleRunCommand(branch_cmd) |
| 305 | return self.branch_name in branches.split() |
| 306 | |
| 307 | def Delete(self): |
| 308 | """Deletes the branch and returns the user to the master branch. |
| 309 | |
| 310 | Returns True on success. |
| 311 | """ |
| 312 | tracking_branch = GitBranch(self.tracking_branch, self.tracking_branch) |
| 313 | GitBranch.Checkout(tracking_branch) |
Chris Sosa | 8cae72b | 2011-06-07 17:05:03 -0700 | [diff] [blame] | 314 | delete_cmd = 'repo abandon %s' % self.branch_name |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 315 | _SimpleRunCommand(delete_cmd) |
| 316 | |
| 317 | |
| 318 | class EBuild(object): |
| 319 | """Wrapper class for information about an ebuild.""" |
| 320 | |
| 321 | def __init__(self, path): |
| 322 | """Sets up data about an ebuild from its path.""" |
| 323 | from portage.versions import pkgsplit |
| 324 | unused_path, self.category, self.pkgname, filename = path.rsplit('/', 3) |
| 325 | unused_pkgname, self.version_no_rev, rev = pkgsplit( |
| 326 | filename.replace('.ebuild', '')) |
| 327 | |
| 328 | self.ebuild_path_no_version = os.path.join( |
| 329 | os.path.dirname(path), self.pkgname) |
| 330 | self.ebuild_path_no_revision = '%s-%s' % (self.ebuild_path_no_version, |
| 331 | self.version_no_rev) |
| 332 | self.current_revision = int(rev.replace('r', '')) |
| 333 | self.version = '%s-%s' % (self.version_no_rev, rev) |
| 334 | self.package = '%s/%s' % (self.category, self.pkgname) |
| 335 | self.ebuild_path = path |
| 336 | |
| 337 | self.is_workon = False |
| 338 | self.is_stable = False |
| 339 | |
| 340 | for line in fileinput.input(path): |
| 341 | if line.startswith('inherit ') and 'cros-workon' in line: |
| 342 | self.is_workon = True |
| 343 | elif (line.startswith('KEYWORDS=') and '~' not in line and |
| 344 | ('amd64' in line or 'x86' in line or 'arm' in line)): |
| 345 | self.is_stable = True |
| 346 | fileinput.close() |
| 347 | |
Chris Sosa | 62ad852 | 2011-03-08 17:46:17 -0800 | [diff] [blame] | 348 | def GetCommitId(self, srcroot): |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 349 | """Get the commit id for this ebuild.""" |
| 350 | # Grab and evaluate CROS_WORKON variables from this ebuild. |
| 351 | unstable_ebuild = '%s-9999.ebuild' % self.ebuild_path_no_version |
| 352 | cmd = ('export CROS_WORKON_LOCALNAME="%s" CROS_WORKON_PROJECT="%s"; ' |
| 353 | 'eval $(grep -E "^CROS_WORKON" %s) && ' |
| 354 | 'echo $CROS_WORKON_PROJECT ' |
| 355 | '$CROS_WORKON_LOCALNAME/$CROS_WORKON_SUBDIR' |
| 356 | % (self.pkgname, self.pkgname, unstable_ebuild)) |
| 357 | project, subdir = _SimpleRunCommand(cmd).split() |
| 358 | |
| 359 | # Calculate srcdir. |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 360 | if self.category == 'chromeos-base': |
| 361 | dir = 'platform' |
| 362 | else: |
| 363 | dir = 'third_party' |
Chris Sosa | c31bd2d | 2011-04-29 17:53:35 -0700 | [diff] [blame] | 364 | |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 365 | srcdir = os.path.join(srcroot, dir, subdir) |
| 366 | |
| 367 | if not os.path.isdir(srcdir): |
Chris Sosa | c13bba5 | 2011-05-24 15:14:09 -0700 | [diff] [blame] | 368 | cros_build_lib.Die('Cannot find commit id for %s' % self.ebuild_path) |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 369 | |
| 370 | # Verify that we're grabbing the commit id from the right project name. |
| 371 | # NOTE: chromeos-kernel has the wrong project name, so it fails this |
| 372 | # check. |
| 373 | # TODO(davidjames): Fix the project name in the chromeos-kernel ebuild. |
Chris Sosa | e0a7048 | 2011-05-02 19:18:34 -0700 | [diff] [blame] | 374 | cmd = ('cd %s && ( git config --get remote.cros.projectname || ' |
| 375 | 'git config --get remote.cros-internal.projectname )') % srcdir |
Chris Sosa | c31bd2d | 2011-04-29 17:53:35 -0700 | [diff] [blame] | 376 | actual_project = _SimpleRunCommand(cmd).rstrip() |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 377 | if project not in (actual_project, 'chromeos-kernel'): |
Chris Sosa | c13bba5 | 2011-05-24 15:14:09 -0700 | [diff] [blame] | 378 | cros_build_lib.Die('Project name mismatch for %s (%s != %s)' % ( |
| 379 | unstable_ebuild, project, actual_project)) |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 380 | |
| 381 | # Get commit id. |
| 382 | output = _SimpleRunCommand('cd %s && git rev-parse HEAD' % srcdir) |
| 383 | if not output: |
Chris Sosa | c13bba5 | 2011-05-24 15:14:09 -0700 | [diff] [blame] | 384 | cros_build_lib.Die('Missing commit id for %s' % self.ebuild_path) |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 385 | return output.rstrip() |
| 386 | |
| 387 | |
| 388 | class EBuildStableMarker(object): |
| 389 | """Class that revs the ebuild and commits locally or pushes the change.""" |
| 390 | |
| 391 | def __init__(self, ebuild): |
| 392 | assert ebuild |
| 393 | self._ebuild = ebuild |
| 394 | |
| 395 | @classmethod |
| 396 | def MarkAsStable(cls, unstable_ebuild_path, new_stable_ebuild_path, |
| 397 | commit_keyword, commit_value, redirect_file=None, |
| 398 | make_stable=True): |
| 399 | """Static function that creates a revved stable ebuild. |
| 400 | |
| 401 | This function assumes you have already figured out the name of the new |
| 402 | stable ebuild path and then creates that file from the given unstable |
| 403 | ebuild and marks it as stable. If the commit_value is set, it also |
| 404 | set the commit_keyword=commit_value pair in the ebuild. |
| 405 | |
| 406 | Args: |
| 407 | unstable_ebuild_path: The path to the unstable ebuild. |
| 408 | new_stable_ebuild_path: The path you want to use for the new stable |
| 409 | ebuild. |
| 410 | commit_keyword: Optional keyword to set in the ebuild to mark it as |
| 411 | stable. |
| 412 | commit_value: Value to set the above keyword to. |
| 413 | redirect_file: Optionally redirect output of new ebuild somewhere else. |
| 414 | make_stable: Actually make the ebuild stable. |
| 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 | if make_stable: |
| 424 | redirect_file.write(line.replace('~', '')) |
| 425 | else: |
| 426 | redirect_file.write(line) |
| 427 | elif line.startswith('EAPI'): |
| 428 | # Always add new commit_id after EAPI definition. |
| 429 | redirect_file.write(line) |
| 430 | if commit_keyword and commit_value: |
| 431 | redirect_file.write('%s="%s"\n' % (commit_keyword, commit_value)) |
| 432 | elif not line.startswith(commit_keyword): |
| 433 | # Skip old commit_keyword definition. |
| 434 | redirect_file.write(line) |
| 435 | fileinput.close() |
| 436 | |
| 437 | def RevWorkOnEBuild(self, commit_id, redirect_file=None): |
| 438 | """Revs a workon ebuild given the git commit hash. |
| 439 | |
| 440 | By default this class overwrites a new ebuild given the normal |
| 441 | ebuild rev'ing logic. However, a user can specify a redirect_file |
| 442 | to redirect the new stable ebuild to another file. |
| 443 | |
| 444 | Args: |
| 445 | commit_id: String corresponding to the commit hash of the developer |
| 446 | package to rev. |
| 447 | redirect_file: Optional file to write the new ebuild. By default |
| 448 | it is written using the standard rev'ing logic. This file must be |
| 449 | opened and closed by the caller. |
| 450 | |
| 451 | Raises: |
| 452 | OSError: Error occurred while creating a new ebuild. |
| 453 | IOError: Error occurred while writing to the new revved ebuild file. |
| 454 | Returns: |
| 455 | If the revved package is different than the old ebuild, return the full |
| 456 | revved package name, including the version number. Otherwise, return None. |
| 457 | """ |
| 458 | if self._ebuild.is_stable: |
| 459 | stable_version_no_rev = self._ebuild.version_no_rev |
| 460 | else: |
| 461 | # If given unstable ebuild, use 0.0.1 rather than 9999. |
| 462 | stable_version_no_rev = '0.0.1' |
| 463 | |
| 464 | new_version = '%s-r%d' % (stable_version_no_rev, |
| 465 | self._ebuild.current_revision + 1) |
| 466 | new_stable_ebuild_path = '%s-%s.ebuild' % ( |
| 467 | self._ebuild.ebuild_path_no_version, new_version) |
| 468 | |
| 469 | _Print('Creating new stable ebuild %s' % new_stable_ebuild_path) |
| 470 | unstable_ebuild_path = ('%s-9999.ebuild' % |
| 471 | self._ebuild.ebuild_path_no_version) |
| 472 | if not os.path.exists(unstable_ebuild_path): |
Chris Sosa | c13bba5 | 2011-05-24 15:14:09 -0700 | [diff] [blame] | 473 | cros_build_lib.Die('Missing unstable ebuild: %s' % unstable_ebuild_path) |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 474 | |
| 475 | self.MarkAsStable(unstable_ebuild_path, new_stable_ebuild_path, |
| 476 | 'CROS_WORKON_COMMIT', commit_id, redirect_file) |
| 477 | |
| 478 | old_ebuild_path = self._ebuild.ebuild_path |
Chris Sosa | 8be3913 | 2011-04-14 12:09:24 -0700 | [diff] [blame] | 479 | if filecmp.cmp(old_ebuild_path, new_stable_ebuild_path, shallow=False): |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 480 | os.unlink(new_stable_ebuild_path) |
| 481 | return None |
| 482 | else: |
| 483 | _Print('Adding new stable ebuild to git') |
| 484 | _SimpleRunCommand('git add %s' % new_stable_ebuild_path) |
| 485 | |
| 486 | if self._ebuild.is_stable: |
| 487 | _Print('Removing old ebuild from git') |
| 488 | _SimpleRunCommand('git rm %s' % old_ebuild_path) |
| 489 | |
| 490 | return '%s-%s' % (self._ebuild.package, new_version) |
| 491 | |
| 492 | @classmethod |
| 493 | def CommitChange(cls, message): |
| 494 | """Commits current changes in git locally with given commit message. |
| 495 | |
| 496 | Args: |
| 497 | message: the commit string to write when committing to git. |
| 498 | |
| 499 | Raises: |
| 500 | OSError: Error occurred while committing. |
| 501 | """ |
Chris Sosa | c13bba5 | 2011-05-24 15:14:09 -0700 | [diff] [blame] | 502 | cros_build_lib.Info('Committing changes with commit message: %s' % message) |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 503 | git_commit_cmd = 'git commit -am "%s"' % message |
| 504 | _SimpleRunCommand(git_commit_cmd) |
| 505 | |
| 506 | |
| 507 | def main(argv): |
Chris Sosa | 62ad852 | 2011-03-08 17:46:17 -0800 | [diff] [blame] | 508 | parser = optparse.OptionParser('cros_mark_as_stable OPTIONS packages') |
| 509 | parser.add_option('--all', action='store_true', |
| 510 | help='Mark all packages as stable.') |
| 511 | parser.add_option('-b', '--board', |
| 512 | help='Board for which the package belongs.') |
| 513 | parser.add_option('--drop_file', |
| 514 | help='File to list packages that were revved.') |
| 515 | parser.add_option('--dryrun', action='store_true', |
| 516 | help='Passes dry-run to git push if pushing a change.') |
| 517 | parser.add_option('-o', '--overlays', |
| 518 | help='Colon-separated list of overlays to modify.') |
| 519 | parser.add_option('-p', '--packages', |
| 520 | help='Colon separated list of packages to rev.') |
| 521 | parser.add_option('-r', '--srcroot', |
| 522 | default='%s/trunk/src' % os.environ['HOME'], |
| 523 | help='Path to root src directory.') |
Chris Sosa | 62ad852 | 2011-03-08 17:46:17 -0800 | [diff] [blame] | 524 | parser.add_option('--verbose', action='store_true', |
| 525 | help='Prints out debug info.') |
| 526 | (options, args) = parser.parse_args() |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 527 | |
Chris Sosa | 62ad852 | 2011-03-08 17:46:17 -0800 | [diff] [blame] | 528 | global VERBOSE |
| 529 | VERBOSE = options.verbose |
| 530 | |
| 531 | if len(args) != 1: |
Ryan Cui | 05a31ba | 2011-05-31 17:47:37 -0700 | [diff] [blame] | 532 | _PrintUsageAndDie('Must specify a valid command [commit, push]') |
Chris Sosa | 62ad852 | 2011-03-08 17:46:17 -0800 | [diff] [blame] | 533 | |
| 534 | command = args[0] |
| 535 | package_list = None |
| 536 | if options.packages: |
| 537 | package_list = options.packages.split(':') |
| 538 | |
| 539 | _CheckSaneArguments(package_list, command, options) |
| 540 | if options.overlays: |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 541 | overlays = {} |
Chris Sosa | 62ad852 | 2011-03-08 17:46:17 -0800 | [diff] [blame] | 542 | for path in options.overlays.split(':'): |
Ryan Cui | 05a31ba | 2011-05-31 17:47:37 -0700 | [diff] [blame] | 543 | if not os.path.isdir(path): |
Chris Sosa | c13bba5 | 2011-05-24 15:14:09 -0700 | [diff] [blame] | 544 | cros_build_lib.Die('Cannot find overlay: %s' % path) |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 545 | overlays[path] = [] |
| 546 | else: |
Chris Sosa | c13bba5 | 2011-05-24 15:14:09 -0700 | [diff] [blame] | 547 | cros_build_lib.Warning('Missing --overlays argument') |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 548 | overlays = { |
Chris Sosa | 62ad852 | 2011-03-08 17:46:17 -0800 | [diff] [blame] | 549 | '%s/private-overlays/chromeos-overlay' % options.srcroot: [], |
| 550 | '%s/third_party/chromiumos-overlay' % options.srcroot: [] |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 551 | } |
| 552 | |
| 553 | if command == 'commit': |
Chris Sosa | 95e85d9 | 2011-05-18 16:15:33 -0700 | [diff] [blame] | 554 | blacklist = _BlackListManager() |
| 555 | _BuildEBuildDictionary(overlays, options.all, package_list, blacklist) |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 556 | |
Ryan Cui | 556aad5 | 2011-05-31 15:42:28 -0700 | [diff] [blame] | 557 | tracking_branch = cros_build_lib.GetManifestDefaultBranch(options.srcroot) |
| 558 | tracking_branch = 'remotes/m/' + tracking_branch |
Ryan Cui | 4656a3c | 2011-05-24 12:30:30 -0700 | [diff] [blame] | 559 | |
David James | a8457b5 | 2011-05-28 00:03:20 -0700 | [diff] [blame] | 560 | # Contains the array of packages we actually revved. |
| 561 | revved_packages = [] |
| 562 | new_package_atoms = [] |
| 563 | |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 564 | for overlay, ebuilds in overlays.items(): |
| 565 | if not os.path.isdir(overlay): |
Chris Sosa | c13bba5 | 2011-05-24 15:14:09 -0700 | [diff] [blame] | 566 | cros_build_lib.Warning("Skipping %s" % overlay) |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 567 | continue |
| 568 | |
| 569 | # TODO(davidjames): Currently, all code that interacts with git depends on |
| 570 | # the cwd being set to the overlay directory. We should instead pass in |
| 571 | # this parameter so that we don't need to modify the cwd globally. |
| 572 | os.chdir(overlay) |
| 573 | |
Ryan Cui | 05a31ba | 2011-05-31 17:47:37 -0700 | [diff] [blame] | 574 | if command == 'push': |
| 575 | PushChange(constants.STABLE_EBUILD_BRANCH, tracking_branch, |
| 576 | options.dryrun) |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 577 | elif command == 'commit' and ebuilds: |
Ryan Cui | f0d57b4 | 2011-07-27 17:43:42 -0700 | [diff] [blame^] | 578 | existing_branch = cros_build_lib.GetCurrentBranch('.') |
Ryan Cui | 05a31ba | 2011-05-31 17:47:37 -0700 | [diff] [blame] | 579 | work_branch = GitBranch(constants.STABLE_EBUILD_BRANCH, tracking_branch) |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 580 | work_branch.CreateBranch() |
| 581 | if not work_branch.Exists(): |
Chris Sosa | c13bba5 | 2011-05-24 15:14:09 -0700 | [diff] [blame] | 582 | cros_build_lib.Die('Unable to create stabilizing branch in %s' % |
| 583 | overlay) |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 584 | |
Ryan Cui | f0d57b4 | 2011-07-27 17:43:42 -0700 | [diff] [blame^] | 585 | # In the case of uprevving overlays that have patches applied to them, |
| 586 | # include the patched changes in the stabilizing branch. |
| 587 | if existing_branch: |
| 588 | _SimpleRunCommand('git rebase %s' % existing_branch) |
| 589 | |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 590 | for ebuild in ebuilds: |
| 591 | try: |
| 592 | _Print('Working on %s' % ebuild.package) |
| 593 | worker = EBuildStableMarker(ebuild) |
Chris Sosa | 62ad852 | 2011-03-08 17:46:17 -0800 | [diff] [blame] | 594 | commit_id = ebuild.GetCommitId(options.srcroot) |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 595 | new_package = worker.RevWorkOnEBuild(commit_id) |
| 596 | if new_package: |
| 597 | message = _GIT_COMMIT_MESSAGE % (ebuild.package, commit_id) |
| 598 | worker.CommitChange(message) |
| 599 | revved_packages.append(ebuild.package) |
| 600 | new_package_atoms.append('=%s' % new_package) |
| 601 | except (OSError, IOError): |
Ryan Cui | 556aad5 | 2011-05-31 15:42:28 -0700 | [diff] [blame] | 602 | cros_build_lib.Warning('Cannot rev %s\n' % ebuild.package + |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 603 | 'Note you will have to go into %s ' |
| 604 | 'and reset the git repo yourself.' % overlay) |
| 605 | raise |
| 606 | |
David James | a8457b5 | 2011-05-28 00:03:20 -0700 | [diff] [blame] | 607 | if command == 'commit': |
| 608 | CleanStalePackages(options.board, new_package_atoms) |
| 609 | if options.drop_file: |
| 610 | fh = open(options.drop_file, 'w') |
| 611 | fh.write(' '.join(revved_packages)) |
| 612 | fh.close() |
Chris Sosa | dad0d32 | 2011-01-31 16:37:33 -0800 | [diff] [blame] | 613 | |
| 614 | |
| 615 | if __name__ == '__main__': |
| 616 | main(sys.argv) |