blob: 07d469740e445e54a054c842e8e75983932a5780 [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
Ralph Nathan03047282015-03-23 11:09:32 -070015from chromite.lib import cros_logging as logging
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
Alex Deymo075c2292014-09-04 18:31:50 -070019from chromite.lib import portage_util
Chris Sosac13bba52011-05-24 15:14:09 -070020
Gabe Black71e963e2014-10-28 20:19:59 -070021# Commit message subject for uprevving Portage packages.
22GIT_COMMIT_SUBJECT = 'Marking set of ebuilds as stable'
David James15ed1302013-04-25 09:21:19 -070023
David James29e86d52013-04-19 09:41:29 -070024# Commit message for uprevving Portage packages.
25_GIT_COMMIT_MESSAGE = 'Marking 9999 ebuild for %s as stable.'
26
Chris Sosadad0d322011-01-31 16:37:33 -080027# Dictionary of valid commands with usage information.
28COMMAND_DICTIONARY = {
Mike Frysinger5cd8c742013-10-11 14:43:01 -040029 'commit': 'Marks given ebuilds as stable locally',
30 'push': 'Pushes previous marking of ebuilds to remote repo',
31}
Chris Sosadad0d322011-01-31 16:37:33 -080032
Chris Sosadad0d322011-01-31 16:37:33 -080033
Chris Sosadad0d322011-01-31 16:37:33 -080034# ======================= Global Helper Functions ========================
35
36
David Jamescc09c9b2012-01-26 22:10:13 -080037def CleanStalePackages(boards, package_atoms):
Chris Sosabf153872011-04-28 14:21:09 -070038 """Cleans up stale package info from a previous build.
Mike Frysinger5cd8c742013-10-11 14:43:01 -040039
Chris Sosabf153872011-04-28 14:21:09 -070040 Args:
David Jamescc09c9b2012-01-26 22:10:13 -080041 boards: Boards to clean the packages from.
Mike Frysingerde5ab0e2013-03-21 20:48:36 -040042 package_atoms: A list of package atoms to unmerge.
Chris Sosabf153872011-04-28 14:21:09 -070043 """
David James15ed1302013-04-25 09:21:19 -070044 if package_atoms:
Ralph Nathan03047282015-03-23 11:09:32 -070045 logging.info('Cleaning up stale packages %s.' % package_atoms)
David James15ed1302013-04-25 09:21:19 -070046
Mike Frysingerb7ab9b82012-04-04 16:22:43 -040047 # First unmerge all the packages for a board, then eclean it.
48 # We need these two steps to run in order (unmerge/eclean),
49 # but we can let all the boards run in parallel.
50 def _CleanStalePackages(board):
51 if board:
52 suffix = '-' + board
53 runcmd = cros_build_lib.RunCommand
54 else:
55 suffix = ''
56 runcmd = cros_build_lib.SudoRunCommand
Chris Sosadad0d322011-01-31 16:37:33 -080057
David James59a0a2b2013-03-22 14:04:44 -070058 emerge, eclean = 'emerge' + suffix, 'eclean' + suffix
59 if not osutils.FindMissingBinaries([emerge, eclean]):
David James63841a82014-01-16 14:39:24 -080060 if package_atoms:
61 # If nothing was found to be unmerged, emerge will exit(1).
62 result = runcmd([emerge, '-q', '--unmerge'] + package_atoms,
63 extra_env={'CLEAN_DELAY': '0'}, error_code_ok=True)
64 if not result.returncode in (0, 1):
65 raise cros_build_lib.RunCommandError('unexpected error', result)
David James59a0a2b2013-03-22 14:04:44 -070066 runcmd([eclean, '-d', 'packages'],
67 redirect_stdout=True, redirect_stderr=True)
Mike Frysingerb7ab9b82012-04-04 16:22:43 -040068
69 tasks = []
David Jamescc09c9b2012-01-26 22:10:13 -080070 for board in boards:
Mike Frysingerb7ab9b82012-04-04 16:22:43 -040071 tasks.append([board])
72 tasks.append([None])
73
David James6450a0a2012-12-04 07:59:53 -080074 parallel.RunTasksInProcessPool(_CleanStalePackages, tasks)
Chris Sosadad0d322011-01-31 16:37:33 -080075
76
Mike Frysinger2ebe3732012-05-08 17:04:12 -040077# TODO(build): This code needs to be gutted and rebased to cros_build_lib.
78def _DoWeHaveLocalCommits(stable_branch, tracking_branch, cwd):
Chris Sosadad0d322011-01-31 16:37:33 -080079 """Returns true if there are local commits."""
David James97d95872012-11-16 15:09:56 -080080 current_branch = git.GetCurrentBranch(cwd)
Brian Harring609dc4e2012-05-07 02:17:44 -070081
82 if current_branch != stable_branch:
Chris Sosadad0d322011-01-31 16:37:33 -080083 return False
David James97d95872012-11-16 15:09:56 -080084 output = git.RunGit(
Brian Harring609dc4e2012-05-07 02:17:44 -070085 cwd, ['rev-parse', 'HEAD', tracking_branch]).output.split()
Brian Harring5b86b5e2012-05-25 18:27:40 -070086 return output[0] != output[1]
Chris Sosadad0d322011-01-31 16:37:33 -080087
88
David James1b363582012-12-17 11:53:11 -080089def _CheckSaneArguments(command, options):
Chris Sosadad0d322011-01-31 16:37:33 -080090 """Checks to make sure the flags are sane. Dies if arguments are not sane."""
91 if not command in COMMAND_DICTIONARY.keys():
92 _PrintUsageAndDie('%s is not a valid command' % command)
Chris Sosa62ad8522011-03-08 17:46:17 -080093 if not options.packages and command == 'commit' and not options.all:
Chris Sosadad0d322011-01-31 16:37:33 -080094 _PrintUsageAndDie('Please specify at least one package')
Mike Frysinger8fd67dc2012-12-03 23:51:18 -050095 if options.boards:
96 cros_build_lib.AssertInsideChroot()
Chris Sosa62ad8522011-03-08 17:46:17 -080097 if not os.path.isdir(options.srcroot):
Chris Sosadad0d322011-01-31 16:37:33 -080098 _PrintUsageAndDie('srcroot is not a valid path')
Chris Sosa62ad8522011-03-08 17:46:17 -080099 options.srcroot = os.path.abspath(options.srcroot)
Chris Sosadad0d322011-01-31 16:37:33 -0800100
101
102def _PrintUsageAndDie(error_message=''):
103 """Prints optional error_message the usage and returns an error exit code."""
104 command_usage = 'Commands: \n'
105 # Add keys and usage information from dictionary.
106 commands = sorted(COMMAND_DICTIONARY.keys())
107 for command in commands:
108 command_usage += ' %s: %s\n' % (command, COMMAND_DICTIONARY[command])
109 commands_str = '|'.join(commands)
Chris Sosac13bba52011-05-24 15:14:09 -0700110 cros_build_lib.Warning('Usage: %s FLAGS [%s]\n\n%s' % (
111 sys.argv[0], commands_str, command_usage))
Chris Sosadad0d322011-01-31 16:37:33 -0800112 if error_message:
Chris Sosac13bba52011-05-24 15:14:09 -0700113 cros_build_lib.Die(error_message)
Chris Sosadad0d322011-01-31 16:37:33 -0800114 else:
115 sys.exit(1)
116
117
Chris Sosadad0d322011-01-31 16:37:33 -0800118# ======================= End Global Helper Functions ========================
119
120
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400121def PushChange(stable_branch, tracking_branch, dryrun, cwd):
Chris Sosadad0d322011-01-31 16:37:33 -0800122 """Pushes commits in the stable_branch to the remote git repository.
123
David Jamesee2da622012-02-23 09:32:16 -0800124 Pushes local commits from calls to CommitChange to the remote git
David James66009462012-03-25 10:08:38 -0700125 repository specified by current working directory. If changes are
126 found to commit, they will be merged to the merge branch and pushed.
127 In that case, the local repository will be left on the merge branch.
Chris Sosadad0d322011-01-31 16:37:33 -0800128
129 Args:
130 stable_branch: The local branch with commits we want to push.
131 tracking_branch: The tracking branch of the local branch.
Chris Sosa62ad8522011-03-08 17:46:17 -0800132 dryrun: Use git push --dryrun to emulate a push.
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400133 cwd: The directory to run commands in.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500134
Chris Sosadad0d322011-01-31 16:37:33 -0800135 Raises:
Mike Frysinger5cd8c742013-10-11 14:43:01 -0400136 OSError: Error occurred while pushing.
Chris Sosadad0d322011-01-31 16:37:33 -0800137 """
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400138 if not _DoWeHaveLocalCommits(stable_branch, tracking_branch, cwd):
Ralph Nathan03047282015-03-23 11:09:32 -0700139 logging.info('No work found to push in %s. Exiting', cwd)
Chris Sosadad0d322011-01-31 16:37:33 -0800140 return
141
David James66009462012-03-25 10:08:38 -0700142 # For the commit queue, our local branch may contain commits that were
143 # just tested and pushed during the CommitQueueCompletion stage. Sync
144 # and rebase our local branch on top of the remote commits.
David James97d95872012-11-16 15:09:56 -0800145 remote, push_branch = git.GetTrackingBranch(cwd, for_push=True)
146 git.SyncPushBranch(cwd, remote, push_branch)
David James66009462012-03-25 10:08:38 -0700147
148 # Check whether any local changes remain after the sync.
Brian Harring609dc4e2012-05-07 02:17:44 -0700149 if not _DoWeHaveLocalCommits(stable_branch, push_branch, cwd):
Ralph Nathan03047282015-03-23 11:09:32 -0700150 logging.info('All changes already pushed for %s. Exiting', cwd)
David James66009462012-03-25 10:08:38 -0700151 return
152
Matt Tennantcb522052013-11-25 14:23:43 -0800153 # Add a failsafe check here. Only CLs from the 'chrome-bot' user should
154 # be involved here. If any other CLs are found then complain.
155 # In dryruns extra CLs are normal, though, and can be ignored.
156 bad_cl_cmd = ['log', '--format=short', '--perl-regexp',
157 '--author', '^(?!chrome-bot)', '%s..%s' % (
158 push_branch, stable_branch)]
159 bad_cls = git.RunGit(cwd, bad_cl_cmd).output
160 if bad_cls.strip() and not dryrun:
161 cros_build_lib.Error('The Uprev stage found changes from users other'
162 ' than chrome-bot:\n\n%s', bad_cls)
163 raise AssertionError('Unexpected CLs found during uprev stage.')
164
Mike Frysingere65f3752014-12-08 00:46:39 -0500165 description = git.RunGit(
166 cwd,
167 ['log', '--format=format:%s%n%n%b',
168 '%s..%s' % (push_branch, stable_branch)]).output
Gabe Black71e963e2014-10-28 20:19:59 -0700169 description = '%s\n\n%s' % (GIT_COMMIT_SUBJECT, description)
Ralph Nathan03047282015-03-23 11:09:32 -0700170 logging.info('For %s, using description %s', cwd, description)
David James97d95872012-11-16 15:09:56 -0800171 git.CreatePushBranch(constants.MERGE_BRANCH, cwd)
172 git.RunGit(cwd, ['merge', '--squash', stable_branch])
173 git.RunGit(cwd, ['commit', '-m', description])
174 git.RunGit(cwd, ['config', 'push.default', 'tracking'])
175 git.PushWithRetry(constants.MERGE_BRANCH, cwd, dryrun=dryrun)
Chris Sosadad0d322011-01-31 16:37:33 -0800176
177
178class GitBranch(object):
179 """Wrapper class for a git branch."""
180
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400181 def __init__(self, branch_name, tracking_branch, cwd):
David Jamesc7c4ff52013-09-18 17:57:13 -0700182 """Sets up variables but does not create the branch.
183
Mike Frysinger5cd8c742013-10-11 14:43:01 -0400184 Args:
David Jamesc7c4ff52013-09-18 17:57:13 -0700185 branch_name: The name of the branch.
186 tracking_branch: The associated tracking branch.
187 cwd: The git repository to work in.
188 """
Chris Sosadad0d322011-01-31 16:37:33 -0800189 self.branch_name = branch_name
190 self.tracking_branch = tracking_branch
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400191 self.cwd = cwd
Chris Sosadad0d322011-01-31 16:37:33 -0800192
193 def CreateBranch(self):
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400194 self.Checkout()
Chris Sosadad0d322011-01-31 16:37:33 -0800195
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400196 def Checkout(self, branch=None):
Chris Sosadad0d322011-01-31 16:37:33 -0800197 """Function used to check out to another GitBranch."""
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400198 if not branch:
199 branch = self.branch_name
200 if branch == self.tracking_branch or self.Exists(branch):
201 git_cmd = ['git', 'checkout', '-f', branch]
Chris Sosadad0d322011-01-31 16:37:33 -0800202 else:
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400203 git_cmd = ['repo', 'start', branch, '.']
Yu-Ju Hong3add4432014-01-30 11:46:15 -0800204 cros_build_lib.RunCommand(git_cmd, print_cmd=False, cwd=self.cwd,
205 capture_output=True)
Chris Sosadad0d322011-01-31 16:37:33 -0800206
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400207 def Exists(self, branch=None):
Chris Sosadad0d322011-01-31 16:37:33 -0800208 """Returns True if the branch exists."""
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400209 if not branch:
210 branch = self.branch_name
David James67d73252013-09-19 17:33:12 -0700211 branches = git.RunGit(self.cwd, ['branch']).output
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400212 return branch in branches.split()
Chris Sosadad0d322011-01-31 16:37:33 -0800213
214
David James1b363582012-12-17 11:53:11 -0800215def main(_argv):
Chris Sosa62ad8522011-03-08 17:46:17 -0800216 parser = optparse.OptionParser('cros_mark_as_stable OPTIONS packages')
217 parser.add_option('--all', action='store_true',
218 help='Mark all packages as stable.')
Mike Frysinger587bd562012-11-19 16:41:39 -0500219 parser.add_option('-b', '--boards', default='',
David Jamescc09c9b2012-01-26 22:10:13 -0800220 help='Colon-separated list of boards')
Chris Sosa62ad8522011-03-08 17:46:17 -0800221 parser.add_option('--drop_file',
222 help='File to list packages that were revved.')
223 parser.add_option('--dryrun', action='store_true',
224 help='Passes dry-run to git push if pushing a change.')
225 parser.add_option('-o', '--overlays',
226 help='Colon-separated list of overlays to modify.')
227 parser.add_option('-p', '--packages',
228 help='Colon separated list of packages to rev.')
229 parser.add_option('-r', '--srcroot',
Mike Frysingerfddaeb52012-11-20 11:17:31 -0500230 default=os.path.join(constants.SOURCE_ROOT, 'src'),
Chris Sosa62ad8522011-03-08 17:46:17 -0800231 help='Path to root src directory.')
Chris Sosa62ad8522011-03-08 17:46:17 -0800232 parser.add_option('--verbose', action='store_true',
233 help='Prints out debug info.')
234 (options, args) = parser.parse_args()
Chris Sosadad0d322011-01-31 16:37:33 -0800235
Alex Deymo075c2292014-09-04 18:31:50 -0700236 portage_util.EBuild.VERBOSE = options.verbose
Chris Sosa62ad8522011-03-08 17:46:17 -0800237
238 if len(args) != 1:
Ryan Cui05a31ba2011-05-31 17:47:37 -0700239 _PrintUsageAndDie('Must specify a valid command [commit, push]')
Chris Sosa62ad8522011-03-08 17:46:17 -0800240
241 command = args[0]
242 package_list = None
243 if options.packages:
244 package_list = options.packages.split(':')
245
David James1b363582012-12-17 11:53:11 -0800246 _CheckSaneArguments(command, options)
Chris Sosa62ad8522011-03-08 17:46:17 -0800247 if options.overlays:
Chris Sosadad0d322011-01-31 16:37:33 -0800248 overlays = {}
Chris Sosa62ad8522011-03-08 17:46:17 -0800249 for path in options.overlays.split(':'):
Ryan Cui05a31ba2011-05-31 17:47:37 -0700250 if not os.path.isdir(path):
Chris Sosac13bba52011-05-24 15:14:09 -0700251 cros_build_lib.Die('Cannot find overlay: %s' % path)
Chris Sosadad0d322011-01-31 16:37:33 -0800252 overlays[path] = []
253 else:
Chris Sosac13bba52011-05-24 15:14:09 -0700254 cros_build_lib.Warning('Missing --overlays argument')
Chris Sosadad0d322011-01-31 16:37:33 -0800255 overlays = {
Mike Frysingere65f3752014-12-08 00:46:39 -0500256 '%s/private-overlays/chromeos-overlay' % options.srcroot: [],
257 '%s/third_party/chromiumos-overlay' % options.srcroot: [],
Chris Sosadad0d322011-01-31 16:37:33 -0800258 }
259
David James97d95872012-11-16 15:09:56 -0800260 manifest = git.ManifestCheckout.Cached(options.srcroot)
Ryan Cui4656a3c2011-05-24 12:30:30 -0700261
David James84e953c2013-04-23 18:44:06 -0700262 if command == 'commit':
Alex Deymo075c2292014-09-04 18:31:50 -0700263 portage_util.BuildEBuildDictionary(overlays, options.all, package_list)
David James84e953c2013-04-23 18:44:06 -0700264
David James15ed1302013-04-25 09:21:19 -0700265 # Contains the array of packages we actually revved.
266 revved_packages = []
267 new_package_atoms = []
David Jamesa8457b52011-05-28 00:03:20 -0700268
David James15ed1302013-04-25 09:21:19 -0700269 # Slight optimization hack: process the chromiumos overlay before any other
270 # cros-workon overlay first so we can do background cache generation in it.
271 # A perfect solution would walk all the overlays, figure out any dependencies
272 # between them (with layout.conf), and then process them in dependency order.
273 # However, this operation isn't slow enough to warrant that level of
274 # complexity, so we'll just special case the main overlay.
275 #
276 # Similarly, generate the cache in the portage-stable tree asap. We know
277 # we won't have any cros-workon packages in there, so generating the cache
278 # is the only thing it'll be doing. The chromiumos overlay instead might
279 # have revbumping to do before it can generate the cache.
280 keys = overlays.keys()
281 for overlay in ('/third_party/chromiumos-overlay',
282 '/third_party/portage-stable'):
283 for k in keys:
284 if k.endswith(overlay):
285 keys.remove(k)
286 keys.insert(0, k)
287 break
Chris Sosadad0d322011-01-31 16:37:33 -0800288
Alex Deymo075c2292014-09-04 18:31:50 -0700289 with parallel.BackgroundTaskRunner(portage_util.RegenCache) as queue:
David James15ed1302013-04-25 09:21:19 -0700290 for overlay in keys:
291 ebuilds = overlays[overlay]
292 if not os.path.isdir(overlay):
Mike Frysinger5cd8c742013-10-11 14:43:01 -0400293 cros_build_lib.Warning('Skipping %s' % overlay)
David James15ed1302013-04-25 09:21:19 -0700294 continue
Chris Sosadad0d322011-01-31 16:37:33 -0800295
David James15ed1302013-04-25 09:21:19 -0700296 # Note we intentionally work from the non push tracking branch;
297 # everything built thus far has been against it (meaning, http mirrors),
298 # thus we should honor that. During the actual push, the code switches
299 # to the correct urls, and does an appropriate rebasing.
300 tracking_branch = git.GetTrackingBranchViaManifest(
301 overlay, manifest=manifest)[1]
Brian Harring609dc4e2012-05-07 02:17:44 -0700302
David James15ed1302013-04-25 09:21:19 -0700303 if command == 'push':
304 PushChange(constants.STABLE_EBUILD_BRANCH, tracking_branch,
305 options.dryrun, cwd=overlay)
306 elif command == 'commit':
David James98b77f52013-11-19 10:11:56 -0800307 existing_commit = git.GetGitRepoRevision(overlay)
David James15ed1302013-04-25 09:21:19 -0700308 work_branch = GitBranch(constants.STABLE_EBUILD_BRANCH, tracking_branch,
309 cwd=overlay)
310 work_branch.CreateBranch()
311 if not work_branch.Exists():
312 cros_build_lib.Die('Unable to create stabilizing branch in %s' %
313 overlay)
314
315 # In the case of uprevving overlays that have patches applied to them,
316 # include the patched changes in the stabilizing branch.
David James98b77f52013-11-19 10:11:56 -0800317 git.RunGit(overlay, ['rebase', existing_commit])
David James15ed1302013-04-25 09:21:19 -0700318
319 messages = []
320 for ebuild in ebuilds:
321 if options.verbose:
Ralph Nathan03047282015-03-23 11:09:32 -0700322 logging.info('Working on %s', ebuild.package)
David James15ed1302013-04-25 09:21:19 -0700323 try:
324 new_package = ebuild.RevWorkOnEBuild(options.srcroot, manifest)
325 if new_package:
326 revved_packages.append(ebuild.package)
327 new_package_atoms.append('=%s' % new_package)
328 messages.append(_GIT_COMMIT_MESSAGE % ebuild.package)
329 except (OSError, IOError):
Mike Frysinger5cd8c742013-10-11 14:43:01 -0400330 cros_build_lib.Warning(
331 'Cannot rev %s\n'
332 'Note you will have to go into %s '
333 'and reset the git repo yourself.' % (ebuild.package, overlay))
David James15ed1302013-04-25 09:21:19 -0700334 raise
335
336 if messages:
Alex Deymo075c2292014-09-04 18:31:50 -0700337 portage_util.EBuild.CommitChange('\n\n'.join(messages), overlay)
David James15ed1302013-04-25 09:21:19 -0700338
339 if cros_build_lib.IsInsideChroot():
340 # Regenerate caches if need be. We do this all the time to
341 # catch when users make changes without updating cache files.
342 queue.put([overlay])
343
344 if command == 'commit':
345 if cros_build_lib.IsInsideChroot():
346 CleanStalePackages(options.boards.split(':'), new_package_atoms)
347 if options.drop_file:
348 osutils.WriteFile(options.drop_file, ' '.join(revved_packages))