blob: 4d72bbda2c6e5034ad65e39a09cdaf350bb54107 [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
David James15ed1302013-04-25 09:21:19 -070020
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 = {
Mike Frysinger5cd8c742013-10-11 14:43:01 -040026 'commit': 'Marks given ebuilds as stable locally',
27 'push': 'Pushes previous marking of ebuilds to remote repo',
28}
Chris Sosadad0d322011-01-31 16:37:33 -080029
Chris Sosadad0d322011-01-31 16:37:33 -080030
Chris Sosadad0d322011-01-31 16:37:33 -080031# ======================= Global Helper Functions ========================
32
33
David Jamescc09c9b2012-01-26 22:10:13 -080034def CleanStalePackages(boards, package_atoms):
Chris Sosabf153872011-04-28 14:21:09 -070035 """Cleans up stale package info from a previous build.
Mike Frysinger5cd8c742013-10-11 14:43:01 -040036
Chris Sosabf153872011-04-28 14:21:09 -070037 Args:
David Jamescc09c9b2012-01-26 22:10:13 -080038 boards: Boards to clean the packages from.
Mike Frysingerde5ab0e2013-03-21 20:48:36 -040039 package_atoms: A list of package atoms to unmerge.
Chris Sosabf153872011-04-28 14:21:09 -070040 """
David James15ed1302013-04-25 09:21:19 -070041 if package_atoms:
42 cros_build_lib.Info('Cleaning up stale packages %s.' % package_atoms)
43
Mike Frysingerb7ab9b82012-04-04 16:22:43 -040044 # First unmerge all the packages for a board, then eclean it.
45 # We need these two steps to run in order (unmerge/eclean),
46 # but we can let all the boards run in parallel.
47 def _CleanStalePackages(board):
48 if board:
49 suffix = '-' + board
50 runcmd = cros_build_lib.RunCommand
51 else:
52 suffix = ''
53 runcmd = cros_build_lib.SudoRunCommand
Chris Sosadad0d322011-01-31 16:37:33 -080054
David James59a0a2b2013-03-22 14:04:44 -070055 emerge, eclean = 'emerge' + suffix, 'eclean' + suffix
56 if not osutils.FindMissingBinaries([emerge, eclean]):
Mike Frysingerde5ab0e2013-03-21 20:48:36 -040057 # If nothing was found to be unmerged, emerge will exit(1).
David James59a0a2b2013-03-22 14:04:44 -070058 result = runcmd([emerge, '-q', '--unmerge'] + package_atoms,
Mike Frysingerde5ab0e2013-03-21 20:48:36 -040059 extra_env={'CLEAN_DELAY': '0'}, error_code_ok=True)
60 if not result.returncode in (0, 1):
61 raise cros_build_lib.RunCommandError('unexpected error', result)
David James59a0a2b2013-03-22 14:04:44 -070062 runcmd([eclean, '-d', 'packages'],
63 redirect_stdout=True, redirect_stderr=True)
Mike Frysingerb7ab9b82012-04-04 16:22:43 -040064
65 tasks = []
David Jamescc09c9b2012-01-26 22:10:13 -080066 for board in boards:
Mike Frysingerb7ab9b82012-04-04 16:22:43 -040067 tasks.append([board])
68 tasks.append([None])
69
David James6450a0a2012-12-04 07:59:53 -080070 parallel.RunTasksInProcessPool(_CleanStalePackages, tasks)
Chris Sosadad0d322011-01-31 16:37:33 -080071
72
Mike Frysinger2ebe3732012-05-08 17:04:12 -040073# TODO(build): This code needs to be gutted and rebased to cros_build_lib.
74def _DoWeHaveLocalCommits(stable_branch, tracking_branch, cwd):
Chris Sosadad0d322011-01-31 16:37:33 -080075 """Returns true if there are local commits."""
David James97d95872012-11-16 15:09:56 -080076 current_branch = git.GetCurrentBranch(cwd)
Brian Harring609dc4e2012-05-07 02:17:44 -070077
78 if current_branch != stable_branch:
Chris Sosadad0d322011-01-31 16:37:33 -080079 return False
David James97d95872012-11-16 15:09:56 -080080 output = git.RunGit(
Brian Harring609dc4e2012-05-07 02:17:44 -070081 cwd, ['rev-parse', 'HEAD', tracking_branch]).output.split()
Brian Harring5b86b5e2012-05-25 18:27:40 -070082 return output[0] != output[1]
Chris Sosadad0d322011-01-31 16:37:33 -080083
84
David James1b363582012-12-17 11:53:11 -080085def _CheckSaneArguments(command, options):
Chris Sosadad0d322011-01-31 16:37:33 -080086 """Checks to make sure the flags are sane. Dies if arguments are not sane."""
87 if not command in COMMAND_DICTIONARY.keys():
88 _PrintUsageAndDie('%s is not a valid command' % command)
Chris Sosa62ad8522011-03-08 17:46:17 -080089 if not options.packages and command == 'commit' and not options.all:
Chris Sosadad0d322011-01-31 16:37:33 -080090 _PrintUsageAndDie('Please specify at least one package')
Mike Frysinger8fd67dc2012-12-03 23:51:18 -050091 if options.boards:
92 cros_build_lib.AssertInsideChroot()
Chris Sosa62ad8522011-03-08 17:46:17 -080093 if not os.path.isdir(options.srcroot):
Chris Sosadad0d322011-01-31 16:37:33 -080094 _PrintUsageAndDie('srcroot is not a valid path')
Chris Sosa62ad8522011-03-08 17:46:17 -080095 options.srcroot = os.path.abspath(options.srcroot)
Chris Sosadad0d322011-01-31 16:37:33 -080096
97
98def _PrintUsageAndDie(error_message=''):
99 """Prints optional error_message the usage and returns an error exit code."""
100 command_usage = 'Commands: \n'
101 # Add keys and usage information from dictionary.
102 commands = sorted(COMMAND_DICTIONARY.keys())
103 for command in commands:
104 command_usage += ' %s: %s\n' % (command, COMMAND_DICTIONARY[command])
105 commands_str = '|'.join(commands)
Chris Sosac13bba52011-05-24 15:14:09 -0700106 cros_build_lib.Warning('Usage: %s FLAGS [%s]\n\n%s' % (
107 sys.argv[0], commands_str, command_usage))
Chris Sosadad0d322011-01-31 16:37:33 -0800108 if error_message:
Chris Sosac13bba52011-05-24 15:14:09 -0700109 cros_build_lib.Die(error_message)
Chris Sosadad0d322011-01-31 16:37:33 -0800110 else:
111 sys.exit(1)
112
113
Chris Sosadad0d322011-01-31 16:37:33 -0800114# ======================= End Global Helper Functions ========================
115
116
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400117def PushChange(stable_branch, tracking_branch, dryrun, cwd):
Chris Sosadad0d322011-01-31 16:37:33 -0800118 """Pushes commits in the stable_branch to the remote git repository.
119
David Jamesee2da622012-02-23 09:32:16 -0800120 Pushes local commits from calls to CommitChange to the remote git
David James66009462012-03-25 10:08:38 -0700121 repository specified by current working directory. If changes are
122 found to commit, they will be merged to the merge branch and pushed.
123 In that case, the local repository will be left on the merge branch.
Chris Sosadad0d322011-01-31 16:37:33 -0800124
125 Args:
126 stable_branch: The local branch with commits we want to push.
127 tracking_branch: The tracking branch of the local branch.
Chris Sosa62ad8522011-03-08 17:46:17 -0800128 dryrun: Use git push --dryrun to emulate a push.
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400129 cwd: The directory to run commands in.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500130
Chris Sosadad0d322011-01-31 16:37:33 -0800131 Raises:
Mike Frysinger5cd8c742013-10-11 14:43:01 -0400132 OSError: Error occurred while pushing.
Chris Sosadad0d322011-01-31 16:37:33 -0800133 """
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
Matt Tennantcb522052013-11-25 14:23:43 -0800149 # Add a failsafe check here. Only CLs from the 'chrome-bot' user should
150 # be involved here. If any other CLs are found then complain.
151 # In dryruns extra CLs are normal, though, and can be ignored.
152 bad_cl_cmd = ['log', '--format=short', '--perl-regexp',
153 '--author', '^(?!chrome-bot)', '%s..%s' % (
154 push_branch, stable_branch)]
155 bad_cls = git.RunGit(cwd, bad_cl_cmd).output
156 if bad_cls.strip() and not dryrun:
157 cros_build_lib.Error('The Uprev stage found changes from users other'
158 ' than chrome-bot:\n\n%s', bad_cls)
159 raise AssertionError('Unexpected CLs found during uprev stage.')
160
David James67d73252013-09-19 17:33:12 -0700161 description = git.RunGit(cwd,
162 ['log', '--format=format:%s%n%n%b', '%s..%s' % (
163 push_branch, stable_branch)]).output
Chris Sosadad0d322011-01-31 16:37:33 -0800164 description = 'Marking set of ebuilds as stable\n\n%s' % description
Brian Harring609dc4e2012-05-07 02:17:44 -0700165 cros_build_lib.Info('For %s, using description %s', cwd, description)
David James97d95872012-11-16 15:09:56 -0800166 git.CreatePushBranch(constants.MERGE_BRANCH, cwd)
167 git.RunGit(cwd, ['merge', '--squash', stable_branch])
168 git.RunGit(cwd, ['commit', '-m', description])
169 git.RunGit(cwd, ['config', 'push.default', 'tracking'])
170 git.PushWithRetry(constants.MERGE_BRANCH, cwd, dryrun=dryrun)
Chris Sosadad0d322011-01-31 16:37:33 -0800171
172
173class GitBranch(object):
174 """Wrapper class for a git branch."""
175
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400176 def __init__(self, branch_name, tracking_branch, cwd):
David Jamesc7c4ff52013-09-18 17:57:13 -0700177 """Sets up variables but does not create the branch.
178
Mike Frysinger5cd8c742013-10-11 14:43:01 -0400179 Args:
David Jamesc7c4ff52013-09-18 17:57:13 -0700180 branch_name: The name of the branch.
181 tracking_branch: The associated tracking branch.
182 cwd: The git repository to work in.
183 """
Chris Sosadad0d322011-01-31 16:37:33 -0800184 self.branch_name = branch_name
185 self.tracking_branch = tracking_branch
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400186 self.cwd = cwd
Chris Sosadad0d322011-01-31 16:37:33 -0800187
188 def CreateBranch(self):
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400189 self.Checkout()
Chris Sosadad0d322011-01-31 16:37:33 -0800190
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400191 def Checkout(self, branch=None):
Chris Sosadad0d322011-01-31 16:37:33 -0800192 """Function used to check out to another GitBranch."""
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400193 if not branch:
194 branch = self.branch_name
195 if branch == self.tracking_branch or self.Exists(branch):
196 git_cmd = ['git', 'checkout', '-f', branch]
Chris Sosadad0d322011-01-31 16:37:33 -0800197 else:
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400198 git_cmd = ['repo', 'start', branch, '.']
199 cros_build_lib.RunCommandCaptureOutput(git_cmd, print_cmd=False,
200 cwd=self.cwd)
Chris Sosadad0d322011-01-31 16:37:33 -0800201
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400202 def Exists(self, branch=None):
Chris Sosadad0d322011-01-31 16:37:33 -0800203 """Returns True if the branch exists."""
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400204 if not branch:
205 branch = self.branch_name
David James67d73252013-09-19 17:33:12 -0700206 branches = git.RunGit(self.cwd, ['branch']).output
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400207 return branch in branches.split()
Chris Sosadad0d322011-01-31 16:37:33 -0800208
209
David James1b363582012-12-17 11:53:11 -0800210def main(_argv):
Chris Sosa62ad8522011-03-08 17:46:17 -0800211 parser = optparse.OptionParser('cros_mark_as_stable OPTIONS packages')
212 parser.add_option('--all', action='store_true',
213 help='Mark all packages as stable.')
Mike Frysinger587bd562012-11-19 16:41:39 -0500214 parser.add_option('-b', '--boards', default='',
David Jamescc09c9b2012-01-26 22:10:13 -0800215 help='Colon-separated list of boards')
Chris Sosa62ad8522011-03-08 17:46:17 -0800216 parser.add_option('--drop_file',
217 help='File to list packages that were revved.')
218 parser.add_option('--dryrun', action='store_true',
219 help='Passes dry-run to git push if pushing a change.')
220 parser.add_option('-o', '--overlays',
221 help='Colon-separated list of overlays to modify.')
222 parser.add_option('-p', '--packages',
223 help='Colon separated list of packages to rev.')
224 parser.add_option('-r', '--srcroot',
Mike Frysingerfddaeb52012-11-20 11:17:31 -0500225 default=os.path.join(constants.SOURCE_ROOT, 'src'),
Chris Sosa62ad8522011-03-08 17:46:17 -0800226 help='Path to root src directory.')
Chris Sosa62ad8522011-03-08 17:46:17 -0800227 parser.add_option('--verbose', action='store_true',
228 help='Prints out debug info.')
229 (options, args) = parser.parse_args()
Chris Sosadad0d322011-01-31 16:37:33 -0800230
J. Richard Barnette2fa9fd62011-12-02 17:10:10 -0800231 portage_utilities.EBuild.VERBOSE = options.verbose
Chris Sosa62ad8522011-03-08 17:46:17 -0800232
233 if len(args) != 1:
Ryan Cui05a31ba2011-05-31 17:47:37 -0700234 _PrintUsageAndDie('Must specify a valid command [commit, push]')
Chris Sosa62ad8522011-03-08 17:46:17 -0800235
236 command = args[0]
237 package_list = None
238 if options.packages:
239 package_list = options.packages.split(':')
240
David James1b363582012-12-17 11:53:11 -0800241 _CheckSaneArguments(command, options)
Chris Sosa62ad8522011-03-08 17:46:17 -0800242 if options.overlays:
Chris Sosadad0d322011-01-31 16:37:33 -0800243 overlays = {}
Chris Sosa62ad8522011-03-08 17:46:17 -0800244 for path in options.overlays.split(':'):
Ryan Cui05a31ba2011-05-31 17:47:37 -0700245 if not os.path.isdir(path):
Chris Sosac13bba52011-05-24 15:14:09 -0700246 cros_build_lib.Die('Cannot find overlay: %s' % path)
Chris Sosadad0d322011-01-31 16:37:33 -0800247 overlays[path] = []
248 else:
Chris Sosac13bba52011-05-24 15:14:09 -0700249 cros_build_lib.Warning('Missing --overlays argument')
Chris Sosadad0d322011-01-31 16:37:33 -0800250 overlays = {
Chris Sosa62ad8522011-03-08 17:46:17 -0800251 '%s/private-overlays/chromeos-overlay' % options.srcroot: [],
252 '%s/third_party/chromiumos-overlay' % options.srcroot: []
Chris Sosadad0d322011-01-31 16:37:33 -0800253 }
254
David James97d95872012-11-16 15:09:56 -0800255 manifest = git.ManifestCheckout.Cached(options.srcroot)
Ryan Cui4656a3c2011-05-24 12:30:30 -0700256
David James84e953c2013-04-23 18:44:06 -0700257 if command == 'commit':
258 portage_utilities.BuildEBuildDictionary(overlays, options.all, package_list)
259
David James15ed1302013-04-25 09:21:19 -0700260 # Contains the array of packages we actually revved.
261 revved_packages = []
262 new_package_atoms = []
David Jamesa8457b52011-05-28 00:03:20 -0700263
David James15ed1302013-04-25 09:21:19 -0700264 # Slight optimization hack: process the chromiumos overlay before any other
265 # cros-workon overlay first so we can do background cache generation in it.
266 # A perfect solution would walk all the overlays, figure out any dependencies
267 # between them (with layout.conf), and then process them in dependency order.
268 # However, this operation isn't slow enough to warrant that level of
269 # complexity, so we'll just special case the main overlay.
270 #
271 # Similarly, generate the cache in the portage-stable tree asap. We know
272 # we won't have any cros-workon packages in there, so generating the cache
273 # is the only thing it'll be doing. The chromiumos overlay instead might
274 # have revbumping to do before it can generate the cache.
275 keys = overlays.keys()
276 for overlay in ('/third_party/chromiumos-overlay',
277 '/third_party/portage-stable'):
278 for k in keys:
279 if k.endswith(overlay):
280 keys.remove(k)
281 keys.insert(0, k)
282 break
Chris Sosadad0d322011-01-31 16:37:33 -0800283
David James15ed1302013-04-25 09:21:19 -0700284 with parallel.BackgroundTaskRunner(portage_utilities.RegenCache) as queue:
285 for overlay in keys:
286 ebuilds = overlays[overlay]
287 if not os.path.isdir(overlay):
Mike Frysinger5cd8c742013-10-11 14:43:01 -0400288 cros_build_lib.Warning('Skipping %s' % overlay)
David James15ed1302013-04-25 09:21:19 -0700289 continue
Chris Sosadad0d322011-01-31 16:37:33 -0800290
David James15ed1302013-04-25 09:21:19 -0700291 # Note we intentionally work from the non push tracking branch;
292 # everything built thus far has been against it (meaning, http mirrors),
293 # thus we should honor that. During the actual push, the code switches
294 # to the correct urls, and does an appropriate rebasing.
295 tracking_branch = git.GetTrackingBranchViaManifest(
296 overlay, manifest=manifest)[1]
Brian Harring609dc4e2012-05-07 02:17:44 -0700297
David James15ed1302013-04-25 09:21:19 -0700298 if command == 'push':
299 PushChange(constants.STABLE_EBUILD_BRANCH, tracking_branch,
300 options.dryrun, cwd=overlay)
301 elif command == 'commit':
David James98b77f52013-11-19 10:11:56 -0800302 existing_commit = git.GetGitRepoRevision(overlay)
David James15ed1302013-04-25 09:21:19 -0700303 work_branch = GitBranch(constants.STABLE_EBUILD_BRANCH, tracking_branch,
304 cwd=overlay)
305 work_branch.CreateBranch()
306 if not work_branch.Exists():
307 cros_build_lib.Die('Unable to create stabilizing branch in %s' %
308 overlay)
309
310 # In the case of uprevving overlays that have patches applied to them,
311 # include the patched changes in the stabilizing branch.
David James98b77f52013-11-19 10:11:56 -0800312 git.RunGit(overlay, ['rebase', existing_commit])
David James15ed1302013-04-25 09:21:19 -0700313
314 messages = []
315 for ebuild in ebuilds:
316 if options.verbose:
317 cros_build_lib.Info('Working on %s', ebuild.package)
318 try:
319 new_package = ebuild.RevWorkOnEBuild(options.srcroot, manifest)
320 if new_package:
321 revved_packages.append(ebuild.package)
322 new_package_atoms.append('=%s' % new_package)
323 messages.append(_GIT_COMMIT_MESSAGE % ebuild.package)
324 except (OSError, IOError):
Mike Frysinger5cd8c742013-10-11 14:43:01 -0400325 cros_build_lib.Warning(
326 'Cannot rev %s\n'
327 'Note you will have to go into %s '
328 'and reset the git repo yourself.' % (ebuild.package, overlay))
David James15ed1302013-04-25 09:21:19 -0700329 raise
330
331 if messages:
332 portage_utilities.EBuild.CommitChange('\n\n'.join(messages), overlay)
333
334 if cros_build_lib.IsInsideChroot():
335 # Regenerate caches if need be. We do this all the time to
336 # catch when users make changes without updating cache files.
337 queue.put([overlay])
338
339 if command == 'commit':
340 if cros_build_lib.IsInsideChroot():
341 CleanStalePackages(options.boards.split(':'), new_package_atoms)
342 if options.drop_file:
343 osutils.WriteFile(options.drop_file, ' '.join(revved_packages))