blob: 2b482a9328e66cb45fad00e70c218f2546714034 [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
Chris Sosa62ad8522011-03-08 17:46:17 -08007import optparse
Chris Sosadad0d322011-01-31 16:37:33 -08008import os
Chris Sosadad0d322011-01-31 16:37:33 -08009import sys
10
Don Garrett88b8d782014-05-13 17:30:55 -070011from chromite.cbuildbot import constants
Chris Sosac13bba52011-05-24 15:14:09 -070012from chromite.lib import cros_build_lib
David James97d95872012-11-16 15:09:56 -080013from chromite.lib import git
Mike Frysingerfddaeb52012-11-20 11:17:31 -050014from chromite.lib import osutils
David James6450a0a2012-12-04 07:59:53 -080015from chromite.lib import parallel
Alex Deymo075c2292014-09-04 18:31:50 -070016from chromite.lib import portage_util
Chris Sosac13bba52011-05-24 15:14:09 -070017
David James15ed1302013-04-25 09:21:19 -070018
David James29e86d52013-04-19 09:41:29 -070019# Commit message for uprevving Portage packages.
20_GIT_COMMIT_MESSAGE = 'Marking 9999 ebuild for %s as stable.'
21
Chris Sosadad0d322011-01-31 16:37:33 -080022# Dictionary of valid commands with usage information.
23COMMAND_DICTIONARY = {
Mike Frysinger5cd8c742013-10-11 14:43:01 -040024 'commit': 'Marks given ebuilds as stable locally',
25 'push': 'Pushes previous marking of ebuilds to remote repo',
26}
Chris Sosadad0d322011-01-31 16:37:33 -080027
Chris Sosadad0d322011-01-31 16:37:33 -080028
Chris Sosadad0d322011-01-31 16:37:33 -080029# ======================= Global Helper Functions ========================
30
31
David Jamescc09c9b2012-01-26 22:10:13 -080032def CleanStalePackages(boards, package_atoms):
Chris Sosabf153872011-04-28 14:21:09 -070033 """Cleans up stale package info from a previous build.
Mike Frysinger5cd8c742013-10-11 14:43:01 -040034
Chris Sosabf153872011-04-28 14:21:09 -070035 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 """
David James15ed1302013-04-25 09:21:19 -070039 if package_atoms:
40 cros_build_lib.Info('Cleaning up stale packages %s.' % package_atoms)
41
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
David James59a0a2b2013-03-22 14:04:44 -070053 emerge, eclean = 'emerge' + suffix, 'eclean' + suffix
54 if not osutils.FindMissingBinaries([emerge, eclean]):
David James63841a82014-01-16 14:39:24 -080055 if package_atoms:
56 # If nothing was found to be unmerged, emerge will exit(1).
57 result = runcmd([emerge, '-q', '--unmerge'] + package_atoms,
58 extra_env={'CLEAN_DELAY': '0'}, error_code_ok=True)
59 if not result.returncode in (0, 1):
60 raise cros_build_lib.RunCommandError('unexpected error', result)
David James59a0a2b2013-03-22 14:04:44 -070061 runcmd([eclean, '-d', 'packages'],
62 redirect_stdout=True, redirect_stderr=True)
Mike Frysingerb7ab9b82012-04-04 16:22:43 -040063
64 tasks = []
David Jamescc09c9b2012-01-26 22:10:13 -080065 for board in boards:
Mike Frysingerb7ab9b82012-04-04 16:22:43 -040066 tasks.append([board])
67 tasks.append([None])
68
David James6450a0a2012-12-04 07:59:53 -080069 parallel.RunTasksInProcessPool(_CleanStalePackages, tasks)
Chris Sosadad0d322011-01-31 16:37:33 -080070
71
Mike Frysinger2ebe3732012-05-08 17:04:12 -040072# TODO(build): This code needs to be gutted and rebased to cros_build_lib.
73def _DoWeHaveLocalCommits(stable_branch, tracking_branch, cwd):
Chris Sosadad0d322011-01-31 16:37:33 -080074 """Returns true if there are local commits."""
David James97d95872012-11-16 15:09:56 -080075 current_branch = git.GetCurrentBranch(cwd)
Brian Harring609dc4e2012-05-07 02:17:44 -070076
77 if current_branch != stable_branch:
Chris Sosadad0d322011-01-31 16:37:33 -080078 return False
David James97d95872012-11-16 15:09:56 -080079 output = git.RunGit(
Brian Harring609dc4e2012-05-07 02:17:44 -070080 cwd, ['rev-parse', 'HEAD', tracking_branch]).output.split()
Brian Harring5b86b5e2012-05-25 18:27:40 -070081 return output[0] != output[1]
Chris Sosadad0d322011-01-31 16:37:33 -080082
83
David James1b363582012-12-17 11:53:11 -080084def _CheckSaneArguments(command, options):
Chris Sosadad0d322011-01-31 16:37:33 -080085 """Checks to make sure the flags are sane. Dies if arguments are not sane."""
86 if not command in COMMAND_DICTIONARY.keys():
87 _PrintUsageAndDie('%s is not a valid command' % command)
Chris Sosa62ad8522011-03-08 17:46:17 -080088 if not options.packages and command == 'commit' and not options.all:
Chris Sosadad0d322011-01-31 16:37:33 -080089 _PrintUsageAndDie('Please specify at least one package')
Mike Frysinger8fd67dc2012-12-03 23:51:18 -050090 if options.boards:
91 cros_build_lib.AssertInsideChroot()
Chris Sosa62ad8522011-03-08 17:46:17 -080092 if not os.path.isdir(options.srcroot):
Chris Sosadad0d322011-01-31 16:37:33 -080093 _PrintUsageAndDie('srcroot is not a valid path')
Chris Sosa62ad8522011-03-08 17:46:17 -080094 options.srcroot = os.path.abspath(options.srcroot)
Chris Sosadad0d322011-01-31 16:37:33 -080095
96
97def _PrintUsageAndDie(error_message=''):
98 """Prints optional error_message the usage and returns an error exit code."""
99 command_usage = 'Commands: \n'
100 # Add keys and usage information from dictionary.
101 commands = sorted(COMMAND_DICTIONARY.keys())
102 for command in commands:
103 command_usage += ' %s: %s\n' % (command, COMMAND_DICTIONARY[command])
104 commands_str = '|'.join(commands)
Chris Sosac13bba52011-05-24 15:14:09 -0700105 cros_build_lib.Warning('Usage: %s FLAGS [%s]\n\n%s' % (
106 sys.argv[0], commands_str, command_usage))
Chris Sosadad0d322011-01-31 16:37:33 -0800107 if error_message:
Chris Sosac13bba52011-05-24 15:14:09 -0700108 cros_build_lib.Die(error_message)
Chris Sosadad0d322011-01-31 16:37:33 -0800109 else:
110 sys.exit(1)
111
112
Chris Sosadad0d322011-01-31 16:37:33 -0800113# ======================= End Global Helper Functions ========================
114
115
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400116def PushChange(stable_branch, tracking_branch, dryrun, cwd):
Chris Sosadad0d322011-01-31 16:37:33 -0800117 """Pushes commits in the stable_branch to the remote git repository.
118
David Jamesee2da622012-02-23 09:32:16 -0800119 Pushes local commits from calls to CommitChange to the remote git
David James66009462012-03-25 10:08:38 -0700120 repository specified by current working directory. If changes are
121 found to commit, they will be merged to the merge branch and pushed.
122 In that case, the local repository will be left on the merge branch.
Chris Sosadad0d322011-01-31 16:37:33 -0800123
124 Args:
125 stable_branch: The local branch with commits we want to push.
126 tracking_branch: The tracking branch of the local branch.
Chris Sosa62ad8522011-03-08 17:46:17 -0800127 dryrun: Use git push --dryrun to emulate a push.
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400128 cwd: The directory to run commands in.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500129
Chris Sosadad0d322011-01-31 16:37:33 -0800130 Raises:
Mike Frysinger5cd8c742013-10-11 14:43:01 -0400131 OSError: Error occurred while pushing.
Chris Sosadad0d322011-01-31 16:37:33 -0800132 """
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400133 if not _DoWeHaveLocalCommits(stable_branch, tracking_branch, cwd):
Brian Harring609dc4e2012-05-07 02:17:44 -0700134 cros_build_lib.Info('No work found to push in %s. Exiting', cwd)
Chris Sosadad0d322011-01-31 16:37:33 -0800135 return
136
David James66009462012-03-25 10:08:38 -0700137 # For the commit queue, our local branch may contain commits that were
138 # just tested and pushed during the CommitQueueCompletion stage. Sync
139 # and rebase our local branch on top of the remote commits.
David James97d95872012-11-16 15:09:56 -0800140 remote, push_branch = git.GetTrackingBranch(cwd, for_push=True)
141 git.SyncPushBranch(cwd, remote, push_branch)
David James66009462012-03-25 10:08:38 -0700142
143 # Check whether any local changes remain after the sync.
Brian Harring609dc4e2012-05-07 02:17:44 -0700144 if not _DoWeHaveLocalCommits(stable_branch, push_branch, cwd):
145 cros_build_lib.Info('All changes already pushed for %s. Exiting', cwd)
David James66009462012-03-25 10:08:38 -0700146 return
147
Matt Tennantcb522052013-11-25 14:23:43 -0800148 # Add a failsafe check here. Only CLs from the 'chrome-bot' user should
149 # be involved here. If any other CLs are found then complain.
150 # In dryruns extra CLs are normal, though, and can be ignored.
151 bad_cl_cmd = ['log', '--format=short', '--perl-regexp',
152 '--author', '^(?!chrome-bot)', '%s..%s' % (
153 push_branch, stable_branch)]
154 bad_cls = git.RunGit(cwd, bad_cl_cmd).output
155 if bad_cls.strip() and not dryrun:
156 cros_build_lib.Error('The Uprev stage found changes from users other'
157 ' than chrome-bot:\n\n%s', bad_cls)
158 raise AssertionError('Unexpected CLs found during uprev stage.')
159
David James67d73252013-09-19 17:33:12 -0700160 description = git.RunGit(cwd,
161 ['log', '--format=format:%s%n%n%b', '%s..%s' % (
162 push_branch, stable_branch)]).output
Chris Sosadad0d322011-01-31 16:37:33 -0800163 description = 'Marking set of ebuilds as stable\n\n%s' % description
Brian Harring609dc4e2012-05-07 02:17:44 -0700164 cros_build_lib.Info('For %s, using description %s', cwd, description)
David James97d95872012-11-16 15:09:56 -0800165 git.CreatePushBranch(constants.MERGE_BRANCH, cwd)
166 git.RunGit(cwd, ['merge', '--squash', stable_branch])
167 git.RunGit(cwd, ['commit', '-m', description])
168 git.RunGit(cwd, ['config', 'push.default', 'tracking'])
169 git.PushWithRetry(constants.MERGE_BRANCH, cwd, dryrun=dryrun)
Chris Sosadad0d322011-01-31 16:37:33 -0800170
171
172class GitBranch(object):
173 """Wrapper class for a git branch."""
174
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400175 def __init__(self, branch_name, tracking_branch, cwd):
David Jamesc7c4ff52013-09-18 17:57:13 -0700176 """Sets up variables but does not create the branch.
177
Mike Frysinger5cd8c742013-10-11 14:43:01 -0400178 Args:
David Jamesc7c4ff52013-09-18 17:57:13 -0700179 branch_name: The name of the branch.
180 tracking_branch: The associated tracking branch.
181 cwd: The git repository to work in.
182 """
Chris Sosadad0d322011-01-31 16:37:33 -0800183 self.branch_name = branch_name
184 self.tracking_branch = tracking_branch
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400185 self.cwd = cwd
Chris Sosadad0d322011-01-31 16:37:33 -0800186
187 def CreateBranch(self):
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400188 self.Checkout()
Chris Sosadad0d322011-01-31 16:37:33 -0800189
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400190 def Checkout(self, branch=None):
Chris Sosadad0d322011-01-31 16:37:33 -0800191 """Function used to check out to another GitBranch."""
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400192 if not branch:
193 branch = self.branch_name
194 if branch == self.tracking_branch or self.Exists(branch):
195 git_cmd = ['git', 'checkout', '-f', branch]
Chris Sosadad0d322011-01-31 16:37:33 -0800196 else:
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400197 git_cmd = ['repo', 'start', branch, '.']
Yu-Ju Hong3add4432014-01-30 11:46:15 -0800198 cros_build_lib.RunCommand(git_cmd, print_cmd=False, cwd=self.cwd,
199 capture_output=True)
Chris Sosadad0d322011-01-31 16:37:33 -0800200
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400201 def Exists(self, branch=None):
Chris Sosadad0d322011-01-31 16:37:33 -0800202 """Returns True if the branch exists."""
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400203 if not branch:
204 branch = self.branch_name
David James67d73252013-09-19 17:33:12 -0700205 branches = git.RunGit(self.cwd, ['branch']).output
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400206 return branch in branches.split()
Chris Sosadad0d322011-01-31 16:37:33 -0800207
208
David James1b363582012-12-17 11:53:11 -0800209def main(_argv):
Chris Sosa62ad8522011-03-08 17:46:17 -0800210 parser = optparse.OptionParser('cros_mark_as_stable OPTIONS packages')
211 parser.add_option('--all', action='store_true',
212 help='Mark all packages as stable.')
Mike Frysinger587bd562012-11-19 16:41:39 -0500213 parser.add_option('-b', '--boards', default='',
David Jamescc09c9b2012-01-26 22:10:13 -0800214 help='Colon-separated list of boards')
Chris Sosa62ad8522011-03-08 17:46:17 -0800215 parser.add_option('--drop_file',
216 help='File to list packages that were revved.')
217 parser.add_option('--dryrun', action='store_true',
218 help='Passes dry-run to git push if pushing a change.')
219 parser.add_option('-o', '--overlays',
220 help='Colon-separated list of overlays to modify.')
221 parser.add_option('-p', '--packages',
222 help='Colon separated list of packages to rev.')
223 parser.add_option('-r', '--srcroot',
Mike Frysingerfddaeb52012-11-20 11:17:31 -0500224 default=os.path.join(constants.SOURCE_ROOT, 'src'),
Chris Sosa62ad8522011-03-08 17:46:17 -0800225 help='Path to root src directory.')
Chris Sosa62ad8522011-03-08 17:46:17 -0800226 parser.add_option('--verbose', action='store_true',
227 help='Prints out debug info.')
228 (options, args) = parser.parse_args()
Chris Sosadad0d322011-01-31 16:37:33 -0800229
Alex Deymo075c2292014-09-04 18:31:50 -0700230 portage_util.EBuild.VERBOSE = options.verbose
Chris Sosa62ad8522011-03-08 17:46:17 -0800231
232 if len(args) != 1:
Ryan Cui05a31ba2011-05-31 17:47:37 -0700233 _PrintUsageAndDie('Must specify a valid command [commit, push]')
Chris Sosa62ad8522011-03-08 17:46:17 -0800234
235 command = args[0]
236 package_list = None
237 if options.packages:
238 package_list = options.packages.split(':')
239
David James1b363582012-12-17 11:53:11 -0800240 _CheckSaneArguments(command, options)
Chris Sosa62ad8522011-03-08 17:46:17 -0800241 if options.overlays:
Chris Sosadad0d322011-01-31 16:37:33 -0800242 overlays = {}
Chris Sosa62ad8522011-03-08 17:46:17 -0800243 for path in options.overlays.split(':'):
Ryan Cui05a31ba2011-05-31 17:47:37 -0700244 if not os.path.isdir(path):
Chris Sosac13bba52011-05-24 15:14:09 -0700245 cros_build_lib.Die('Cannot find overlay: %s' % path)
Chris Sosadad0d322011-01-31 16:37:33 -0800246 overlays[path] = []
247 else:
Chris Sosac13bba52011-05-24 15:14:09 -0700248 cros_build_lib.Warning('Missing --overlays argument')
Chris Sosadad0d322011-01-31 16:37:33 -0800249 overlays = {
Chris Sosa62ad8522011-03-08 17:46:17 -0800250 '%s/private-overlays/chromeos-overlay' % options.srcroot: [],
251 '%s/third_party/chromiumos-overlay' % options.srcroot: []
Chris Sosadad0d322011-01-31 16:37:33 -0800252 }
253
David James97d95872012-11-16 15:09:56 -0800254 manifest = git.ManifestCheckout.Cached(options.srcroot)
Ryan Cui4656a3c2011-05-24 12:30:30 -0700255
David James84e953c2013-04-23 18:44:06 -0700256 if command == 'commit':
Alex Deymo075c2292014-09-04 18:31:50 -0700257 portage_util.BuildEBuildDictionary(overlays, options.all, package_list)
David James84e953c2013-04-23 18:44:06 -0700258
David James15ed1302013-04-25 09:21:19 -0700259 # Contains the array of packages we actually revved.
260 revved_packages = []
261 new_package_atoms = []
David Jamesa8457b52011-05-28 00:03:20 -0700262
David James15ed1302013-04-25 09:21:19 -0700263 # Slight optimization hack: process the chromiumos overlay before any other
264 # cros-workon overlay first so we can do background cache generation in it.
265 # A perfect solution would walk all the overlays, figure out any dependencies
266 # between them (with layout.conf), and then process them in dependency order.
267 # However, this operation isn't slow enough to warrant that level of
268 # complexity, so we'll just special case the main overlay.
269 #
270 # Similarly, generate the cache in the portage-stable tree asap. We know
271 # we won't have any cros-workon packages in there, so generating the cache
272 # is the only thing it'll be doing. The chromiumos overlay instead might
273 # have revbumping to do before it can generate the cache.
274 keys = overlays.keys()
275 for overlay in ('/third_party/chromiumos-overlay',
276 '/third_party/portage-stable'):
277 for k in keys:
278 if k.endswith(overlay):
279 keys.remove(k)
280 keys.insert(0, k)
281 break
Chris Sosadad0d322011-01-31 16:37:33 -0800282
Alex Deymo075c2292014-09-04 18:31:50 -0700283 with parallel.BackgroundTaskRunner(portage_util.RegenCache) as queue:
David James15ed1302013-04-25 09:21:19 -0700284 for overlay in keys:
285 ebuilds = overlays[overlay]
286 if not os.path.isdir(overlay):
Mike Frysinger5cd8c742013-10-11 14:43:01 -0400287 cros_build_lib.Warning('Skipping %s' % overlay)
David James15ed1302013-04-25 09:21:19 -0700288 continue
Chris Sosadad0d322011-01-31 16:37:33 -0800289
David James15ed1302013-04-25 09:21:19 -0700290 # Note we intentionally work from the non push tracking branch;
291 # everything built thus far has been against it (meaning, http mirrors),
292 # thus we should honor that. During the actual push, the code switches
293 # to the correct urls, and does an appropriate rebasing.
294 tracking_branch = git.GetTrackingBranchViaManifest(
295 overlay, manifest=manifest)[1]
Brian Harring609dc4e2012-05-07 02:17:44 -0700296
David James15ed1302013-04-25 09:21:19 -0700297 if command == 'push':
298 PushChange(constants.STABLE_EBUILD_BRANCH, tracking_branch,
299 options.dryrun, cwd=overlay)
300 elif command == 'commit':
David James98b77f52013-11-19 10:11:56 -0800301 existing_commit = git.GetGitRepoRevision(overlay)
David James15ed1302013-04-25 09:21:19 -0700302 work_branch = GitBranch(constants.STABLE_EBUILD_BRANCH, tracking_branch,
303 cwd=overlay)
304 work_branch.CreateBranch()
305 if not work_branch.Exists():
306 cros_build_lib.Die('Unable to create stabilizing branch in %s' %
307 overlay)
308
309 # In the case of uprevving overlays that have patches applied to them,
310 # include the patched changes in the stabilizing branch.
David James98b77f52013-11-19 10:11:56 -0800311 git.RunGit(overlay, ['rebase', existing_commit])
David James15ed1302013-04-25 09:21:19 -0700312
313 messages = []
314 for ebuild in ebuilds:
315 if options.verbose:
316 cros_build_lib.Info('Working on %s', ebuild.package)
317 try:
318 new_package = ebuild.RevWorkOnEBuild(options.srcroot, manifest)
319 if new_package:
320 revved_packages.append(ebuild.package)
321 new_package_atoms.append('=%s' % new_package)
322 messages.append(_GIT_COMMIT_MESSAGE % ebuild.package)
323 except (OSError, IOError):
Mike Frysinger5cd8c742013-10-11 14:43:01 -0400324 cros_build_lib.Warning(
325 'Cannot rev %s\n'
326 'Note you will have to go into %s '
327 'and reset the git repo yourself.' % (ebuild.package, overlay))
David James15ed1302013-04-25 09:21:19 -0700328 raise
329
330 if messages:
Alex Deymo075c2292014-09-04 18:31:50 -0700331 portage_util.EBuild.CommitChange('\n\n'.join(messages), overlay)
David James15ed1302013-04-25 09:21:19 -0700332
333 if cros_build_lib.IsInsideChroot():
334 # Regenerate caches if need be. We do this all the time to
335 # catch when users make changes without updating cache files.
336 queue.put([overlay])
337
338 if command == 'commit':
339 if cros_build_lib.IsInsideChroot():
340 CleanStalePackages(options.boards.split(':'), new_package_atoms)
341 if options.drop_file:
342 osutils.WriteFile(options.drop_file, ' '.join(revved_packages))