blob: 361f6fb7cb039ad2b90031f42ed5d1673b65c667 [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
David James29e86d52013-04-19 09:41:29 -070021# Commit message for uprevving Portage packages.
22_GIT_COMMIT_MESSAGE = 'Marking 9999 ebuild for %s as stable.'
23
Chris Sosadad0d322011-01-31 16:37:33 -080024# Dictionary of valid commands with usage information.
25COMMAND_DICTIONARY = {
Chris Sosadad0d322011-01-31 16:37:33 -080026 'commit':
27 'Marks given ebuilds as stable locally',
28 'push':
29 'Pushes previous marking of ebuilds to remote repo',
30 }
31
Chris Sosadad0d322011-01-31 16:37:33 -080032
Chris Sosadad0d322011-01-31 16:37:33 -080033# ======================= Global Helper Functions ========================
34
35
David Jamescc09c9b2012-01-26 22:10:13 -080036def CleanStalePackages(boards, package_atoms):
Chris Sosabf153872011-04-28 14:21:09 -070037 """Cleans up stale package info from a previous build.
38 Args:
David Jamescc09c9b2012-01-26 22:10:13 -080039 boards: Boards to clean the packages from.
Mike Frysingerde5ab0e2013-03-21 20:48:36 -040040 package_atoms: A list of package atoms to unmerge.
Chris Sosabf153872011-04-28 14:21:09 -070041 """
42 if package_atoms:
Chris Sosac13bba52011-05-24 15:14:09 -070043 cros_build_lib.Info('Cleaning up stale packages %s.' % package_atoms)
Chris Sosadad0d322011-01-31 16:37:33 -080044
Mike Frysingerb7ab9b82012-04-04 16:22:43 -040045 # First unmerge all the packages for a board, then eclean it.
46 # We need these two steps to run in order (unmerge/eclean),
47 # but we can let all the boards run in parallel.
48 def _CleanStalePackages(board):
49 if board:
50 suffix = '-' + board
51 runcmd = cros_build_lib.RunCommand
52 else:
53 suffix = ''
54 runcmd = cros_build_lib.SudoRunCommand
Chris Sosadad0d322011-01-31 16:37:33 -080055
David James59a0a2b2013-03-22 14:04:44 -070056 emerge, eclean = 'emerge' + suffix, 'eclean' + suffix
57 if not osutils.FindMissingBinaries([emerge, eclean]):
Mike Frysingerde5ab0e2013-03-21 20:48:36 -040058 # If nothing was found to be unmerged, emerge will exit(1).
David James59a0a2b2013-03-22 14:04:44 -070059 result = runcmd([emerge, '-q', '--unmerge'] + package_atoms,
Mike Frysingerde5ab0e2013-03-21 20:48:36 -040060 extra_env={'CLEAN_DELAY': '0'}, error_code_ok=True)
61 if not result.returncode in (0, 1):
62 raise cros_build_lib.RunCommandError('unexpected error', result)
David James59a0a2b2013-03-22 14:04:44 -070063 runcmd([eclean, '-d', 'packages'],
64 redirect_stdout=True, redirect_stderr=True)
Mike Frysingerb7ab9b82012-04-04 16:22:43 -040065
66 tasks = []
David Jamescc09c9b2012-01-26 22:10:13 -080067 for board in boards:
Mike Frysingerb7ab9b82012-04-04 16:22:43 -040068 tasks.append([board])
69 tasks.append([None])
70
David James6450a0a2012-12-04 07:59:53 -080071 parallel.RunTasksInProcessPool(_CleanStalePackages, tasks)
Chris Sosadad0d322011-01-31 16:37:33 -080072
73
Mike Frysinger2ebe3732012-05-08 17:04:12 -040074# TODO(build): This code needs to be gutted and rebased to cros_build_lib.
75def _DoWeHaveLocalCommits(stable_branch, tracking_branch, cwd):
Chris Sosadad0d322011-01-31 16:37:33 -080076 """Returns true if there are local commits."""
David James97d95872012-11-16 15:09:56 -080077 current_branch = git.GetCurrentBranch(cwd)
Brian Harring609dc4e2012-05-07 02:17:44 -070078
79 if current_branch != stable_branch:
Chris Sosadad0d322011-01-31 16:37:33 -080080 return False
David James97d95872012-11-16 15:09:56 -080081 output = git.RunGit(
Brian Harring609dc4e2012-05-07 02:17:44 -070082 cwd, ['rev-parse', 'HEAD', tracking_branch]).output.split()
Brian Harring5b86b5e2012-05-25 18:27:40 -070083 return output[0] != output[1]
Chris Sosadad0d322011-01-31 16:37:33 -080084
85
David James1b363582012-12-17 11:53:11 -080086def _CheckSaneArguments(command, options):
Chris Sosadad0d322011-01-31 16:37:33 -080087 """Checks to make sure the flags are sane. Dies if arguments are not sane."""
88 if not command in COMMAND_DICTIONARY.keys():
89 _PrintUsageAndDie('%s is not a valid command' % command)
Chris Sosa62ad8522011-03-08 17:46:17 -080090 if not options.packages and command == 'commit' and not options.all:
Chris Sosadad0d322011-01-31 16:37:33 -080091 _PrintUsageAndDie('Please specify at least one package')
Mike Frysinger8fd67dc2012-12-03 23:51:18 -050092 if options.boards:
93 cros_build_lib.AssertInsideChroot()
Chris Sosa62ad8522011-03-08 17:46:17 -080094 if not os.path.isdir(options.srcroot):
Chris Sosadad0d322011-01-31 16:37:33 -080095 _PrintUsageAndDie('srcroot is not a valid path')
Chris Sosa62ad8522011-03-08 17:46:17 -080096 options.srcroot = os.path.abspath(options.srcroot)
Chris Sosadad0d322011-01-31 16:37:33 -080097
98
99def _PrintUsageAndDie(error_message=''):
100 """Prints optional error_message the usage and returns an error exit code."""
101 command_usage = 'Commands: \n'
102 # Add keys and usage information from dictionary.
103 commands = sorted(COMMAND_DICTIONARY.keys())
104 for command in commands:
105 command_usage += ' %s: %s\n' % (command, COMMAND_DICTIONARY[command])
106 commands_str = '|'.join(commands)
Chris Sosac13bba52011-05-24 15:14:09 -0700107 cros_build_lib.Warning('Usage: %s FLAGS [%s]\n\n%s' % (
108 sys.argv[0], commands_str, command_usage))
Chris Sosadad0d322011-01-31 16:37:33 -0800109 if error_message:
Chris Sosac13bba52011-05-24 15:14:09 -0700110 cros_build_lib.Die(error_message)
Chris Sosadad0d322011-01-31 16:37:33 -0800111 else:
112 sys.exit(1)
113
114
Chris Sosadad0d322011-01-31 16:37:33 -0800115# ======================= End Global Helper Functions ========================
116
117
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400118def PushChange(stable_branch, tracking_branch, dryrun, cwd):
Chris Sosadad0d322011-01-31 16:37:33 -0800119 """Pushes commits in the stable_branch to the remote git repository.
120
David Jamesee2da622012-02-23 09:32:16 -0800121 Pushes local commits from calls to CommitChange to the remote git
David James66009462012-03-25 10:08:38 -0700122 repository specified by current working directory. If changes are
123 found to commit, they will be merged to the merge branch and pushed.
124 In that case, the local repository will be left on the merge branch.
Chris Sosadad0d322011-01-31 16:37:33 -0800125
126 Args:
127 stable_branch: The local branch with commits we want to push.
128 tracking_branch: The tracking branch of the local branch.
Chris Sosa62ad8522011-03-08 17:46:17 -0800129 dryrun: Use git push --dryrun to emulate a push.
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400130 cwd: The directory to run commands in.
Chris Sosadad0d322011-01-31 16:37:33 -0800131 Raises:
132 OSError: Error occurred while pushing.
133 """
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400134 if not _DoWeHaveLocalCommits(stable_branch, tracking_branch, cwd):
Brian Harring609dc4e2012-05-07 02:17:44 -0700135 cros_build_lib.Info('No work found to push in %s. Exiting', cwd)
Chris Sosadad0d322011-01-31 16:37:33 -0800136 return
137
David James66009462012-03-25 10:08:38 -0700138 # For the commit queue, our local branch may contain commits that were
139 # just tested and pushed during the CommitQueueCompletion stage. Sync
140 # and rebase our local branch on top of the remote commits.
David James97d95872012-11-16 15:09:56 -0800141 remote, push_branch = git.GetTrackingBranch(cwd, for_push=True)
142 git.SyncPushBranch(cwd, remote, push_branch)
David James66009462012-03-25 10:08:38 -0700143
144 # Check whether any local changes remain after the sync.
Brian Harring609dc4e2012-05-07 02:17:44 -0700145 if not _DoWeHaveLocalCommits(stable_branch, push_branch, cwd):
146 cros_build_lib.Info('All changes already pushed for %s. Exiting', cwd)
David James66009462012-03-25 10:08:38 -0700147 return
148
Mike Frysinger7dafd0e2012-05-08 15:47:16 -0400149 description = cros_build_lib.RunCommandCaptureOutput(
Brian Harring609dc4e2012-05-07 02:17:44 -0700150 ['git', 'log', '--format=format:%s%n%n%b', '%s..%s' % (
151 push_branch, stable_branch)], cwd=cwd).output
Chris Sosadad0d322011-01-31 16:37:33 -0800152 description = 'Marking set of ebuilds as stable\n\n%s' % description
Brian Harring609dc4e2012-05-07 02:17:44 -0700153 cros_build_lib.Info('For %s, using description %s', cwd, description)
David James97d95872012-11-16 15:09:56 -0800154 git.CreatePushBranch(constants.MERGE_BRANCH, cwd)
155 git.RunGit(cwd, ['merge', '--squash', stable_branch])
156 git.RunGit(cwd, ['commit', '-m', description])
157 git.RunGit(cwd, ['config', 'push.default', 'tracking'])
158 git.PushWithRetry(constants.MERGE_BRANCH, cwd, dryrun=dryrun)
Chris Sosadad0d322011-01-31 16:37:33 -0800159
160
161class GitBranch(object):
162 """Wrapper class for a git branch."""
163
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400164 def __init__(self, branch_name, tracking_branch, cwd):
Chris Sosadad0d322011-01-31 16:37:33 -0800165 """Sets up variables but does not create the branch."""
166 self.branch_name = branch_name
167 self.tracking_branch = tracking_branch
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400168 self.cwd = cwd
Chris Sosadad0d322011-01-31 16:37:33 -0800169
170 def CreateBranch(self):
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400171 self.Checkout()
Chris Sosadad0d322011-01-31 16:37:33 -0800172
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400173 def Checkout(self, branch=None):
Chris Sosadad0d322011-01-31 16:37:33 -0800174 """Function used to check out to another GitBranch."""
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400175 if not branch:
176 branch = self.branch_name
177 if branch == self.tracking_branch or self.Exists(branch):
178 git_cmd = ['git', 'checkout', '-f', branch]
Chris Sosadad0d322011-01-31 16:37:33 -0800179 else:
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400180 git_cmd = ['repo', 'start', branch, '.']
181 cros_build_lib.RunCommandCaptureOutput(git_cmd, print_cmd=False,
182 cwd=self.cwd)
Chris Sosadad0d322011-01-31 16:37:33 -0800183
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400184 def Exists(self, branch=None):
Chris Sosadad0d322011-01-31 16:37:33 -0800185 """Returns True if the branch exists."""
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400186 if not branch:
187 branch = self.branch_name
Mike Frysinger7dafd0e2012-05-08 15:47:16 -0400188 branches = cros_build_lib.RunCommandCaptureOutput(['git', 'branch'],
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400189 print_cmd=False,
190 cwd=self.cwd).output
191 return branch in branches.split()
Chris Sosadad0d322011-01-31 16:37:33 -0800192
193
David James1b363582012-12-17 11:53:11 -0800194def main(_argv):
Chris Sosa62ad8522011-03-08 17:46:17 -0800195 parser = optparse.OptionParser('cros_mark_as_stable OPTIONS packages')
196 parser.add_option('--all', action='store_true',
197 help='Mark all packages as stable.')
Mike Frysinger587bd562012-11-19 16:41:39 -0500198 parser.add_option('-b', '--boards', default='',
David Jamescc09c9b2012-01-26 22:10:13 -0800199 help='Colon-separated list of boards')
Chris Sosa62ad8522011-03-08 17:46:17 -0800200 parser.add_option('--drop_file',
201 help='File to list packages that were revved.')
202 parser.add_option('--dryrun', action='store_true',
203 help='Passes dry-run to git push if pushing a change.')
204 parser.add_option('-o', '--overlays',
205 help='Colon-separated list of overlays to modify.')
206 parser.add_option('-p', '--packages',
207 help='Colon separated list of packages to rev.')
208 parser.add_option('-r', '--srcroot',
Mike Frysingerfddaeb52012-11-20 11:17:31 -0500209 default=os.path.join(constants.SOURCE_ROOT, 'src'),
Chris Sosa62ad8522011-03-08 17:46:17 -0800210 help='Path to root src directory.')
Chris Sosa62ad8522011-03-08 17:46:17 -0800211 parser.add_option('--verbose', action='store_true',
212 help='Prints out debug info.')
213 (options, args) = parser.parse_args()
Chris Sosadad0d322011-01-31 16:37:33 -0800214
J. Richard Barnette2fa9fd62011-12-02 17:10:10 -0800215 portage_utilities.EBuild.VERBOSE = options.verbose
Chris Sosa62ad8522011-03-08 17:46:17 -0800216
217 if len(args) != 1:
Ryan Cui05a31ba2011-05-31 17:47:37 -0700218 _PrintUsageAndDie('Must specify a valid command [commit, push]')
Chris Sosa62ad8522011-03-08 17:46:17 -0800219
220 command = args[0]
221 package_list = None
222 if options.packages:
223 package_list = options.packages.split(':')
224
David James1b363582012-12-17 11:53:11 -0800225 _CheckSaneArguments(command, options)
Chris Sosa62ad8522011-03-08 17:46:17 -0800226 if options.overlays:
Chris Sosadad0d322011-01-31 16:37:33 -0800227 overlays = {}
Chris Sosa62ad8522011-03-08 17:46:17 -0800228 for path in options.overlays.split(':'):
Ryan Cui05a31ba2011-05-31 17:47:37 -0700229 if not os.path.isdir(path):
Chris Sosac13bba52011-05-24 15:14:09 -0700230 cros_build_lib.Die('Cannot find overlay: %s' % path)
Chris Sosadad0d322011-01-31 16:37:33 -0800231 overlays[path] = []
232 else:
Chris Sosac13bba52011-05-24 15:14:09 -0700233 cros_build_lib.Warning('Missing --overlays argument')
Chris Sosadad0d322011-01-31 16:37:33 -0800234 overlays = {
Chris Sosa62ad8522011-03-08 17:46:17 -0800235 '%s/private-overlays/chromeos-overlay' % options.srcroot: [],
236 '%s/third_party/chromiumos-overlay' % options.srcroot: []
Chris Sosadad0d322011-01-31 16:37:33 -0800237 }
238
David James97d95872012-11-16 15:09:56 -0800239 manifest = git.ManifestCheckout.Cached(options.srcroot)
Ryan Cui4656a3c2011-05-24 12:30:30 -0700240
David James84e953c2013-04-23 18:44:06 -0700241 if command == 'commit':
242 portage_utilities.BuildEBuildDictionary(overlays, options.all, package_list)
243
David Jamesa8457b52011-05-28 00:03:20 -0700244 # Contains the array of packages we actually revved.
245 revved_packages = []
246 new_package_atoms = []
247
Mike Frysingerb9bd2f82012-05-08 14:55:23 -0400248 # Slight optimization hack: process the chromiumos overlay before any other
249 # cros-workon overlay first so we can do background cache generation in it.
250 # A perfect solution would walk all the overlays, figure out any dependencies
251 # between them (with layout.conf), and then process them in dependency order.
252 # However, this operation isn't slow enough to warrant that level of
253 # complexity, so we'll just special case the main overlay.
254 #
255 # Similarly, generate the cache in the portage-stable tree asap. We know
256 # we won't have any cros-workon packages in there, so generating the cache
257 # is the only thing it'll be doing. The chromiumos overlay instead might
258 # have revbumping to do before it can generate the cache.
Mike Frysinger110750a2012-03-26 14:19:20 -0400259 keys = overlays.keys()
Mike Frysingerb9bd2f82012-05-08 14:55:23 -0400260 for overlay in ('/third_party/chromiumos-overlay',
261 '/third_party/portage-stable'):
262 for k in keys:
263 if k.endswith(overlay):
264 keys.remove(k)
265 keys.insert(0, k)
266 break
Chris Sosadad0d322011-01-31 16:37:33 -0800267
Brian Harring9fdd23d2012-12-07 12:09:08 -0800268 with parallel.BackgroundTaskRunner(portage_utilities.RegenCache) as queue:
Mike Frysinger110750a2012-03-26 14:19:20 -0400269 for overlay in keys:
270 ebuilds = overlays[overlay]
271 if not os.path.isdir(overlay):
272 cros_build_lib.Warning("Skipping %s" % overlay)
273 continue
Chris Sosadad0d322011-01-31 16:37:33 -0800274
Brian Harringe08e4422012-05-30 12:40:50 -0700275 # Note we intentionally work from the non push tracking branch;
276 # everything built thus far has been against it (meaning, http mirrors),
277 # thus we should honor that. During the actual push, the code switches
278 # to the correct urls, and does an appropriate rebasing.
David James97d95872012-11-16 15:09:56 -0800279 tracking_branch = git.GetTrackingBranchViaManifest(
Brian Harringe08e4422012-05-30 12:40:50 -0700280 overlay, manifest=manifest)[1]
Brian Harring609dc4e2012-05-07 02:17:44 -0700281
Mike Frysinger110750a2012-03-26 14:19:20 -0400282 if command == 'push':
283 PushChange(constants.STABLE_EBUILD_BRANCH, tracking_branch,
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400284 options.dryrun, cwd=overlay)
Mike Frysinger303083d2012-07-16 14:57:24 -0400285 elif command == 'commit':
David James97d95872012-11-16 15:09:56 -0800286 existing_branch = git.GetCurrentBranch(overlay)
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400287 work_branch = GitBranch(constants.STABLE_EBUILD_BRANCH, tracking_branch,
288 cwd=overlay)
Mike Frysinger110750a2012-03-26 14:19:20 -0400289 work_branch.CreateBranch()
290 if not work_branch.Exists():
291 cros_build_lib.Die('Unable to create stabilizing branch in %s' %
292 overlay)
Ryan Cuif0d57b42011-07-27 17:43:42 -0700293
Mike Frysinger110750a2012-03-26 14:19:20 -0400294 # In the case of uprevving overlays that have patches applied to them,
295 # include the patched changes in the stabilizing branch.
296 if existing_branch:
Mike Frysinger7dafd0e2012-05-08 15:47:16 -0400297 cros_build_lib.RunCommand(['git', 'rebase', existing_branch],
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400298 print_cmd=False, cwd=overlay)
Mike Frysinger110750a2012-03-26 14:19:20 -0400299
David James29e86d52013-04-19 09:41:29 -0700300 messages = []
Mike Frysinger110750a2012-03-26 14:19:20 -0400301 for ebuild in ebuilds:
David James1b363582012-12-17 11:53:11 -0800302 if options.verbose:
303 cros_build_lib.Info('Working on %s', ebuild.package)
Mike Frysinger110750a2012-03-26 14:19:20 -0400304 try:
David James84e953c2013-04-23 18:44:06 -0700305 new_package = ebuild.RevWorkOnEBuild(options.srcroot, manifest)
Mike Frysinger110750a2012-03-26 14:19:20 -0400306 if new_package:
307 revved_packages.append(ebuild.package)
308 new_package_atoms.append('=%s' % new_package)
David James29e86d52013-04-19 09:41:29 -0700309 messages.append(_GIT_COMMIT_MESSAGE % ebuild.package)
Mike Frysinger110750a2012-03-26 14:19:20 -0400310 except (OSError, IOError):
311 cros_build_lib.Warning('Cannot rev %s\n' % ebuild.package +
312 'Note you will have to go into %s '
313 'and reset the git repo yourself.' % overlay)
314 raise
315
David James29e86d52013-04-19 09:41:29 -0700316 if messages:
317 portage_utilities.EBuild.CommitChange('\n\n'.join(messages), overlay)
318
Mike Frysingerfddaeb52012-11-20 11:17:31 -0500319 if cros_build_lib.IsInsideChroot():
320 # Regenerate caches if need be. We do this all the time to
321 # catch when users make changes without updating cache files.
Brian Harring9fdd23d2012-12-07 12:09:08 -0800322 queue.put([overlay])
Chris Sosadad0d322011-01-31 16:37:33 -0800323
David Jamesa8457b52011-05-28 00:03:20 -0700324 if command == 'commit':
Mike Frysingerfddaeb52012-11-20 11:17:31 -0500325 if cros_build_lib.IsInsideChroot():
326 CleanStalePackages(options.boards.split(':'), new_package_atoms)
David Jamesa8457b52011-05-28 00:03:20 -0700327 if options.drop_file:
Mike Frysingerfddaeb52012-11-20 11:17:31 -0500328 osutils.WriteFile(options.drop_file, ' '.join(revved_packages))