blob: bb921489d4e46e342e4ba197516111cf963d1abf [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 Sosadad0d322011-01-31 16:37:33 -08009import os
Chris Sosadad0d322011-01-31 16:37:33 -080010
Don Garrett88b8d782014-05-13 17:30:55 -070011from chromite.cbuildbot import constants
Mike Frysingere8dcbfd2015-03-08 21:45:52 -040012from chromite.lib import commandline
Chris Sosac13bba52011-05-24 15:14:09 -070013from chromite.lib import cros_build_lib
Ralph Nathan03047282015-03-23 11:09:32 -070014from chromite.lib import cros_logging as logging
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:
Ralph Nathan03047282015-03-23 11:09:32 -070044 logging.info('Cleaning up stale packages %s.' % package_atoms)
David James15ed1302013-04-25 09:21:19 -070045
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
Chris Sosadad0d322011-01-31 16:37:33 -080088# ======================= End Global Helper Functions ========================
89
90
Mike Frysinger2ebe3732012-05-08 17:04:12 -040091def PushChange(stable_branch, tracking_branch, dryrun, cwd):
Chris Sosadad0d322011-01-31 16:37:33 -080092 """Pushes commits in the stable_branch to the remote git repository.
93
David Jamesee2da622012-02-23 09:32:16 -080094 Pushes local commits from calls to CommitChange to the remote git
David James66009462012-03-25 10:08:38 -070095 repository specified by current working directory. If changes are
96 found to commit, they will be merged to the merge branch and pushed.
97 In that case, the local repository will be left on the merge branch.
Chris Sosadad0d322011-01-31 16:37:33 -080098
99 Args:
100 stable_branch: The local branch with commits we want to push.
101 tracking_branch: The tracking branch of the local branch.
Chris Sosa62ad8522011-03-08 17:46:17 -0800102 dryrun: Use git push --dryrun to emulate a push.
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400103 cwd: The directory to run commands in.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500104
Chris Sosadad0d322011-01-31 16:37:33 -0800105 Raises:
Mike Frysinger5cd8c742013-10-11 14:43:01 -0400106 OSError: Error occurred while pushing.
Chris Sosadad0d322011-01-31 16:37:33 -0800107 """
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400108 if not _DoWeHaveLocalCommits(stable_branch, tracking_branch, cwd):
Ralph Nathan03047282015-03-23 11:09:32 -0700109 logging.info('No work found to push in %s. Exiting', cwd)
Chris Sosadad0d322011-01-31 16:37:33 -0800110 return
111
David James66009462012-03-25 10:08:38 -0700112 # For the commit queue, our local branch may contain commits that were
113 # just tested and pushed during the CommitQueueCompletion stage. Sync
114 # and rebase our local branch on top of the remote commits.
David James97d95872012-11-16 15:09:56 -0800115 remote, push_branch = git.GetTrackingBranch(cwd, for_push=True)
116 git.SyncPushBranch(cwd, remote, push_branch)
David James66009462012-03-25 10:08:38 -0700117
118 # Check whether any local changes remain after the sync.
Brian Harring609dc4e2012-05-07 02:17:44 -0700119 if not _DoWeHaveLocalCommits(stable_branch, push_branch, cwd):
Ralph Nathan03047282015-03-23 11:09:32 -0700120 logging.info('All changes already pushed for %s. Exiting', cwd)
David James66009462012-03-25 10:08:38 -0700121 return
122
Matt Tennantcb522052013-11-25 14:23:43 -0800123 # Add a failsafe check here. Only CLs from the 'chrome-bot' user should
124 # be involved here. If any other CLs are found then complain.
125 # In dryruns extra CLs are normal, though, and can be ignored.
126 bad_cl_cmd = ['log', '--format=short', '--perl-regexp',
127 '--author', '^(?!chrome-bot)', '%s..%s' % (
128 push_branch, stable_branch)]
129 bad_cls = git.RunGit(cwd, bad_cl_cmd).output
130 if bad_cls.strip() and not dryrun:
131 cros_build_lib.Error('The Uprev stage found changes from users other'
132 ' than chrome-bot:\n\n%s', bad_cls)
133 raise AssertionError('Unexpected CLs found during uprev stage.')
134
Mike Frysingere65f3752014-12-08 00:46:39 -0500135 description = git.RunGit(
136 cwd,
137 ['log', '--format=format:%s%n%n%b',
138 '%s..%s' % (push_branch, stable_branch)]).output
Gabe Black71e963e2014-10-28 20:19:59 -0700139 description = '%s\n\n%s' % (GIT_COMMIT_SUBJECT, description)
Ralph Nathan03047282015-03-23 11:09:32 -0700140 logging.info('For %s, using description %s', cwd, description)
David James97d95872012-11-16 15:09:56 -0800141 git.CreatePushBranch(constants.MERGE_BRANCH, cwd)
142 git.RunGit(cwd, ['merge', '--squash', stable_branch])
143 git.RunGit(cwd, ['commit', '-m', description])
144 git.RunGit(cwd, ['config', 'push.default', 'tracking'])
145 git.PushWithRetry(constants.MERGE_BRANCH, cwd, dryrun=dryrun)
Chris Sosadad0d322011-01-31 16:37:33 -0800146
147
148class GitBranch(object):
149 """Wrapper class for a git branch."""
150
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400151 def __init__(self, branch_name, tracking_branch, cwd):
David Jamesc7c4ff52013-09-18 17:57:13 -0700152 """Sets up variables but does not create the branch.
153
Mike Frysinger5cd8c742013-10-11 14:43:01 -0400154 Args:
David Jamesc7c4ff52013-09-18 17:57:13 -0700155 branch_name: The name of the branch.
156 tracking_branch: The associated tracking branch.
157 cwd: The git repository to work in.
158 """
Chris Sosadad0d322011-01-31 16:37:33 -0800159 self.branch_name = branch_name
160 self.tracking_branch = tracking_branch
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400161 self.cwd = cwd
Chris Sosadad0d322011-01-31 16:37:33 -0800162
163 def CreateBranch(self):
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400164 self.Checkout()
Chris Sosadad0d322011-01-31 16:37:33 -0800165
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400166 def Checkout(self, branch=None):
Chris Sosadad0d322011-01-31 16:37:33 -0800167 """Function used to check out to another GitBranch."""
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400168 if not branch:
169 branch = self.branch_name
170 if branch == self.tracking_branch or self.Exists(branch):
171 git_cmd = ['git', 'checkout', '-f', branch]
Chris Sosadad0d322011-01-31 16:37:33 -0800172 else:
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400173 git_cmd = ['repo', 'start', branch, '.']
Yu-Ju Hong3add4432014-01-30 11:46:15 -0800174 cros_build_lib.RunCommand(git_cmd, print_cmd=False, cwd=self.cwd,
175 capture_output=True)
Chris Sosadad0d322011-01-31 16:37:33 -0800176
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400177 def Exists(self, branch=None):
Chris Sosadad0d322011-01-31 16:37:33 -0800178 """Returns True if the branch exists."""
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400179 if not branch:
180 branch = self.branch_name
David James67d73252013-09-19 17:33:12 -0700181 branches = git.RunGit(self.cwd, ['branch']).output
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400182 return branch in branches.split()
Chris Sosadad0d322011-01-31 16:37:33 -0800183
184
Mike Frysingere8dcbfd2015-03-08 21:45:52 -0400185def GetParser():
186 """Creates the argparse parser."""
187 parser = commandline.ArgumentParser()
188 parser.add_argument('--all', action='store_true',
189 help='Mark all packages as stable.')
190 parser.add_argument('-b', '--boards', default='',
191 help='Colon-separated list of boards.')
192 parser.add_argument('--drop_file',
193 help='File to list packages that were revved.')
194 parser.add_argument('--dryrun', action='store_true',
195 help='Passes dry-run to git push if pushing a change.')
196 parser.add_argument('-o', '--overlays',
197 help='Colon-separated list of overlays to modify.')
198 parser.add_argument('-p', '--packages',
199 help='Colon separated list of packages to rev.')
200 parser.add_argument('-r', '--srcroot', type='path',
201 default=os.path.join(constants.SOURCE_ROOT, 'src'),
202 help='Path to root src directory.')
203 parser.add_argument('--verbose', action='store_true',
204 help='Prints out debug info.')
205 parser.add_argument('command', choices=COMMAND_DICTIONARY.keys(),
206 help='Command to run.')
207 return parser
208
209
210def main(argv):
211 parser = GetParser()
212 options = parser.parse_args(argv)
213
214 if not options.packages and options.command == 'commit' and not options.all:
215 parser.error('Please specify at least one package (--packages)')
216 if not os.path.isdir(options.srcroot):
217 parser.error('srcroot is not a valid path: %s' % options.srcroot)
218 if options.boards:
219 cros_build_lib.AssertInsideChroot()
220
221 options.Freeze()
Chris Sosadad0d322011-01-31 16:37:33 -0800222
Alex Deymo075c2292014-09-04 18:31:50 -0700223 portage_util.EBuild.VERBOSE = options.verbose
Chris Sosa62ad8522011-03-08 17:46:17 -0800224
Chris Sosa62ad8522011-03-08 17:46:17 -0800225 package_list = None
226 if options.packages:
227 package_list = options.packages.split(':')
228
Chris Sosa62ad8522011-03-08 17:46:17 -0800229 if options.overlays:
Chris Sosadad0d322011-01-31 16:37:33 -0800230 overlays = {}
Chris Sosa62ad8522011-03-08 17:46:17 -0800231 for path in options.overlays.split(':'):
Ryan Cui05a31ba2011-05-31 17:47:37 -0700232 if not os.path.isdir(path):
Chris Sosac13bba52011-05-24 15:14:09 -0700233 cros_build_lib.Die('Cannot find overlay: %s' % path)
Chris Sosadad0d322011-01-31 16:37:33 -0800234 overlays[path] = []
235 else:
Chris Sosac13bba52011-05-24 15:14:09 -0700236 cros_build_lib.Warning('Missing --overlays argument')
Chris Sosadad0d322011-01-31 16:37:33 -0800237 overlays = {
Mike Frysingere65f3752014-12-08 00:46:39 -0500238 '%s/private-overlays/chromeos-overlay' % options.srcroot: [],
239 '%s/third_party/chromiumos-overlay' % options.srcroot: [],
Chris Sosadad0d322011-01-31 16:37:33 -0800240 }
241
David James97d95872012-11-16 15:09:56 -0800242 manifest = git.ManifestCheckout.Cached(options.srcroot)
Ryan Cui4656a3c2011-05-24 12:30:30 -0700243
Mike Frysingere8dcbfd2015-03-08 21:45:52 -0400244 if options.command == 'commit':
Alex Deymo075c2292014-09-04 18:31:50 -0700245 portage_util.BuildEBuildDictionary(overlays, options.all, package_list)
David James84e953c2013-04-23 18:44:06 -0700246
David James15ed1302013-04-25 09:21:19 -0700247 # Contains the array of packages we actually revved.
248 revved_packages = []
249 new_package_atoms = []
David Jamesa8457b52011-05-28 00:03:20 -0700250
David James15ed1302013-04-25 09:21:19 -0700251 # Slight optimization hack: process the chromiumos overlay before any other
252 # cros-workon overlay first so we can do background cache generation in it.
253 # A perfect solution would walk all the overlays, figure out any dependencies
254 # between them (with layout.conf), and then process them in dependency order.
255 # However, this operation isn't slow enough to warrant that level of
256 # complexity, so we'll just special case the main overlay.
257 #
258 # Similarly, generate the cache in the portage-stable tree asap. We know
259 # we won't have any cros-workon packages in there, so generating the cache
260 # is the only thing it'll be doing. The chromiumos overlay instead might
261 # have revbumping to do before it can generate the cache.
262 keys = overlays.keys()
263 for overlay in ('/third_party/chromiumos-overlay',
264 '/third_party/portage-stable'):
265 for k in keys:
266 if k.endswith(overlay):
267 keys.remove(k)
268 keys.insert(0, k)
269 break
Chris Sosadad0d322011-01-31 16:37:33 -0800270
Alex Deymo075c2292014-09-04 18:31:50 -0700271 with parallel.BackgroundTaskRunner(portage_util.RegenCache) as queue:
David James15ed1302013-04-25 09:21:19 -0700272 for overlay in keys:
273 ebuilds = overlays[overlay]
274 if not os.path.isdir(overlay):
Mike Frysinger5cd8c742013-10-11 14:43:01 -0400275 cros_build_lib.Warning('Skipping %s' % overlay)
David James15ed1302013-04-25 09:21:19 -0700276 continue
Chris Sosadad0d322011-01-31 16:37:33 -0800277
David James15ed1302013-04-25 09:21:19 -0700278 # Note we intentionally work from the non push tracking branch;
279 # everything built thus far has been against it (meaning, http mirrors),
280 # thus we should honor that. During the actual push, the code switches
281 # to the correct urls, and does an appropriate rebasing.
282 tracking_branch = git.GetTrackingBranchViaManifest(
283 overlay, manifest=manifest)[1]
Brian Harring609dc4e2012-05-07 02:17:44 -0700284
Mike Frysingere8dcbfd2015-03-08 21:45:52 -0400285 if options.command == 'push':
David James15ed1302013-04-25 09:21:19 -0700286 PushChange(constants.STABLE_EBUILD_BRANCH, tracking_branch,
287 options.dryrun, cwd=overlay)
Mike Frysingere8dcbfd2015-03-08 21:45:52 -0400288 elif options.command == 'commit':
David James98b77f52013-11-19 10:11:56 -0800289 existing_commit = git.GetGitRepoRevision(overlay)
David James15ed1302013-04-25 09:21:19 -0700290 work_branch = GitBranch(constants.STABLE_EBUILD_BRANCH, tracking_branch,
291 cwd=overlay)
292 work_branch.CreateBranch()
293 if not work_branch.Exists():
294 cros_build_lib.Die('Unable to create stabilizing branch in %s' %
295 overlay)
296
297 # In the case of uprevving overlays that have patches applied to them,
298 # include the patched changes in the stabilizing branch.
David James98b77f52013-11-19 10:11:56 -0800299 git.RunGit(overlay, ['rebase', existing_commit])
David James15ed1302013-04-25 09:21:19 -0700300
301 messages = []
302 for ebuild in ebuilds:
303 if options.verbose:
Ralph Nathan03047282015-03-23 11:09:32 -0700304 logging.info('Working on %s', ebuild.package)
David James15ed1302013-04-25 09:21:19 -0700305 try:
306 new_package = ebuild.RevWorkOnEBuild(options.srcroot, manifest)
307 if new_package:
308 revved_packages.append(ebuild.package)
309 new_package_atoms.append('=%s' % new_package)
310 messages.append(_GIT_COMMIT_MESSAGE % ebuild.package)
311 except (OSError, IOError):
Mike Frysinger5cd8c742013-10-11 14:43:01 -0400312 cros_build_lib.Warning(
313 'Cannot rev %s\n'
314 'Note you will have to go into %s '
315 'and reset the git repo yourself.' % (ebuild.package, overlay))
David James15ed1302013-04-25 09:21:19 -0700316 raise
317
318 if messages:
Alex Deymo075c2292014-09-04 18:31:50 -0700319 portage_util.EBuild.CommitChange('\n\n'.join(messages), overlay)
David James15ed1302013-04-25 09:21:19 -0700320
321 if cros_build_lib.IsInsideChroot():
322 # Regenerate caches if need be. We do this all the time to
323 # catch when users make changes without updating cache files.
324 queue.put([overlay])
325
Mike Frysingere8dcbfd2015-03-08 21:45:52 -0400326 if options.command == 'commit':
David James15ed1302013-04-25 09:21:19 -0700327 if cros_build_lib.IsInsideChroot():
328 CleanStalePackages(options.boards.split(':'), new_package_atoms)
329 if options.drop_file:
330 osutils.WriteFile(options.drop_file, ' '.join(revved_packages))