blob: f9e1dbae50b684317206081d9194361365725533 [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
Mike Frysinger110750a2012-03-26 14:19:20 -04009import multiprocessing
Chris Sosa62ad8522011-03-08 17:46:17 -080010import optparse
Chris Sosadad0d322011-01-31 16:37:33 -080011import os
Chris Sosadad0d322011-01-31 16:37:33 -080012import sys
13
Mike Frysinger6cb624a2012-05-24 18:17:38 -040014from chromite.buildbot import constants
Mike Frysingerb7ab9b82012-04-04 16:22:43 -040015from chromite.buildbot import cbuildbot_background as background
David James66009462012-03-25 10:08:38 -070016from chromite.buildbot import portage_utilities
Chris Sosac13bba52011-05-24 15:14:09 -070017from chromite.lib import cros_build_lib
18
Chris Sosadad0d322011-01-31 16:37:33 -080019
Chris Sosa62ad8522011-03-08 17:46:17 -080020# TODO(sosa): Remove during OO refactor.
21VERBOSE = False
Chris Sosadad0d322011-01-31 16:37:33 -080022
Chris Sosadad0d322011-01-31 16:37:33 -080023# Dictionary of valid commands with usage information.
24COMMAND_DICTIONARY = {
Chris Sosadad0d322011-01-31 16:37:33 -080025 'commit':
26 'Marks given ebuilds as stable locally',
27 'push':
28 'Pushes previous marking of ebuilds to remote repo',
29 }
30
Chris Sosadad0d322011-01-31 16:37:33 -080031
Chris Sosadad0d322011-01-31 16:37:33 -080032# ======================= Global Helper Functions ========================
33
34
35def _Print(message):
36 """Verbose print function."""
Chris Sosac31bd2d2011-04-29 17:53:35 -070037 if VERBOSE:
Chris Sosac13bba52011-05-24 15:14:09 -070038 cros_build_lib.Info(message)
Chris Sosadad0d322011-01-31 16:37:33 -080039
40
David Jamescc09c9b2012-01-26 22:10:13 -080041def CleanStalePackages(boards, package_atoms):
Chris Sosabf153872011-04-28 14:21:09 -070042 """Cleans up stale package info from a previous build.
43 Args:
David Jamescc09c9b2012-01-26 22:10:13 -080044 boards: Boards to clean the packages from.
Chris Sosabf153872011-04-28 14:21:09 -070045 package_atoms: The actual package atom to unmerge.
46 """
47 if package_atoms:
Chris Sosac13bba52011-05-24 15:14:09 -070048 cros_build_lib.Info('Cleaning up stale packages %s.' % package_atoms)
Chris Sosadad0d322011-01-31 16:37:33 -080049
Mike Frysingerb7ab9b82012-04-04 16:22:43 -040050 # First unmerge all the packages for a board, then eclean it.
51 # We need these two steps to run in order (unmerge/eclean),
52 # but we can let all the boards run in parallel.
53 def _CleanStalePackages(board):
54 if board:
55 suffix = '-' + board
56 runcmd = cros_build_lib.RunCommand
57 else:
58 suffix = ''
59 runcmd = cros_build_lib.SudoRunCommand
Chris Sosadad0d322011-01-31 16:37:33 -080060
Mike Frysingerb7ab9b82012-04-04 16:22:43 -040061 if package_atoms:
Mike Frysinger0a647fc2012-08-06 14:36:05 -040062 runcmd(['emerge' + suffix, '-q', '--unmerge'] + package_atoms,
63 extra_env={'CLEAN_DELAY': '0'})
Mike Frysingerb7ab9b82012-04-04 16:22:43 -040064 runcmd(['eclean' + suffix, '-d', 'packages'],
65 redirect_stdout=True, redirect_stderr=True)
66
67 tasks = []
David Jamescc09c9b2012-01-26 22:10:13 -080068 for board in boards:
Mike Frysingerb7ab9b82012-04-04 16:22:43 -040069 tasks.append([board])
70 tasks.append([None])
71
72 background.RunTasksInProcessPool(_CleanStalePackages, tasks)
Chris Sosadad0d322011-01-31 16:37:33 -080073
74
Mike Frysinger2ebe3732012-05-08 17:04:12 -040075# TODO(build): This code needs to be gutted and rebased to cros_build_lib.
76def _DoWeHaveLocalCommits(stable_branch, tracking_branch, cwd):
Chris Sosadad0d322011-01-31 16:37:33 -080077 """Returns true if there are local commits."""
Brian Harring609dc4e2012-05-07 02:17:44 -070078 current_branch = cros_build_lib.GetCurrentBranch(cwd)
79
80 if current_branch != stable_branch:
Chris Sosadad0d322011-01-31 16:37:33 -080081 return False
Brian Harring609dc4e2012-05-07 02:17:44 -070082 output = cros_build_lib.RunGitCommand(
83 cwd, ['rev-parse', 'HEAD', tracking_branch]).output.split()
Brian Harring5b86b5e2012-05-25 18:27:40 -070084 return output[0] != output[1]
Chris Sosadad0d322011-01-31 16:37:33 -080085
86
Chris Sosa62ad8522011-03-08 17:46:17 -080087def _CheckSaneArguments(package_list, command, options):
Chris Sosadad0d322011-01-31 16:37:33 -080088 """Checks to make sure the flags are sane. Dies if arguments are not sane."""
89 if not command in COMMAND_DICTIONARY.keys():
90 _PrintUsageAndDie('%s is not a valid command' % command)
Chris Sosa62ad8522011-03-08 17:46:17 -080091 if not options.packages and command == 'commit' and not options.all:
Chris Sosadad0d322011-01-31 16:37:33 -080092 _PrintUsageAndDie('Please specify at least one package')
David Jamescc09c9b2012-01-26 22:10:13 -080093 if not options.boards and command == 'commit':
Chris Sosadad0d322011-01-31 16:37:33 -080094 _PrintUsageAndDie('Please specify a board')
Chris Sosa62ad8522011-03-08 17:46:17 -080095 if not os.path.isdir(options.srcroot):
Chris Sosadad0d322011-01-31 16:37:33 -080096 _PrintUsageAndDie('srcroot is not a valid path')
Chris Sosa62ad8522011-03-08 17:46:17 -080097 options.srcroot = os.path.abspath(options.srcroot)
Chris Sosadad0d322011-01-31 16:37:33 -080098
99
100def _PrintUsageAndDie(error_message=''):
101 """Prints optional error_message the usage and returns an error exit code."""
102 command_usage = 'Commands: \n'
103 # Add keys and usage information from dictionary.
104 commands = sorted(COMMAND_DICTIONARY.keys())
105 for command in commands:
106 command_usage += ' %s: %s\n' % (command, COMMAND_DICTIONARY[command])
107 commands_str = '|'.join(commands)
Chris Sosac13bba52011-05-24 15:14:09 -0700108 cros_build_lib.Warning('Usage: %s FLAGS [%s]\n\n%s' % (
109 sys.argv[0], commands_str, command_usage))
Chris Sosadad0d322011-01-31 16:37:33 -0800110 if error_message:
Chris Sosac13bba52011-05-24 15:14:09 -0700111 cros_build_lib.Die(error_message)
Chris Sosadad0d322011-01-31 16:37:33 -0800112 else:
113 sys.exit(1)
114
115
Chris Sosadad0d322011-01-31 16:37:33 -0800116# ======================= End Global Helper Functions ========================
117
118
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400119def PushChange(stable_branch, tracking_branch, dryrun, cwd):
Chris Sosadad0d322011-01-31 16:37:33 -0800120 """Pushes commits in the stable_branch to the remote git repository.
121
David Jamesee2da622012-02-23 09:32:16 -0800122 Pushes local commits from calls to CommitChange to the remote git
David James66009462012-03-25 10:08:38 -0700123 repository specified by current working directory. If changes are
124 found to commit, they will be merged to the merge branch and pushed.
125 In that case, the local repository will be left on the merge branch.
Chris Sosadad0d322011-01-31 16:37:33 -0800126
127 Args:
128 stable_branch: The local branch with commits we want to push.
129 tracking_branch: The tracking branch of the local branch.
Chris Sosa62ad8522011-03-08 17:46:17 -0800130 dryrun: Use git push --dryrun to emulate a push.
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400131 cwd: The directory to run commands in.
Chris Sosadad0d322011-01-31 16:37:33 -0800132 Raises:
133 OSError: Error occurred while pushing.
134 """
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400135 if not _DoWeHaveLocalCommits(stable_branch, tracking_branch, cwd):
Brian Harring609dc4e2012-05-07 02:17:44 -0700136 cros_build_lib.Info('No work found to push in %s. Exiting', cwd)
Chris Sosadad0d322011-01-31 16:37:33 -0800137 return
138
David James66009462012-03-25 10:08:38 -0700139 # For the commit queue, our local branch may contain commits that were
140 # just tested and pushed during the CommitQueueCompletion stage. Sync
141 # and rebase our local branch on top of the remote commits.
Brian Harring609dc4e2012-05-07 02:17:44 -0700142 remote, push_branch = cros_build_lib.GetTrackingBranch(cwd, for_push=True)
David James66009462012-03-25 10:08:38 -0700143 cros_build_lib.SyncPushBranch(cwd, remote, push_branch)
144
145 # Check whether any local changes remain after the sync.
Brian Harring609dc4e2012-05-07 02:17:44 -0700146 if not _DoWeHaveLocalCommits(stable_branch, push_branch, cwd):
147 cros_build_lib.Info('All changes already pushed for %s. Exiting', cwd)
David James66009462012-03-25 10:08:38 -0700148 return
149
Mike Frysinger7dafd0e2012-05-08 15:47:16 -0400150 description = cros_build_lib.RunCommandCaptureOutput(
Brian Harring609dc4e2012-05-07 02:17:44 -0700151 ['git', 'log', '--format=format:%s%n%n%b', '%s..%s' % (
152 push_branch, stable_branch)], cwd=cwd).output
Chris Sosadad0d322011-01-31 16:37:33 -0800153 description = 'Marking set of ebuilds as stable\n\n%s' % description
Brian Harring609dc4e2012-05-07 02:17:44 -0700154 cros_build_lib.Info('For %s, using description %s', cwd, description)
David James66009462012-03-25 10:08:38 -0700155 cros_build_lib.CreatePushBranch(constants.MERGE_BRANCH, cwd)
Brian Harring609dc4e2012-05-07 02:17:44 -0700156 cros_build_lib.RunGitCommand(cwd, ['merge', '--squash', stable_branch])
157 cros_build_lib.RunGitCommand(cwd, ['commit', '-m', description])
158 cros_build_lib.RunGitCommand(cwd, ['config', 'push.default', 'tracking'])
159 cros_build_lib.GitPushWithRetry(constants.MERGE_BRANCH, cwd,
Ryan Cui05a31ba2011-05-31 17:47:37 -0700160 dryrun=dryrun)
Chris Sosadad0d322011-01-31 16:37:33 -0800161
162
163class GitBranch(object):
164 """Wrapper class for a git branch."""
165
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400166 def __init__(self, branch_name, tracking_branch, cwd):
Chris Sosadad0d322011-01-31 16:37:33 -0800167 """Sets up variables but does not create the branch."""
168 self.branch_name = branch_name
169 self.tracking_branch = tracking_branch
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400170 self.cwd = cwd
Chris Sosadad0d322011-01-31 16:37:33 -0800171
172 def CreateBranch(self):
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400173 self.Checkout()
Chris Sosadad0d322011-01-31 16:37:33 -0800174
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400175 def Checkout(self, branch=None):
Chris Sosadad0d322011-01-31 16:37:33 -0800176 """Function used to check out to another GitBranch."""
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400177 if not branch:
178 branch = self.branch_name
179 if branch == self.tracking_branch or self.Exists(branch):
180 git_cmd = ['git', 'checkout', '-f', branch]
Chris Sosadad0d322011-01-31 16:37:33 -0800181 else:
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400182 git_cmd = ['repo', 'start', branch, '.']
183 cros_build_lib.RunCommandCaptureOutput(git_cmd, print_cmd=False,
184 cwd=self.cwd)
Chris Sosadad0d322011-01-31 16:37:33 -0800185
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400186 def Exists(self, branch=None):
Chris Sosadad0d322011-01-31 16:37:33 -0800187 """Returns True if the branch exists."""
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400188 if not branch:
189 branch = self.branch_name
Mike Frysinger7dafd0e2012-05-08 15:47:16 -0400190 branches = cros_build_lib.RunCommandCaptureOutput(['git', 'branch'],
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400191 print_cmd=False,
192 cwd=self.cwd).output
193 return branch in branches.split()
Chris Sosadad0d322011-01-31 16:37:33 -0800194
195
Mike Frysinger368bbb72012-05-23 15:57:10 -0400196def main(argv):
Chris Sosa62ad8522011-03-08 17:46:17 -0800197 parser = optparse.OptionParser('cros_mark_as_stable OPTIONS packages')
198 parser.add_option('--all', action='store_true',
199 help='Mark all packages as stable.')
David Jamescc09c9b2012-01-26 22:10:13 -0800200 parser.add_option('-b', '--boards',
201 help='Colon-separated list of boards')
Chris Sosa62ad8522011-03-08 17:46:17 -0800202 parser.add_option('--drop_file',
203 help='File to list packages that were revved.')
204 parser.add_option('--dryrun', action='store_true',
205 help='Passes dry-run to git push if pushing a change.')
206 parser.add_option('-o', '--overlays',
207 help='Colon-separated list of overlays to modify.')
208 parser.add_option('-p', '--packages',
209 help='Colon separated list of packages to rev.')
210 parser.add_option('-r', '--srcroot',
211 default='%s/trunk/src' % os.environ['HOME'],
212 help='Path to root src directory.')
Chris Sosa62ad8522011-03-08 17:46:17 -0800213 parser.add_option('--verbose', action='store_true',
214 help='Prints out debug info.')
215 (options, args) = parser.parse_args()
Chris Sosadad0d322011-01-31 16:37:33 -0800216
Chris Sosa62ad8522011-03-08 17:46:17 -0800217 global VERBOSE
218 VERBOSE = options.verbose
J. Richard Barnette2fa9fd62011-12-02 17:10:10 -0800219 portage_utilities.EBuild.VERBOSE = options.verbose
Chris Sosa62ad8522011-03-08 17:46:17 -0800220
221 if len(args) != 1:
Ryan Cui05a31ba2011-05-31 17:47:37 -0700222 _PrintUsageAndDie('Must specify a valid command [commit, push]')
Chris Sosa62ad8522011-03-08 17:46:17 -0800223
224 command = args[0]
225 package_list = None
226 if options.packages:
227 package_list = options.packages.split(':')
228
229 _CheckSaneArguments(package_list, command, options)
230 if options.overlays:
Chris Sosadad0d322011-01-31 16:37:33 -0800231 overlays = {}
Chris Sosa62ad8522011-03-08 17:46:17 -0800232 for path in options.overlays.split(':'):
Ryan Cui05a31ba2011-05-31 17:47:37 -0700233 if not os.path.isdir(path):
Chris Sosac13bba52011-05-24 15:14:09 -0700234 cros_build_lib.Die('Cannot find overlay: %s' % path)
Chris Sosadad0d322011-01-31 16:37:33 -0800235 overlays[path] = []
236 else:
Chris Sosac13bba52011-05-24 15:14:09 -0700237 cros_build_lib.Warning('Missing --overlays argument')
Chris Sosadad0d322011-01-31 16:37:33 -0800238 overlays = {
Chris Sosa62ad8522011-03-08 17:46:17 -0800239 '%s/private-overlays/chromeos-overlay' % options.srcroot: [],
240 '%s/third_party/chromiumos-overlay' % options.srcroot: []
Chris Sosadad0d322011-01-31 16:37:33 -0800241 }
242
243 if command == 'commit':
J. Richard Barnettef6697cf2011-11-18 12:42:08 -0800244 portage_utilities.BuildEBuildDictionary(
J. Richard Barnetted422f622011-11-17 09:39:46 -0800245 overlays, options.all, package_list)
Chris Sosadad0d322011-01-31 16:37:33 -0800246
Brian Harring609dc4e2012-05-07 02:17:44 -0700247 manifest = cros_build_lib.ManifestCheckout.Cached(options.srcroot)
Ryan Cui4656a3c2011-05-24 12:30:30 -0700248
David Jamesa8457b52011-05-28 00:03:20 -0700249 # Contains the array of packages we actually revved.
250 revved_packages = []
251 new_package_atoms = []
252
Mike Frysingerb9bd2f82012-05-08 14:55:23 -0400253 # Slight optimization hack: process the chromiumos overlay before any other
254 # cros-workon overlay first so we can do background cache generation in it.
255 # A perfect solution would walk all the overlays, figure out any dependencies
256 # between them (with layout.conf), and then process them in dependency order.
257 # However, this operation isn't slow enough to warrant that level of
258 # complexity, so we'll just special case the main overlay.
259 #
260 # Similarly, generate the cache in the portage-stable tree asap. We know
261 # we won't have any cros-workon packages in there, so generating the cache
262 # is the only thing it'll be doing. The chromiumos overlay instead might
263 # have revbumping to do before it can generate the cache.
Mike Frysinger110750a2012-03-26 14:19:20 -0400264 keys = overlays.keys()
Mike Frysingerb9bd2f82012-05-08 14:55:23 -0400265 for overlay in ('/third_party/chromiumos-overlay',
266 '/third_party/portage-stable'):
267 for k in keys:
268 if k.endswith(overlay):
269 keys.remove(k)
270 keys.insert(0, k)
271 break
Chris Sosadad0d322011-01-31 16:37:33 -0800272
Mike Frysinger110750a2012-03-26 14:19:20 -0400273 cache_queue = multiprocessing.Queue()
274 with background.BackgroundTaskRunner(cache_queue,
275 portage_utilities.RegenCache):
276 for overlay in keys:
277 ebuilds = overlays[overlay]
278 if not os.path.isdir(overlay):
279 cros_build_lib.Warning("Skipping %s" % overlay)
280 continue
Chris Sosadad0d322011-01-31 16:37:33 -0800281
Brian Harringe08e4422012-05-30 12:40:50 -0700282 # Note we intentionally work from the non push tracking branch;
283 # everything built thus far has been against it (meaning, http mirrors),
284 # thus we should honor that. During the actual push, the code switches
285 # to the correct urls, and does an appropriate rebasing.
Brian Harring609dc4e2012-05-07 02:17:44 -0700286 tracking_branch = cros_build_lib.GetTrackingBranchViaManifest(
Brian Harringe08e4422012-05-30 12:40:50 -0700287 overlay, manifest=manifest)[1]
Brian Harring609dc4e2012-05-07 02:17:44 -0700288
Mike Frysinger110750a2012-03-26 14:19:20 -0400289 if command == 'push':
290 PushChange(constants.STABLE_EBUILD_BRANCH, tracking_branch,
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400291 options.dryrun, cwd=overlay)
Mike Frysinger303083d2012-07-16 14:57:24 -0400292 elif command == 'commit':
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400293 existing_branch = cros_build_lib.GetCurrentBranch(overlay)
294 work_branch = GitBranch(constants.STABLE_EBUILD_BRANCH, tracking_branch,
295 cwd=overlay)
Mike Frysinger110750a2012-03-26 14:19:20 -0400296 work_branch.CreateBranch()
297 if not work_branch.Exists():
298 cros_build_lib.Die('Unable to create stabilizing branch in %s' %
299 overlay)
Ryan Cuif0d57b42011-07-27 17:43:42 -0700300
Mike Frysinger110750a2012-03-26 14:19:20 -0400301 # In the case of uprevving overlays that have patches applied to them,
302 # include the patched changes in the stabilizing branch.
303 if existing_branch:
Mike Frysinger7dafd0e2012-05-08 15:47:16 -0400304 cros_build_lib.RunCommand(['git', 'rebase', existing_branch],
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400305 print_cmd=False, cwd=overlay)
Mike Frysinger110750a2012-03-26 14:19:20 -0400306
307 for ebuild in ebuilds:
308 try:
309 _Print('Working on %s' % ebuild.package)
310 new_package = ebuild.RevWorkOnEBuild(options.srcroot)
311 if new_package:
312 revved_packages.append(ebuild.package)
313 new_package_atoms.append('=%s' % new_package)
314 except (OSError, IOError):
315 cros_build_lib.Warning('Cannot rev %s\n' % ebuild.package +
316 'Note you will have to go into %s '
317 'and reset the git repo yourself.' % overlay)
318 raise
319
320 # Regenerate caches if need be. We do this all the time to
321 # catch when users make changes without updating cache files.
322 cache_queue.put([overlay])
Chris Sosadad0d322011-01-31 16:37:33 -0800323
David Jamesa8457b52011-05-28 00:03:20 -0700324 if command == 'commit':
David Jamescc09c9b2012-01-26 22:10:13 -0800325 CleanStalePackages(options.boards.split(':'), new_package_atoms)
David Jamesa8457b52011-05-28 00:03:20 -0700326 if options.drop_file:
327 fh = open(options.drop_file, 'w')
328 fh.write(' '.join(revved_packages))
329 fh.close()