blob: 5150ed86e71c705753269145dbaad7fa26f3ef57 [file] [log] [blame]
Mike Frysinger110750a2012-03-26 14:19:20 -04001# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
Chris Sosadad0d322011-01-31 16:37:33 -08002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""This module uprevs a given package's ebuild to the next revision."""
6
Mike Frysinger383367e2014-09-16 15:06:17 -04007from __future__ import print_function
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
Don Garrett88b8d782014-05-13 17:30:55 -070013from chromite.cbuildbot import constants
Chris Sosac13bba52011-05-24 15:14:09 -070014from chromite.lib import cros_build_lib
David James97d95872012-11-16 15:09:56 -080015from chromite.lib import git
Mike Frysingerfddaeb52012-11-20 11:17:31 -050016from chromite.lib import osutils
David James6450a0a2012-12-04 07:59:53 -080017from chromite.lib import parallel
Alex Deymo075c2292014-09-04 18:31:50 -070018from chromite.lib import portage_util
Chris Sosac13bba52011-05-24 15:14:09 -070019
Gabe Black71e963e2014-10-28 20:19:59 -070020# Commit message subject for uprevving Portage packages.
21GIT_COMMIT_SUBJECT = 'Marking set of ebuilds as stable'
David James15ed1302013-04-25 09:21:19 -070022
David James29e86d52013-04-19 09:41:29 -070023# Commit message for uprevving Portage packages.
24_GIT_COMMIT_MESSAGE = 'Marking 9999 ebuild for %s as stable.'
25
Chris Sosadad0d322011-01-31 16:37:33 -080026# Dictionary of valid commands with usage information.
27COMMAND_DICTIONARY = {
Mike Frysinger5cd8c742013-10-11 14:43:01 -040028 'commit': 'Marks given ebuilds as stable locally',
29 'push': 'Pushes previous marking of ebuilds to remote repo',
30}
Chris Sosadad0d322011-01-31 16:37:33 -080031
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.
Mike Frysinger5cd8c742013-10-11 14:43:01 -040038
Chris Sosabf153872011-04-28 14:21:09 -070039 Args:
David Jamescc09c9b2012-01-26 22:10:13 -080040 boards: Boards to clean the packages from.
Mike Frysingerde5ab0e2013-03-21 20:48:36 -040041 package_atoms: A list of package atoms to unmerge.
Chris Sosabf153872011-04-28 14:21:09 -070042 """
David James15ed1302013-04-25 09:21:19 -070043 if package_atoms:
44 cros_build_lib.Info('Cleaning up stale packages %s.' % package_atoms)
45
Mike Frysingerb7ab9b82012-04-04 16:22:43 -040046 # First unmerge all the packages for a board, then eclean it.
47 # We need these two steps to run in order (unmerge/eclean),
48 # but we can let all the boards run in parallel.
49 def _CleanStalePackages(board):
50 if board:
51 suffix = '-' + board
52 runcmd = cros_build_lib.RunCommand
53 else:
54 suffix = ''
55 runcmd = cros_build_lib.SudoRunCommand
Chris Sosadad0d322011-01-31 16:37:33 -080056
David James59a0a2b2013-03-22 14:04:44 -070057 emerge, eclean = 'emerge' + suffix, 'eclean' + suffix
58 if not osutils.FindMissingBinaries([emerge, eclean]):
David James63841a82014-01-16 14:39:24 -080059 if package_atoms:
60 # If nothing was found to be unmerged, emerge will exit(1).
61 result = runcmd([emerge, '-q', '--unmerge'] + package_atoms,
62 extra_env={'CLEAN_DELAY': '0'}, error_code_ok=True)
63 if not result.returncode in (0, 1):
64 raise cros_build_lib.RunCommandError('unexpected error', result)
David James59a0a2b2013-03-22 14:04:44 -070065 runcmd([eclean, '-d', 'packages'],
66 redirect_stdout=True, redirect_stderr=True)
Mike Frysingerb7ab9b82012-04-04 16:22:43 -040067
68 tasks = []
David Jamescc09c9b2012-01-26 22:10:13 -080069 for board in boards:
Mike Frysingerb7ab9b82012-04-04 16:22:43 -040070 tasks.append([board])
71 tasks.append([None])
72
David James6450a0a2012-12-04 07:59:53 -080073 parallel.RunTasksInProcessPool(_CleanStalePackages, tasks)
Chris Sosadad0d322011-01-31 16:37:33 -080074
75
Mike Frysinger2ebe3732012-05-08 17:04:12 -040076# TODO(build): This code needs to be gutted and rebased to cros_build_lib.
77def _DoWeHaveLocalCommits(stable_branch, tracking_branch, cwd):
Chris Sosadad0d322011-01-31 16:37:33 -080078 """Returns true if there are local commits."""
David James97d95872012-11-16 15:09:56 -080079 current_branch = git.GetCurrentBranch(cwd)
Brian Harring609dc4e2012-05-07 02:17:44 -070080
81 if current_branch != stable_branch:
Chris Sosadad0d322011-01-31 16:37:33 -080082 return False
David James97d95872012-11-16 15:09:56 -080083 output = git.RunGit(
Brian Harring609dc4e2012-05-07 02:17:44 -070084 cwd, ['rev-parse', 'HEAD', tracking_branch]).output.split()
Brian Harring5b86b5e2012-05-25 18:27:40 -070085 return output[0] != output[1]
Chris Sosadad0d322011-01-31 16:37:33 -080086
87
David James1b363582012-12-17 11:53:11 -080088def _CheckSaneArguments(command, options):
Chris Sosadad0d322011-01-31 16:37:33 -080089 """Checks to make sure the flags are sane. Dies if arguments are not sane."""
90 if not command in COMMAND_DICTIONARY.keys():
91 _PrintUsageAndDie('%s is not a valid command' % command)
Chris Sosa62ad8522011-03-08 17:46:17 -080092 if not options.packages and command == 'commit' and not options.all:
Chris Sosadad0d322011-01-31 16:37:33 -080093 _PrintUsageAndDie('Please specify at least one package')
Mike Frysinger8fd67dc2012-12-03 23:51:18 -050094 if options.boards:
95 cros_build_lib.AssertInsideChroot()
Chris Sosa62ad8522011-03-08 17:46:17 -080096 if not os.path.isdir(options.srcroot):
Chris Sosadad0d322011-01-31 16:37:33 -080097 _PrintUsageAndDie('srcroot is not a valid path')
Chris Sosa62ad8522011-03-08 17:46:17 -080098 options.srcroot = os.path.abspath(options.srcroot)
Chris Sosadad0d322011-01-31 16:37:33 -080099
100
101def _PrintUsageAndDie(error_message=''):
102 """Prints optional error_message the usage and returns an error exit code."""
103 command_usage = 'Commands: \n'
104 # Add keys and usage information from dictionary.
105 commands = sorted(COMMAND_DICTIONARY.keys())
106 for command in commands:
107 command_usage += ' %s: %s\n' % (command, COMMAND_DICTIONARY[command])
108 commands_str = '|'.join(commands)
Chris Sosac13bba52011-05-24 15:14:09 -0700109 cros_build_lib.Warning('Usage: %s FLAGS [%s]\n\n%s' % (
110 sys.argv[0], commands_str, command_usage))
Chris Sosadad0d322011-01-31 16:37:33 -0800111 if error_message:
Chris Sosac13bba52011-05-24 15:14:09 -0700112 cros_build_lib.Die(error_message)
Chris Sosadad0d322011-01-31 16:37:33 -0800113 else:
114 sys.exit(1)
115
116
Chris Sosadad0d322011-01-31 16:37:33 -0800117# ======================= End Global Helper Functions ========================
118
119
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400120def PushChange(stable_branch, tracking_branch, dryrun, cwd):
Chris Sosadad0d322011-01-31 16:37:33 -0800121 """Pushes commits in the stable_branch to the remote git repository.
122
David Jamesee2da622012-02-23 09:32:16 -0800123 Pushes local commits from calls to CommitChange to the remote git
David James66009462012-03-25 10:08:38 -0700124 repository specified by current working directory. If changes are
125 found to commit, they will be merged to the merge branch and pushed.
126 In that case, the local repository will be left on the merge branch.
Chris Sosadad0d322011-01-31 16:37:33 -0800127
128 Args:
129 stable_branch: The local branch with commits we want to push.
130 tracking_branch: The tracking branch of the local branch.
Chris Sosa62ad8522011-03-08 17:46:17 -0800131 dryrun: Use git push --dryrun to emulate a push.
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400132 cwd: The directory to run commands in.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500133
Chris Sosadad0d322011-01-31 16:37:33 -0800134 Raises:
Mike Frysinger5cd8c742013-10-11 14:43:01 -0400135 OSError: Error occurred while pushing.
Chris Sosadad0d322011-01-31 16:37:33 -0800136 """
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400137 if not _DoWeHaveLocalCommits(stable_branch, tracking_branch, cwd):
Brian Harring609dc4e2012-05-07 02:17:44 -0700138 cros_build_lib.Info('No work found to push in %s. Exiting', cwd)
Chris Sosadad0d322011-01-31 16:37:33 -0800139 return
140
David James66009462012-03-25 10:08:38 -0700141 # For the commit queue, our local branch may contain commits that were
142 # just tested and pushed during the CommitQueueCompletion stage. Sync
143 # and rebase our local branch on top of the remote commits.
David James97d95872012-11-16 15:09:56 -0800144 remote, push_branch = git.GetTrackingBranch(cwd, for_push=True)
145 git.SyncPushBranch(cwd, remote, push_branch)
David James66009462012-03-25 10:08:38 -0700146
147 # Check whether any local changes remain after the sync.
Brian Harring609dc4e2012-05-07 02:17:44 -0700148 if not _DoWeHaveLocalCommits(stable_branch, push_branch, cwd):
149 cros_build_lib.Info('All changes already pushed for %s. Exiting', cwd)
David James66009462012-03-25 10:08:38 -0700150 return
151
Matt Tennantcb522052013-11-25 14:23:43 -0800152 # Add a failsafe check here. Only CLs from the 'chrome-bot' user should
153 # be involved here. If any other CLs are found then complain.
154 # In dryruns extra CLs are normal, though, and can be ignored.
155 bad_cl_cmd = ['log', '--format=short', '--perl-regexp',
156 '--author', '^(?!chrome-bot)', '%s..%s' % (
157 push_branch, stable_branch)]
158 bad_cls = git.RunGit(cwd, bad_cl_cmd).output
159 if bad_cls.strip() and not dryrun:
160 cros_build_lib.Error('The Uprev stage found changes from users other'
161 ' than chrome-bot:\n\n%s', bad_cls)
162 raise AssertionError('Unexpected CLs found during uprev stage.')
163
Mike Frysingere65f3752014-12-08 00:46:39 -0500164 description = git.RunGit(
165 cwd,
166 ['log', '--format=format:%s%n%n%b',
167 '%s..%s' % (push_branch, stable_branch)]).output
Gabe Black71e963e2014-10-28 20:19:59 -0700168 description = '%s\n\n%s' % (GIT_COMMIT_SUBJECT, description)
Brian Harring609dc4e2012-05-07 02:17:44 -0700169 cros_build_lib.Info('For %s, using description %s', cwd, description)
David James97d95872012-11-16 15:09:56 -0800170 git.CreatePushBranch(constants.MERGE_BRANCH, cwd)
171 git.RunGit(cwd, ['merge', '--squash', stable_branch])
172 git.RunGit(cwd, ['commit', '-m', description])
173 git.RunGit(cwd, ['config', 'push.default', 'tracking'])
174 git.PushWithRetry(constants.MERGE_BRANCH, cwd, dryrun=dryrun)
Chris Sosadad0d322011-01-31 16:37:33 -0800175
176
177class GitBranch(object):
178 """Wrapper class for a git branch."""
179
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400180 def __init__(self, branch_name, tracking_branch, cwd):
David Jamesc7c4ff52013-09-18 17:57:13 -0700181 """Sets up variables but does not create the branch.
182
Mike Frysinger5cd8c742013-10-11 14:43:01 -0400183 Args:
David Jamesc7c4ff52013-09-18 17:57:13 -0700184 branch_name: The name of the branch.
185 tracking_branch: The associated tracking branch.
186 cwd: The git repository to work in.
187 """
Chris Sosadad0d322011-01-31 16:37:33 -0800188 self.branch_name = branch_name
189 self.tracking_branch = tracking_branch
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400190 self.cwd = cwd
Chris Sosadad0d322011-01-31 16:37:33 -0800191
192 def CreateBranch(self):
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400193 self.Checkout()
Chris Sosadad0d322011-01-31 16:37:33 -0800194
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400195 def Checkout(self, branch=None):
Chris Sosadad0d322011-01-31 16:37:33 -0800196 """Function used to check out to another GitBranch."""
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400197 if not branch:
198 branch = self.branch_name
199 if branch == self.tracking_branch or self.Exists(branch):
200 git_cmd = ['git', 'checkout', '-f', branch]
Chris Sosadad0d322011-01-31 16:37:33 -0800201 else:
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400202 git_cmd = ['repo', 'start', branch, '.']
Yu-Ju Hong3add4432014-01-30 11:46:15 -0800203 cros_build_lib.RunCommand(git_cmd, print_cmd=False, cwd=self.cwd,
204 capture_output=True)
Chris Sosadad0d322011-01-31 16:37:33 -0800205
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400206 def Exists(self, branch=None):
Chris Sosadad0d322011-01-31 16:37:33 -0800207 """Returns True if the branch exists."""
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400208 if not branch:
209 branch = self.branch_name
David James67d73252013-09-19 17:33:12 -0700210 branches = git.RunGit(self.cwd, ['branch']).output
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400211 return branch in branches.split()
Chris Sosadad0d322011-01-31 16:37:33 -0800212
213
David James1b363582012-12-17 11:53:11 -0800214def main(_argv):
Chris Sosa62ad8522011-03-08 17:46:17 -0800215 parser = optparse.OptionParser('cros_mark_as_stable OPTIONS packages')
216 parser.add_option('--all', action='store_true',
217 help='Mark all packages as stable.')
Mike Frysinger587bd562012-11-19 16:41:39 -0500218 parser.add_option('-b', '--boards', default='',
David Jamescc09c9b2012-01-26 22:10:13 -0800219 help='Colon-separated list of boards')
Chris Sosa62ad8522011-03-08 17:46:17 -0800220 parser.add_option('--drop_file',
221 help='File to list packages that were revved.')
222 parser.add_option('--dryrun', action='store_true',
223 help='Passes dry-run to git push if pushing a change.')
224 parser.add_option('-o', '--overlays',
225 help='Colon-separated list of overlays to modify.')
226 parser.add_option('-p', '--packages',
227 help='Colon separated list of packages to rev.')
228 parser.add_option('-r', '--srcroot',
Mike Frysingerfddaeb52012-11-20 11:17:31 -0500229 default=os.path.join(constants.SOURCE_ROOT, 'src'),
Chris Sosa62ad8522011-03-08 17:46:17 -0800230 help='Path to root src directory.')
Chris Sosa62ad8522011-03-08 17:46:17 -0800231 parser.add_option('--verbose', action='store_true',
232 help='Prints out debug info.')
233 (options, args) = parser.parse_args()
Chris Sosadad0d322011-01-31 16:37:33 -0800234
Alex Deymo075c2292014-09-04 18:31:50 -0700235 portage_util.EBuild.VERBOSE = options.verbose
Chris Sosa62ad8522011-03-08 17:46:17 -0800236
237 if len(args) != 1:
Ryan Cui05a31ba2011-05-31 17:47:37 -0700238 _PrintUsageAndDie('Must specify a valid command [commit, push]')
Chris Sosa62ad8522011-03-08 17:46:17 -0800239
240 command = args[0]
241 package_list = None
242 if options.packages:
243 package_list = options.packages.split(':')
244
David James1b363582012-12-17 11:53:11 -0800245 _CheckSaneArguments(command, options)
Chris Sosa62ad8522011-03-08 17:46:17 -0800246 if options.overlays:
Chris Sosadad0d322011-01-31 16:37:33 -0800247 overlays = {}
Chris Sosa62ad8522011-03-08 17:46:17 -0800248 for path in options.overlays.split(':'):
Ryan Cui05a31ba2011-05-31 17:47:37 -0700249 if not os.path.isdir(path):
Chris Sosac13bba52011-05-24 15:14:09 -0700250 cros_build_lib.Die('Cannot find overlay: %s' % path)
Chris Sosadad0d322011-01-31 16:37:33 -0800251 overlays[path] = []
252 else:
Chris Sosac13bba52011-05-24 15:14:09 -0700253 cros_build_lib.Warning('Missing --overlays argument')
Chris Sosadad0d322011-01-31 16:37:33 -0800254 overlays = {
Mike Frysingere65f3752014-12-08 00:46:39 -0500255 '%s/private-overlays/chromeos-overlay' % options.srcroot: [],
256 '%s/third_party/chromiumos-overlay' % options.srcroot: [],
Chris Sosadad0d322011-01-31 16:37:33 -0800257 }
258
David James97d95872012-11-16 15:09:56 -0800259 manifest = git.ManifestCheckout.Cached(options.srcroot)
Ryan Cui4656a3c2011-05-24 12:30:30 -0700260
David James84e953c2013-04-23 18:44:06 -0700261 if command == 'commit':
Alex Deymo075c2292014-09-04 18:31:50 -0700262 portage_util.BuildEBuildDictionary(overlays, options.all, package_list)
David James84e953c2013-04-23 18:44:06 -0700263
David James15ed1302013-04-25 09:21:19 -0700264 # Contains the array of packages we actually revved.
265 revved_packages = []
266 new_package_atoms = []
David Jamesa8457b52011-05-28 00:03:20 -0700267
David James15ed1302013-04-25 09:21:19 -0700268 # Slight optimization hack: process the chromiumos overlay before any other
269 # cros-workon overlay first so we can do background cache generation in it.
270 # A perfect solution would walk all the overlays, figure out any dependencies
271 # between them (with layout.conf), and then process them in dependency order.
272 # However, this operation isn't slow enough to warrant that level of
273 # complexity, so we'll just special case the main overlay.
274 #
275 # Similarly, generate the cache in the portage-stable tree asap. We know
276 # we won't have any cros-workon packages in there, so generating the cache
277 # is the only thing it'll be doing. The chromiumos overlay instead might
278 # have revbumping to do before it can generate the cache.
279 keys = overlays.keys()
280 for overlay in ('/third_party/chromiumos-overlay',
281 '/third_party/portage-stable'):
282 for k in keys:
283 if k.endswith(overlay):
284 keys.remove(k)
285 keys.insert(0, k)
286 break
Chris Sosadad0d322011-01-31 16:37:33 -0800287
Alex Deymo075c2292014-09-04 18:31:50 -0700288 with parallel.BackgroundTaskRunner(portage_util.RegenCache) as queue:
David James15ed1302013-04-25 09:21:19 -0700289 for overlay in keys:
290 ebuilds = overlays[overlay]
291 if not os.path.isdir(overlay):
Mike Frysinger5cd8c742013-10-11 14:43:01 -0400292 cros_build_lib.Warning('Skipping %s' % overlay)
David James15ed1302013-04-25 09:21:19 -0700293 continue
Chris Sosadad0d322011-01-31 16:37:33 -0800294
David James15ed1302013-04-25 09:21:19 -0700295 # Note we intentionally work from the non push tracking branch;
296 # everything built thus far has been against it (meaning, http mirrors),
297 # thus we should honor that. During the actual push, the code switches
298 # to the correct urls, and does an appropriate rebasing.
299 tracking_branch = git.GetTrackingBranchViaManifest(
300 overlay, manifest=manifest)[1]
Brian Harring609dc4e2012-05-07 02:17:44 -0700301
David James15ed1302013-04-25 09:21:19 -0700302 if command == 'push':
303 PushChange(constants.STABLE_EBUILD_BRANCH, tracking_branch,
304 options.dryrun, cwd=overlay)
305 elif command == 'commit':
David James98b77f52013-11-19 10:11:56 -0800306 existing_commit = git.GetGitRepoRevision(overlay)
David James15ed1302013-04-25 09:21:19 -0700307 work_branch = GitBranch(constants.STABLE_EBUILD_BRANCH, tracking_branch,
308 cwd=overlay)
309 work_branch.CreateBranch()
310 if not work_branch.Exists():
311 cros_build_lib.Die('Unable to create stabilizing branch in %s' %
312 overlay)
313
314 # In the case of uprevving overlays that have patches applied to them,
315 # include the patched changes in the stabilizing branch.
David James98b77f52013-11-19 10:11:56 -0800316 git.RunGit(overlay, ['rebase', existing_commit])
David James15ed1302013-04-25 09:21:19 -0700317
318 messages = []
319 for ebuild in ebuilds:
320 if options.verbose:
321 cros_build_lib.Info('Working on %s', ebuild.package)
322 try:
323 new_package = ebuild.RevWorkOnEBuild(options.srcroot, manifest)
324 if new_package:
325 revved_packages.append(ebuild.package)
326 new_package_atoms.append('=%s' % new_package)
327 messages.append(_GIT_COMMIT_MESSAGE % ebuild.package)
328 except (OSError, IOError):
Mike Frysinger5cd8c742013-10-11 14:43:01 -0400329 cros_build_lib.Warning(
330 'Cannot rev %s\n'
331 'Note you will have to go into %s '
332 'and reset the git repo yourself.' % (ebuild.package, overlay))
David James15ed1302013-04-25 09:21:19 -0700333 raise
334
335 if messages:
Alex Deymo075c2292014-09-04 18:31:50 -0700336 portage_util.EBuild.CommitChange('\n\n'.join(messages), overlay)
David James15ed1302013-04-25 09:21:19 -0700337
338 if cros_build_lib.IsInsideChroot():
339 # Regenerate caches if need be. We do this all the time to
340 # catch when users make changes without updating cache files.
341 queue.put([overlay])
342
343 if command == 'commit':
344 if cros_build_lib.IsInsideChroot():
345 CleanStalePackages(options.boards.split(':'), new_package_atoms)
346 if options.drop_file:
347 osutils.WriteFile(options.drop_file, ' '.join(revved_packages))