blob: 8d303868ee1a7f6187ab020a905e2b62151b82de [file] [log] [blame]
Chris Sosadad0d322011-01-31 16:37:33 -08001#!/usr/bin/python
2
Mike Frysinger110750a2012-03-26 14:19:20 -04003# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
Chris Sosadad0d322011-01-31 16:37:33 -08004# 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
Chris Sosa62ad8522011-03-08 17:46:17 -08009import optparse
Chris Sosadad0d322011-01-31 16:37:33 -080010import os
Chris Sosadad0d322011-01-31 16:37:33 -080011import sys
12
Mike Frysinger6cb624a2012-05-24 18:17:38 -040013from chromite.buildbot import constants
David James66009462012-03-25 10:08:38 -070014from chromite.buildbot import portage_utilities
Chris Sosac13bba52011-05-24 15:14:09 -070015from chromite.lib import cros_build_lib
David James97d95872012-11-16 15:09:56 -080016from chromite.lib import git
Mike Frysingerfddaeb52012-11-20 11:17:31 -050017from chromite.lib import osutils
David James6450a0a2012-12-04 07:59:53 -080018from chromite.lib import parallel
Chris Sosac13bba52011-05-24 15:14:09 -070019
Chris Sosadad0d322011-01-31 16:37:33 -080020
Chris Sosadad0d322011-01-31 16:37:33 -080021# Dictionary of valid commands with usage information.
22COMMAND_DICTIONARY = {
Chris Sosadad0d322011-01-31 16:37:33 -080023 'commit':
24 'Marks given ebuilds as stable locally',
25 'push':
26 'Pushes previous marking of ebuilds to remote repo',
27 }
28
Chris Sosadad0d322011-01-31 16:37:33 -080029
Chris Sosadad0d322011-01-31 16:37:33 -080030# ======================= Global Helper Functions ========================
31
32
David Jamescc09c9b2012-01-26 22:10:13 -080033def CleanStalePackages(boards, package_atoms):
Chris Sosabf153872011-04-28 14:21:09 -070034 """Cleans up stale package info from a previous build.
35 Args:
David Jamescc09c9b2012-01-26 22:10:13 -080036 boards: Boards to clean the packages from.
Mike Frysingerde5ab0e2013-03-21 20:48:36 -040037 package_atoms: A list of package atoms to unmerge.
Chris Sosabf153872011-04-28 14:21:09 -070038 """
39 if package_atoms:
Chris Sosac13bba52011-05-24 15:14:09 -070040 cros_build_lib.Info('Cleaning up stale packages %s.' % package_atoms)
Chris Sosadad0d322011-01-31 16:37:33 -080041
Mike Frysingerb7ab9b82012-04-04 16:22:43 -040042 # First unmerge all the packages for a board, then eclean it.
43 # We need these two steps to run in order (unmerge/eclean),
44 # but we can let all the boards run in parallel.
45 def _CleanStalePackages(board):
46 if board:
47 suffix = '-' + board
48 runcmd = cros_build_lib.RunCommand
49 else:
50 suffix = ''
51 runcmd = cros_build_lib.SudoRunCommand
Chris Sosadad0d322011-01-31 16:37:33 -080052
Mike Frysingerb7ab9b82012-04-04 16:22:43 -040053 if package_atoms:
Mike Frysingerde5ab0e2013-03-21 20:48:36 -040054 # If nothing was found to be unmerged, emerge will exit(1).
55 result = runcmd(['emerge' + suffix, '-q', '--unmerge'] + package_atoms,
56 extra_env={'CLEAN_DELAY': '0'}, error_code_ok=True)
57 if not result.returncode in (0, 1):
58 raise cros_build_lib.RunCommandError('unexpected error', result)
Mike Frysingerb7ab9b82012-04-04 16:22:43 -040059 runcmd(['eclean' + suffix, '-d', 'packages'],
60 redirect_stdout=True, redirect_stderr=True)
61
62 tasks = []
David Jamescc09c9b2012-01-26 22:10:13 -080063 for board in boards:
Mike Frysingerb7ab9b82012-04-04 16:22:43 -040064 tasks.append([board])
65 tasks.append([None])
66
David James6450a0a2012-12-04 07:59:53 -080067 parallel.RunTasksInProcessPool(_CleanStalePackages, tasks)
Chris Sosadad0d322011-01-31 16:37:33 -080068
69
Mike Frysinger2ebe3732012-05-08 17:04:12 -040070# TODO(build): This code needs to be gutted and rebased to cros_build_lib.
71def _DoWeHaveLocalCommits(stable_branch, tracking_branch, cwd):
Chris Sosadad0d322011-01-31 16:37:33 -080072 """Returns true if there are local commits."""
David James97d95872012-11-16 15:09:56 -080073 current_branch = git.GetCurrentBranch(cwd)
Brian Harring609dc4e2012-05-07 02:17:44 -070074
75 if current_branch != stable_branch:
Chris Sosadad0d322011-01-31 16:37:33 -080076 return False
David James97d95872012-11-16 15:09:56 -080077 output = git.RunGit(
Brian Harring609dc4e2012-05-07 02:17:44 -070078 cwd, ['rev-parse', 'HEAD', tracking_branch]).output.split()
Brian Harring5b86b5e2012-05-25 18:27:40 -070079 return output[0] != output[1]
Chris Sosadad0d322011-01-31 16:37:33 -080080
81
David James1b363582012-12-17 11:53:11 -080082def _CheckSaneArguments(command, options):
Chris Sosadad0d322011-01-31 16:37:33 -080083 """Checks to make sure the flags are sane. Dies if arguments are not sane."""
84 if not command in COMMAND_DICTIONARY.keys():
85 _PrintUsageAndDie('%s is not a valid command' % command)
Chris Sosa62ad8522011-03-08 17:46:17 -080086 if not options.packages and command == 'commit' and not options.all:
Chris Sosadad0d322011-01-31 16:37:33 -080087 _PrintUsageAndDie('Please specify at least one package')
Mike Frysinger8fd67dc2012-12-03 23:51:18 -050088 if options.boards:
89 cros_build_lib.AssertInsideChroot()
Chris Sosa62ad8522011-03-08 17:46:17 -080090 if not os.path.isdir(options.srcroot):
Chris Sosadad0d322011-01-31 16:37:33 -080091 _PrintUsageAndDie('srcroot is not a valid path')
Chris Sosa62ad8522011-03-08 17:46:17 -080092 options.srcroot = os.path.abspath(options.srcroot)
Chris Sosadad0d322011-01-31 16:37:33 -080093
94
95def _PrintUsageAndDie(error_message=''):
96 """Prints optional error_message the usage and returns an error exit code."""
97 command_usage = 'Commands: \n'
98 # Add keys and usage information from dictionary.
99 commands = sorted(COMMAND_DICTIONARY.keys())
100 for command in commands:
101 command_usage += ' %s: %s\n' % (command, COMMAND_DICTIONARY[command])
102 commands_str = '|'.join(commands)
Chris Sosac13bba52011-05-24 15:14:09 -0700103 cros_build_lib.Warning('Usage: %s FLAGS [%s]\n\n%s' % (
104 sys.argv[0], commands_str, command_usage))
Chris Sosadad0d322011-01-31 16:37:33 -0800105 if error_message:
Chris Sosac13bba52011-05-24 15:14:09 -0700106 cros_build_lib.Die(error_message)
Chris Sosadad0d322011-01-31 16:37:33 -0800107 else:
108 sys.exit(1)
109
110
Chris Sosadad0d322011-01-31 16:37:33 -0800111# ======================= End Global Helper Functions ========================
112
113
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400114def PushChange(stable_branch, tracking_branch, dryrun, cwd):
Chris Sosadad0d322011-01-31 16:37:33 -0800115 """Pushes commits in the stable_branch to the remote git repository.
116
David Jamesee2da622012-02-23 09:32:16 -0800117 Pushes local commits from calls to CommitChange to the remote git
David James66009462012-03-25 10:08:38 -0700118 repository specified by current working directory. If changes are
119 found to commit, they will be merged to the merge branch and pushed.
120 In that case, the local repository will be left on the merge branch.
Chris Sosadad0d322011-01-31 16:37:33 -0800121
122 Args:
123 stable_branch: The local branch with commits we want to push.
124 tracking_branch: The tracking branch of the local branch.
Chris Sosa62ad8522011-03-08 17:46:17 -0800125 dryrun: Use git push --dryrun to emulate a push.
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400126 cwd: The directory to run commands in.
Chris Sosadad0d322011-01-31 16:37:33 -0800127 Raises:
128 OSError: Error occurred while pushing.
129 """
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400130 if not _DoWeHaveLocalCommits(stable_branch, tracking_branch, cwd):
Brian Harring609dc4e2012-05-07 02:17:44 -0700131 cros_build_lib.Info('No work found to push in %s. Exiting', cwd)
Chris Sosadad0d322011-01-31 16:37:33 -0800132 return
133
David James66009462012-03-25 10:08:38 -0700134 # For the commit queue, our local branch may contain commits that were
135 # just tested and pushed during the CommitQueueCompletion stage. Sync
136 # and rebase our local branch on top of the remote commits.
David James97d95872012-11-16 15:09:56 -0800137 remote, push_branch = git.GetTrackingBranch(cwd, for_push=True)
138 git.SyncPushBranch(cwd, remote, push_branch)
David James66009462012-03-25 10:08:38 -0700139
140 # Check whether any local changes remain after the sync.
Brian Harring609dc4e2012-05-07 02:17:44 -0700141 if not _DoWeHaveLocalCommits(stable_branch, push_branch, cwd):
142 cros_build_lib.Info('All changes already pushed for %s. Exiting', cwd)
David James66009462012-03-25 10:08:38 -0700143 return
144
Mike Frysinger7dafd0e2012-05-08 15:47:16 -0400145 description = cros_build_lib.RunCommandCaptureOutput(
Brian Harring609dc4e2012-05-07 02:17:44 -0700146 ['git', 'log', '--format=format:%s%n%n%b', '%s..%s' % (
147 push_branch, stable_branch)], cwd=cwd).output
Chris Sosadad0d322011-01-31 16:37:33 -0800148 description = 'Marking set of ebuilds as stable\n\n%s' % description
Brian Harring609dc4e2012-05-07 02:17:44 -0700149 cros_build_lib.Info('For %s, using description %s', cwd, description)
David James97d95872012-11-16 15:09:56 -0800150 git.CreatePushBranch(constants.MERGE_BRANCH, cwd)
151 git.RunGit(cwd, ['merge', '--squash', stable_branch])
152 git.RunGit(cwd, ['commit', '-m', description])
153 git.RunGit(cwd, ['config', 'push.default', 'tracking'])
154 git.PushWithRetry(constants.MERGE_BRANCH, cwd, dryrun=dryrun)
Chris Sosadad0d322011-01-31 16:37:33 -0800155
156
157class GitBranch(object):
158 """Wrapper class for a git branch."""
159
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400160 def __init__(self, branch_name, tracking_branch, cwd):
Chris Sosadad0d322011-01-31 16:37:33 -0800161 """Sets up variables but does not create the branch."""
162 self.branch_name = branch_name
163 self.tracking_branch = tracking_branch
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400164 self.cwd = cwd
Chris Sosadad0d322011-01-31 16:37:33 -0800165
166 def CreateBranch(self):
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400167 self.Checkout()
Chris Sosadad0d322011-01-31 16:37:33 -0800168
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400169 def Checkout(self, branch=None):
Chris Sosadad0d322011-01-31 16:37:33 -0800170 """Function used to check out to another GitBranch."""
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400171 if not branch:
172 branch = self.branch_name
173 if branch == self.tracking_branch or self.Exists(branch):
174 git_cmd = ['git', 'checkout', '-f', branch]
Chris Sosadad0d322011-01-31 16:37:33 -0800175 else:
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400176 git_cmd = ['repo', 'start', branch, '.']
177 cros_build_lib.RunCommandCaptureOutput(git_cmd, print_cmd=False,
178 cwd=self.cwd)
Chris Sosadad0d322011-01-31 16:37:33 -0800179
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400180 def Exists(self, branch=None):
Chris Sosadad0d322011-01-31 16:37:33 -0800181 """Returns True if the branch exists."""
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400182 if not branch:
183 branch = self.branch_name
Mike Frysinger7dafd0e2012-05-08 15:47:16 -0400184 branches = cros_build_lib.RunCommandCaptureOutput(['git', 'branch'],
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400185 print_cmd=False,
186 cwd=self.cwd).output
187 return branch in branches.split()
Chris Sosadad0d322011-01-31 16:37:33 -0800188
189
David James1b363582012-12-17 11:53:11 -0800190def main(_argv):
Chris Sosa62ad8522011-03-08 17:46:17 -0800191 parser = optparse.OptionParser('cros_mark_as_stable OPTIONS packages')
192 parser.add_option('--all', action='store_true',
193 help='Mark all packages as stable.')
Mike Frysinger587bd562012-11-19 16:41:39 -0500194 parser.add_option('-b', '--boards', default='',
David Jamescc09c9b2012-01-26 22:10:13 -0800195 help='Colon-separated list of boards')
Chris Sosa62ad8522011-03-08 17:46:17 -0800196 parser.add_option('--drop_file',
197 help='File to list packages that were revved.')
198 parser.add_option('--dryrun', action='store_true',
199 help='Passes dry-run to git push if pushing a change.')
200 parser.add_option('-o', '--overlays',
201 help='Colon-separated list of overlays to modify.')
202 parser.add_option('-p', '--packages',
203 help='Colon separated list of packages to rev.')
204 parser.add_option('-r', '--srcroot',
Mike Frysingerfddaeb52012-11-20 11:17:31 -0500205 default=os.path.join(constants.SOURCE_ROOT, 'src'),
Chris Sosa62ad8522011-03-08 17:46:17 -0800206 help='Path to root src directory.')
Chris Sosa62ad8522011-03-08 17:46:17 -0800207 parser.add_option('--verbose', action='store_true',
208 help='Prints out debug info.')
209 (options, args) = parser.parse_args()
Chris Sosadad0d322011-01-31 16:37:33 -0800210
J. Richard Barnette2fa9fd62011-12-02 17:10:10 -0800211 portage_utilities.EBuild.VERBOSE = options.verbose
Chris Sosa62ad8522011-03-08 17:46:17 -0800212
213 if len(args) != 1:
Ryan Cui05a31ba2011-05-31 17:47:37 -0700214 _PrintUsageAndDie('Must specify a valid command [commit, push]')
Chris Sosa62ad8522011-03-08 17:46:17 -0800215
216 command = args[0]
217 package_list = None
218 if options.packages:
219 package_list = options.packages.split(':')
220
David James1b363582012-12-17 11:53:11 -0800221 _CheckSaneArguments(command, options)
Chris Sosa62ad8522011-03-08 17:46:17 -0800222 if options.overlays:
Chris Sosadad0d322011-01-31 16:37:33 -0800223 overlays = {}
Chris Sosa62ad8522011-03-08 17:46:17 -0800224 for path in options.overlays.split(':'):
Ryan Cui05a31ba2011-05-31 17:47:37 -0700225 if not os.path.isdir(path):
Chris Sosac13bba52011-05-24 15:14:09 -0700226 cros_build_lib.Die('Cannot find overlay: %s' % path)
Chris Sosadad0d322011-01-31 16:37:33 -0800227 overlays[path] = []
228 else:
Chris Sosac13bba52011-05-24 15:14:09 -0700229 cros_build_lib.Warning('Missing --overlays argument')
Chris Sosadad0d322011-01-31 16:37:33 -0800230 overlays = {
Chris Sosa62ad8522011-03-08 17:46:17 -0800231 '%s/private-overlays/chromeos-overlay' % options.srcroot: [],
232 '%s/third_party/chromiumos-overlay' % options.srcroot: []
Chris Sosadad0d322011-01-31 16:37:33 -0800233 }
234
235 if command == 'commit':
J. Richard Barnettef6697cf2011-11-18 12:42:08 -0800236 portage_utilities.BuildEBuildDictionary(
J. Richard Barnetted422f622011-11-17 09:39:46 -0800237 overlays, options.all, package_list)
Chris Sosadad0d322011-01-31 16:37:33 -0800238
David James97d95872012-11-16 15:09:56 -0800239 manifest = git.ManifestCheckout.Cached(options.srcroot)
Ryan Cui4656a3c2011-05-24 12:30:30 -0700240
David Jamesa8457b52011-05-28 00:03:20 -0700241 # Contains the array of packages we actually revved.
242 revved_packages = []
243 new_package_atoms = []
244
Mike Frysingerb9bd2f82012-05-08 14:55:23 -0400245 # Slight optimization hack: process the chromiumos overlay before any other
246 # cros-workon overlay first so we can do background cache generation in it.
247 # A perfect solution would walk all the overlays, figure out any dependencies
248 # between them (with layout.conf), and then process them in dependency order.
249 # However, this operation isn't slow enough to warrant that level of
250 # complexity, so we'll just special case the main overlay.
251 #
252 # Similarly, generate the cache in the portage-stable tree asap. We know
253 # we won't have any cros-workon packages in there, so generating the cache
254 # is the only thing it'll be doing. The chromiumos overlay instead might
255 # have revbumping to do before it can generate the cache.
Mike Frysinger110750a2012-03-26 14:19:20 -0400256 keys = overlays.keys()
Mike Frysingerb9bd2f82012-05-08 14:55:23 -0400257 for overlay in ('/third_party/chromiumos-overlay',
258 '/third_party/portage-stable'):
259 for k in keys:
260 if k.endswith(overlay):
261 keys.remove(k)
262 keys.insert(0, k)
263 break
Chris Sosadad0d322011-01-31 16:37:33 -0800264
Brian Harring9fdd23d2012-12-07 12:09:08 -0800265 with parallel.BackgroundTaskRunner(portage_utilities.RegenCache) as queue:
Mike Frysinger110750a2012-03-26 14:19:20 -0400266 for overlay in keys:
267 ebuilds = overlays[overlay]
268 if not os.path.isdir(overlay):
269 cros_build_lib.Warning("Skipping %s" % overlay)
270 continue
Chris Sosadad0d322011-01-31 16:37:33 -0800271
Brian Harringe08e4422012-05-30 12:40:50 -0700272 # Note we intentionally work from the non push tracking branch;
273 # everything built thus far has been against it (meaning, http mirrors),
274 # thus we should honor that. During the actual push, the code switches
275 # to the correct urls, and does an appropriate rebasing.
David James97d95872012-11-16 15:09:56 -0800276 tracking_branch = git.GetTrackingBranchViaManifest(
Brian Harringe08e4422012-05-30 12:40:50 -0700277 overlay, manifest=manifest)[1]
Brian Harring609dc4e2012-05-07 02:17:44 -0700278
Mike Frysinger110750a2012-03-26 14:19:20 -0400279 if command == 'push':
280 PushChange(constants.STABLE_EBUILD_BRANCH, tracking_branch,
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400281 options.dryrun, cwd=overlay)
Mike Frysinger303083d2012-07-16 14:57:24 -0400282 elif command == 'commit':
David James97d95872012-11-16 15:09:56 -0800283 existing_branch = git.GetCurrentBranch(overlay)
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400284 work_branch = GitBranch(constants.STABLE_EBUILD_BRANCH, tracking_branch,
285 cwd=overlay)
Mike Frysinger110750a2012-03-26 14:19:20 -0400286 work_branch.CreateBranch()
287 if not work_branch.Exists():
288 cros_build_lib.Die('Unable to create stabilizing branch in %s' %
289 overlay)
Ryan Cuif0d57b42011-07-27 17:43:42 -0700290
Mike Frysinger110750a2012-03-26 14:19:20 -0400291 # In the case of uprevving overlays that have patches applied to them,
292 # include the patched changes in the stabilizing branch.
293 if existing_branch:
Mike Frysinger7dafd0e2012-05-08 15:47:16 -0400294 cros_build_lib.RunCommand(['git', 'rebase', existing_branch],
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400295 print_cmd=False, cwd=overlay)
Mike Frysinger110750a2012-03-26 14:19:20 -0400296
297 for ebuild in ebuilds:
David James1b363582012-12-17 11:53:11 -0800298 if options.verbose:
299 cros_build_lib.Info('Working on %s', ebuild.package)
Mike Frysinger110750a2012-03-26 14:19:20 -0400300 try:
Mike Frysinger110750a2012-03-26 14:19:20 -0400301 new_package = ebuild.RevWorkOnEBuild(options.srcroot)
302 if new_package:
303 revved_packages.append(ebuild.package)
304 new_package_atoms.append('=%s' % new_package)
305 except (OSError, IOError):
306 cros_build_lib.Warning('Cannot rev %s\n' % ebuild.package +
307 'Note you will have to go into %s '
308 'and reset the git repo yourself.' % overlay)
309 raise
310
Mike Frysingerfddaeb52012-11-20 11:17:31 -0500311 if cros_build_lib.IsInsideChroot():
312 # Regenerate caches if need be. We do this all the time to
313 # catch when users make changes without updating cache files.
Brian Harring9fdd23d2012-12-07 12:09:08 -0800314 queue.put([overlay])
Chris Sosadad0d322011-01-31 16:37:33 -0800315
David Jamesa8457b52011-05-28 00:03:20 -0700316 if command == 'commit':
Mike Frysingerfddaeb52012-11-20 11:17:31 -0500317 if cros_build_lib.IsInsideChroot():
318 CleanStalePackages(options.boards.split(':'), new_package_atoms)
David Jamesa8457b52011-05-28 00:03:20 -0700319 if options.drop_file:
Mike Frysingerfddaeb52012-11-20 11:17:31 -0500320 osutils.WriteFile(options.drop_file, ' '.join(revved_packages))