blob: 0a7c837d8ab81aa1962019ff49eb3206c750b497 [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
David James66009462012-03-25 10:08:38 -070015from chromite.buildbot import portage_utilities
Chris Sosac13bba52011-05-24 15:14:09 -070016from chromite.lib import cros_build_lib
David James97d95872012-11-16 15:09:56 -080017from chromite.lib import git
Mike Frysingerfddaeb52012-11-20 11:17:31 -050018from chromite.lib import osutils
David James6450a0a2012-12-04 07:59:53 -080019from chromite.lib import parallel
Chris Sosac13bba52011-05-24 15:14:09 -070020
Chris Sosadad0d322011-01-31 16:37:33 -080021
Chris Sosa62ad8522011-03-08 17:46:17 -080022# TODO(sosa): Remove during OO refactor.
23VERBOSE = False
Chris Sosadad0d322011-01-31 16:37:33 -080024
Chris Sosadad0d322011-01-31 16:37:33 -080025# Dictionary of valid commands with usage information.
26COMMAND_DICTIONARY = {
Chris Sosadad0d322011-01-31 16:37:33 -080027 'commit':
28 'Marks given ebuilds as stable locally',
29 'push':
30 'Pushes previous marking of ebuilds to remote repo',
31 }
32
Chris Sosadad0d322011-01-31 16:37:33 -080033
Chris Sosadad0d322011-01-31 16:37:33 -080034# ======================= Global Helper Functions ========================
35
36
37def _Print(message):
38 """Verbose print function."""
Chris Sosac31bd2d2011-04-29 17:53:35 -070039 if VERBOSE:
Chris Sosac13bba52011-05-24 15:14:09 -070040 cros_build_lib.Info(message)
Chris Sosadad0d322011-01-31 16:37:33 -080041
42
David Jamescc09c9b2012-01-26 22:10:13 -080043def CleanStalePackages(boards, package_atoms):
Chris Sosabf153872011-04-28 14:21:09 -070044 """Cleans up stale package info from a previous build.
45 Args:
David Jamescc09c9b2012-01-26 22:10:13 -080046 boards: Boards to clean the packages from.
Chris Sosabf153872011-04-28 14:21:09 -070047 package_atoms: The actual package atom to unmerge.
48 """
49 if package_atoms:
Chris Sosac13bba52011-05-24 15:14:09 -070050 cros_build_lib.Info('Cleaning up stale packages %s.' % package_atoms)
Chris Sosadad0d322011-01-31 16:37:33 -080051
Mike Frysingerb7ab9b82012-04-04 16:22:43 -040052 # First unmerge all the packages for a board, then eclean it.
53 # We need these two steps to run in order (unmerge/eclean),
54 # but we can let all the boards run in parallel.
55 def _CleanStalePackages(board):
56 if board:
57 suffix = '-' + board
58 runcmd = cros_build_lib.RunCommand
59 else:
60 suffix = ''
61 runcmd = cros_build_lib.SudoRunCommand
Chris Sosadad0d322011-01-31 16:37:33 -080062
Mike Frysingerb7ab9b82012-04-04 16:22:43 -040063 if package_atoms:
Mike Frysinger0a647fc2012-08-06 14:36:05 -040064 runcmd(['emerge' + suffix, '-q', '--unmerge'] + package_atoms,
65 extra_env={'CLEAN_DELAY': '0'})
Mike Frysingerb7ab9b82012-04-04 16:22:43 -040066 runcmd(['eclean' + suffix, '-d', 'packages'],
67 redirect_stdout=True, redirect_stderr=True)
68
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
Chris Sosa62ad8522011-03-08 17:46:17 -080089def _CheckSaneArguments(package_list, 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.
Chris Sosadad0d322011-01-31 16:37:33 -0800134 Raises:
135 OSError: Error occurred while pushing.
136 """
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
Mike Frysinger7dafd0e2012-05-08 15:47:16 -0400152 description = cros_build_lib.RunCommandCaptureOutput(
Brian Harring609dc4e2012-05-07 02:17:44 -0700153 ['git', 'log', '--format=format:%s%n%n%b', '%s..%s' % (
154 push_branch, stable_branch)], cwd=cwd).output
Chris Sosadad0d322011-01-31 16:37:33 -0800155 description = 'Marking set of ebuilds as stable\n\n%s' % description
Brian Harring609dc4e2012-05-07 02:17:44 -0700156 cros_build_lib.Info('For %s, using description %s', cwd, description)
David James97d95872012-11-16 15:09:56 -0800157 git.CreatePushBranch(constants.MERGE_BRANCH, cwd)
158 git.RunGit(cwd, ['merge', '--squash', stable_branch])
159 git.RunGit(cwd, ['commit', '-m', description])
160 git.RunGit(cwd, ['config', 'push.default', 'tracking'])
161 git.PushWithRetry(constants.MERGE_BRANCH, cwd, dryrun=dryrun)
Chris Sosadad0d322011-01-31 16:37:33 -0800162
163
164class GitBranch(object):
165 """Wrapper class for a git branch."""
166
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400167 def __init__(self, branch_name, tracking_branch, cwd):
Chris Sosadad0d322011-01-31 16:37:33 -0800168 """Sets up variables but does not create the branch."""
169 self.branch_name = branch_name
170 self.tracking_branch = tracking_branch
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400171 self.cwd = cwd
Chris Sosadad0d322011-01-31 16:37:33 -0800172
173 def CreateBranch(self):
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400174 self.Checkout()
Chris Sosadad0d322011-01-31 16:37:33 -0800175
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400176 def Checkout(self, branch=None):
Chris Sosadad0d322011-01-31 16:37:33 -0800177 """Function used to check out to another GitBranch."""
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400178 if not branch:
179 branch = self.branch_name
180 if branch == self.tracking_branch or self.Exists(branch):
181 git_cmd = ['git', 'checkout', '-f', branch]
Chris Sosadad0d322011-01-31 16:37:33 -0800182 else:
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400183 git_cmd = ['repo', 'start', branch, '.']
184 cros_build_lib.RunCommandCaptureOutput(git_cmd, print_cmd=False,
185 cwd=self.cwd)
Chris Sosadad0d322011-01-31 16:37:33 -0800186
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400187 def Exists(self, branch=None):
Chris Sosadad0d322011-01-31 16:37:33 -0800188 """Returns True if the branch exists."""
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400189 if not branch:
190 branch = self.branch_name
Mike Frysinger7dafd0e2012-05-08 15:47:16 -0400191 branches = cros_build_lib.RunCommandCaptureOutput(['git', 'branch'],
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400192 print_cmd=False,
193 cwd=self.cwd).output
194 return branch in branches.split()
Chris Sosadad0d322011-01-31 16:37:33 -0800195
196
Mike Frysinger368bbb72012-05-23 15:57:10 -0400197def main(argv):
Chris Sosa62ad8522011-03-08 17:46:17 -0800198 parser = optparse.OptionParser('cros_mark_as_stable OPTIONS packages')
199 parser.add_option('--all', action='store_true',
200 help='Mark all packages as stable.')
Mike Frysinger587bd562012-11-19 16:41:39 -0500201 parser.add_option('-b', '--boards', default='',
David Jamescc09c9b2012-01-26 22:10:13 -0800202 help='Colon-separated list of boards')
Chris Sosa62ad8522011-03-08 17:46:17 -0800203 parser.add_option('--drop_file',
204 help='File to list packages that were revved.')
205 parser.add_option('--dryrun', action='store_true',
206 help='Passes dry-run to git push if pushing a change.')
207 parser.add_option('-o', '--overlays',
208 help='Colon-separated list of overlays to modify.')
209 parser.add_option('-p', '--packages',
210 help='Colon separated list of packages to rev.')
211 parser.add_option('-r', '--srcroot',
Mike Frysingerfddaeb52012-11-20 11:17:31 -0500212 default=os.path.join(constants.SOURCE_ROOT, 'src'),
Chris Sosa62ad8522011-03-08 17:46:17 -0800213 help='Path to root src directory.')
Chris Sosa62ad8522011-03-08 17:46:17 -0800214 parser.add_option('--verbose', action='store_true',
215 help='Prints out debug info.')
216 (options, args) = parser.parse_args()
Chris Sosadad0d322011-01-31 16:37:33 -0800217
Chris Sosa62ad8522011-03-08 17:46:17 -0800218 global VERBOSE
219 VERBOSE = options.verbose
J. Richard Barnette2fa9fd62011-12-02 17:10:10 -0800220 portage_utilities.EBuild.VERBOSE = options.verbose
Chris Sosa62ad8522011-03-08 17:46:17 -0800221
222 if len(args) != 1:
Ryan Cui05a31ba2011-05-31 17:47:37 -0700223 _PrintUsageAndDie('Must specify a valid command [commit, push]')
Chris Sosa62ad8522011-03-08 17:46:17 -0800224
225 command = args[0]
226 package_list = None
227 if options.packages:
228 package_list = options.packages.split(':')
229
230 _CheckSaneArguments(package_list, command, options)
231 if options.overlays:
Chris Sosadad0d322011-01-31 16:37:33 -0800232 overlays = {}
Chris Sosa62ad8522011-03-08 17:46:17 -0800233 for path in options.overlays.split(':'):
Ryan Cui05a31ba2011-05-31 17:47:37 -0700234 if not os.path.isdir(path):
Chris Sosac13bba52011-05-24 15:14:09 -0700235 cros_build_lib.Die('Cannot find overlay: %s' % path)
Chris Sosadad0d322011-01-31 16:37:33 -0800236 overlays[path] = []
237 else:
Chris Sosac13bba52011-05-24 15:14:09 -0700238 cros_build_lib.Warning('Missing --overlays argument')
Chris Sosadad0d322011-01-31 16:37:33 -0800239 overlays = {
Chris Sosa62ad8522011-03-08 17:46:17 -0800240 '%s/private-overlays/chromeos-overlay' % options.srcroot: [],
241 '%s/third_party/chromiumos-overlay' % options.srcroot: []
Chris Sosadad0d322011-01-31 16:37:33 -0800242 }
243
244 if command == 'commit':
J. Richard Barnettef6697cf2011-11-18 12:42:08 -0800245 portage_utilities.BuildEBuildDictionary(
J. Richard Barnetted422f622011-11-17 09:39:46 -0800246 overlays, options.all, package_list)
Chris Sosadad0d322011-01-31 16:37:33 -0800247
David James97d95872012-11-16 15:09:56 -0800248 manifest = git.ManifestCheckout.Cached(options.srcroot)
Ryan Cui4656a3c2011-05-24 12:30:30 -0700249
David Jamesa8457b52011-05-28 00:03:20 -0700250 # Contains the array of packages we actually revved.
251 revved_packages = []
252 new_package_atoms = []
253
Mike Frysingerb9bd2f82012-05-08 14:55:23 -0400254 # Slight optimization hack: process the chromiumos overlay before any other
255 # cros-workon overlay first so we can do background cache generation in it.
256 # A perfect solution would walk all the overlays, figure out any dependencies
257 # between them (with layout.conf), and then process them in dependency order.
258 # However, this operation isn't slow enough to warrant that level of
259 # complexity, so we'll just special case the main overlay.
260 #
261 # Similarly, generate the cache in the portage-stable tree asap. We know
262 # we won't have any cros-workon packages in there, so generating the cache
263 # is the only thing it'll be doing. The chromiumos overlay instead might
264 # have revbumping to do before it can generate the cache.
Mike Frysinger110750a2012-03-26 14:19:20 -0400265 keys = overlays.keys()
Mike Frysingerb9bd2f82012-05-08 14:55:23 -0400266 for overlay in ('/third_party/chromiumos-overlay',
267 '/third_party/portage-stable'):
268 for k in keys:
269 if k.endswith(overlay):
270 keys.remove(k)
271 keys.insert(0, k)
272 break
Chris Sosadad0d322011-01-31 16:37:33 -0800273
Brian Harring9fdd23d2012-12-07 12:09:08 -0800274 with parallel.BackgroundTaskRunner(portage_utilities.RegenCache) as queue:
Mike Frysinger110750a2012-03-26 14:19:20 -0400275 for overlay in keys:
276 ebuilds = overlays[overlay]
277 if not os.path.isdir(overlay):
278 cros_build_lib.Warning("Skipping %s" % overlay)
279 continue
Chris Sosadad0d322011-01-31 16:37:33 -0800280
Brian Harringe08e4422012-05-30 12:40:50 -0700281 # Note we intentionally work from the non push tracking branch;
282 # everything built thus far has been against it (meaning, http mirrors),
283 # thus we should honor that. During the actual push, the code switches
284 # to the correct urls, and does an appropriate rebasing.
David James97d95872012-11-16 15:09:56 -0800285 tracking_branch = git.GetTrackingBranchViaManifest(
Brian Harringe08e4422012-05-30 12:40:50 -0700286 overlay, manifest=manifest)[1]
Brian Harring609dc4e2012-05-07 02:17:44 -0700287
Mike Frysinger110750a2012-03-26 14:19:20 -0400288 if command == 'push':
289 PushChange(constants.STABLE_EBUILD_BRANCH, tracking_branch,
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400290 options.dryrun, cwd=overlay)
Mike Frysinger303083d2012-07-16 14:57:24 -0400291 elif command == 'commit':
David James97d95872012-11-16 15:09:56 -0800292 existing_branch = git.GetCurrentBranch(overlay)
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400293 work_branch = GitBranch(constants.STABLE_EBUILD_BRANCH, tracking_branch,
294 cwd=overlay)
Mike Frysinger110750a2012-03-26 14:19:20 -0400295 work_branch.CreateBranch()
296 if not work_branch.Exists():
297 cros_build_lib.Die('Unable to create stabilizing branch in %s' %
298 overlay)
Ryan Cuif0d57b42011-07-27 17:43:42 -0700299
Mike Frysinger110750a2012-03-26 14:19:20 -0400300 # In the case of uprevving overlays that have patches applied to them,
301 # include the patched changes in the stabilizing branch.
302 if existing_branch:
Mike Frysinger7dafd0e2012-05-08 15:47:16 -0400303 cros_build_lib.RunCommand(['git', 'rebase', existing_branch],
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400304 print_cmd=False, cwd=overlay)
Mike Frysinger110750a2012-03-26 14:19:20 -0400305
306 for ebuild in ebuilds:
307 try:
308 _Print('Working on %s' % ebuild.package)
309 new_package = ebuild.RevWorkOnEBuild(options.srcroot)
310 if new_package:
311 revved_packages.append(ebuild.package)
312 new_package_atoms.append('=%s' % new_package)
313 except (OSError, IOError):
314 cros_build_lib.Warning('Cannot rev %s\n' % ebuild.package +
315 'Note you will have to go into %s '
316 'and reset the git repo yourself.' % overlay)
317 raise
318
Mike Frysingerfddaeb52012-11-20 11:17:31 -0500319 if cros_build_lib.IsInsideChroot():
320 # Regenerate caches if need be. We do this all the time to
321 # catch when users make changes without updating cache files.
Brian Harring9fdd23d2012-12-07 12:09:08 -0800322 queue.put([overlay])
Chris Sosadad0d322011-01-31 16:37:33 -0800323
David Jamesa8457b52011-05-28 00:03:20 -0700324 if command == 'commit':
Mike Frysingerfddaeb52012-11-20 11:17:31 -0500325 if cros_build_lib.IsInsideChroot():
326 CleanStalePackages(options.boards.split(':'), new_package_atoms)
David Jamesa8457b52011-05-28 00:03:20 -0700327 if options.drop_file:
Mike Frysingerfddaeb52012-11-20 11:17:31 -0500328 osutils.WriteFile(options.drop_file, ' '.join(revved_packages))