blob: dca0432f2dcc90562ae8ae2e10df6cd1c97f506a [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 James41124af2015-06-04 21:13:25 -070036def CleanStalePackages(srcroot, 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 James41124af2015-06-04 21:13:25 -070040 srcroot: Root directory of the source tree.
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,
David James41124af2015-06-04 21:13:25 -070063 enter_chroot=True, extra_env={'CLEAN_DELAY': '0'},
64 error_code_ok=True, cwd=srcroot)
David James63841a82014-01-16 14:39:24 -080065 if not result.returncode in (0, 1):
66 raise cros_build_lib.RunCommandError('unexpected error', result)
David James59a0a2b2013-03-22 14:04:44 -070067 runcmd([eclean, '-d', 'packages'],
David James41124af2015-06-04 21:13:25 -070068 cwd=srcroot, enter_chroot=True,
David James59a0a2b2013-03-22 14:04:44 -070069 redirect_stdout=True, redirect_stderr=True)
Mike Frysingerb7ab9b82012-04-04 16:22:43 -040070
71 tasks = []
David Jamescc09c9b2012-01-26 22:10:13 -080072 for board in boards:
Mike Frysingerb7ab9b82012-04-04 16:22:43 -040073 tasks.append([board])
74 tasks.append([None])
75
David James6450a0a2012-12-04 07:59:53 -080076 parallel.RunTasksInProcessPool(_CleanStalePackages, tasks)
Chris Sosadad0d322011-01-31 16:37:33 -080077
78
Mike Frysinger2ebe3732012-05-08 17:04:12 -040079# TODO(build): This code needs to be gutted and rebased to cros_build_lib.
80def _DoWeHaveLocalCommits(stable_branch, tracking_branch, cwd):
Chris Sosadad0d322011-01-31 16:37:33 -080081 """Returns true if there are local commits."""
David James97d95872012-11-16 15:09:56 -080082 current_branch = git.GetCurrentBranch(cwd)
Brian Harring609dc4e2012-05-07 02:17:44 -070083
84 if current_branch != stable_branch:
Chris Sosadad0d322011-01-31 16:37:33 -080085 return False
David James97d95872012-11-16 15:09:56 -080086 output = git.RunGit(
Brian Harring609dc4e2012-05-07 02:17:44 -070087 cwd, ['rev-parse', 'HEAD', tracking_branch]).output.split()
Brian Harring5b86b5e2012-05-25 18:27:40 -070088 return output[0] != output[1]
Chris Sosadad0d322011-01-31 16:37:33 -080089
90
Chris Sosadad0d322011-01-31 16:37:33 -080091# ======================= End Global Helper Functions ========================
92
93
Mike Frysinger2ebe3732012-05-08 17:04:12 -040094def PushChange(stable_branch, tracking_branch, dryrun, cwd):
Chris Sosadad0d322011-01-31 16:37:33 -080095 """Pushes commits in the stable_branch to the remote git repository.
96
David Jamesee2da622012-02-23 09:32:16 -080097 Pushes local commits from calls to CommitChange to the remote git
David James66009462012-03-25 10:08:38 -070098 repository specified by current working directory. If changes are
99 found to commit, they will be merged to the merge branch and pushed.
100 In that case, the local repository will be left on the merge branch.
Chris Sosadad0d322011-01-31 16:37:33 -0800101
102 Args:
103 stable_branch: The local branch with commits we want to push.
104 tracking_branch: The tracking branch of the local branch.
Chris Sosa62ad8522011-03-08 17:46:17 -0800105 dryrun: Use git push --dryrun to emulate a push.
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400106 cwd: The directory to run commands in.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500107
Chris Sosadad0d322011-01-31 16:37:33 -0800108 Raises:
Mike Frysinger5cd8c742013-10-11 14:43:01 -0400109 OSError: Error occurred while pushing.
Chris Sosadad0d322011-01-31 16:37:33 -0800110 """
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400111 if not _DoWeHaveLocalCommits(stable_branch, tracking_branch, cwd):
Ralph Nathan03047282015-03-23 11:09:32 -0700112 logging.info('No work found to push in %s. Exiting', cwd)
Chris Sosadad0d322011-01-31 16:37:33 -0800113 return
114
David James66009462012-03-25 10:08:38 -0700115 # For the commit queue, our local branch may contain commits that were
116 # just tested and pushed during the CommitQueueCompletion stage. Sync
117 # and rebase our local branch on top of the remote commits.
Don Garrett99449592015-03-25 11:01:30 -0700118 remote_ref = git.GetTrackingBranch(cwd, for_push=True)
119 git.SyncPushBranch(cwd, remote_ref.remote, remote_ref.ref)
David James66009462012-03-25 10:08:38 -0700120
121 # Check whether any local changes remain after the sync.
Don Garrett99449592015-03-25 11:01:30 -0700122 if not _DoWeHaveLocalCommits(stable_branch, remote_ref.ref, cwd):
Ralph Nathan03047282015-03-23 11:09:32 -0700123 logging.info('All changes already pushed for %s. Exiting', cwd)
David James66009462012-03-25 10:08:38 -0700124 return
125
Matt Tennantcb522052013-11-25 14:23:43 -0800126 # Add a failsafe check here. Only CLs from the 'chrome-bot' user should
127 # be involved here. If any other CLs are found then complain.
128 # In dryruns extra CLs are normal, though, and can be ignored.
129 bad_cl_cmd = ['log', '--format=short', '--perl-regexp',
130 '--author', '^(?!chrome-bot)', '%s..%s' % (
Don Garrett99449592015-03-25 11:01:30 -0700131 remote_ref.ref, stable_branch)]
Matt Tennantcb522052013-11-25 14:23:43 -0800132 bad_cls = git.RunGit(cwd, bad_cl_cmd).output
133 if bad_cls.strip() and not dryrun:
Ralph Nathan59900422015-03-24 10:41:17 -0700134 logging.error('The Uprev stage found changes from users other than '
135 'chrome-bot:\n\n%s', bad_cls)
Matt Tennantcb522052013-11-25 14:23:43 -0800136 raise AssertionError('Unexpected CLs found during uprev stage.')
137
Mike Frysingere65f3752014-12-08 00:46:39 -0500138 description = git.RunGit(
139 cwd,
140 ['log', '--format=format:%s%n%n%b',
Don Garrett99449592015-03-25 11:01:30 -0700141 '%s..%s' % (remote_ref.ref, stable_branch)]).output
Gabe Black71e963e2014-10-28 20:19:59 -0700142 description = '%s\n\n%s' % (GIT_COMMIT_SUBJECT, description)
Ralph Nathan03047282015-03-23 11:09:32 -0700143 logging.info('For %s, using description %s', cwd, description)
David James97d95872012-11-16 15:09:56 -0800144 git.CreatePushBranch(constants.MERGE_BRANCH, cwd)
145 git.RunGit(cwd, ['merge', '--squash', stable_branch])
146 git.RunGit(cwd, ['commit', '-m', description])
147 git.RunGit(cwd, ['config', 'push.default', 'tracking'])
148 git.PushWithRetry(constants.MERGE_BRANCH, cwd, dryrun=dryrun)
Chris Sosadad0d322011-01-31 16:37:33 -0800149
150
151class GitBranch(object):
152 """Wrapper class for a git branch."""
153
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400154 def __init__(self, branch_name, tracking_branch, cwd):
David Jamesc7c4ff52013-09-18 17:57:13 -0700155 """Sets up variables but does not create the branch.
156
Mike Frysinger5cd8c742013-10-11 14:43:01 -0400157 Args:
David Jamesc7c4ff52013-09-18 17:57:13 -0700158 branch_name: The name of the branch.
159 tracking_branch: The associated tracking branch.
160 cwd: The git repository to work in.
161 """
Chris Sosadad0d322011-01-31 16:37:33 -0800162 self.branch_name = branch_name
163 self.tracking_branch = tracking_branch
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400164 self.cwd = cwd
Chris Sosadad0d322011-01-31 16:37:33 -0800165
166 def CreateBranch(self):
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400167 self.Checkout()
Chris Sosadad0d322011-01-31 16:37:33 -0800168
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400169 def Checkout(self, branch=None):
Chris Sosadad0d322011-01-31 16:37:33 -0800170 """Function used to check out to another GitBranch."""
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400171 if not branch:
172 branch = self.branch_name
173 if branch == self.tracking_branch or self.Exists(branch):
174 git_cmd = ['git', 'checkout', '-f', branch]
Chris Sosadad0d322011-01-31 16:37:33 -0800175 else:
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400176 git_cmd = ['repo', 'start', branch, '.']
Yu-Ju Hong3add4432014-01-30 11:46:15 -0800177 cros_build_lib.RunCommand(git_cmd, print_cmd=False, cwd=self.cwd,
178 capture_output=True)
Chris Sosadad0d322011-01-31 16:37:33 -0800179
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400180 def Exists(self, branch=None):
Chris Sosadad0d322011-01-31 16:37:33 -0800181 """Returns True if the branch exists."""
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400182 if not branch:
183 branch = self.branch_name
David James67d73252013-09-19 17:33:12 -0700184 branches = git.RunGit(self.cwd, ['branch']).output
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400185 return branch in branches.split()
Chris Sosadad0d322011-01-31 16:37:33 -0800186
187
Mike Frysingere8dcbfd2015-03-08 21:45:52 -0400188def GetParser():
189 """Creates the argparse parser."""
190 parser = commandline.ArgumentParser()
191 parser.add_argument('--all', action='store_true',
192 help='Mark all packages as stable.')
193 parser.add_argument('-b', '--boards', default='',
194 help='Colon-separated list of boards.')
195 parser.add_argument('--drop_file',
196 help='File to list packages that were revved.')
197 parser.add_argument('--dryrun', action='store_true',
198 help='Passes dry-run to git push if pushing a change.')
199 parser.add_argument('-o', '--overlays',
200 help='Colon-separated list of overlays to modify.')
201 parser.add_argument('-p', '--packages',
202 help='Colon separated list of packages to rev.')
203 parser.add_argument('-r', '--srcroot', type='path',
204 default=os.path.join(constants.SOURCE_ROOT, 'src'),
205 help='Path to root src directory.')
206 parser.add_argument('--verbose', action='store_true',
207 help='Prints out debug info.')
208 parser.add_argument('command', choices=COMMAND_DICTIONARY.keys(),
209 help='Command to run.')
210 return parser
211
212
213def main(argv):
214 parser = GetParser()
215 options = parser.parse_args(argv)
216
217 if not options.packages and options.command == 'commit' and not options.all:
218 parser.error('Please specify at least one package (--packages)')
219 if not os.path.isdir(options.srcroot):
220 parser.error('srcroot is not a valid path: %s' % options.srcroot)
Mike Frysingere8dcbfd2015-03-08 21:45:52 -0400221
222 options.Freeze()
Chris Sosadad0d322011-01-31 16:37:33 -0800223
Alex Deymo075c2292014-09-04 18:31:50 -0700224 portage_util.EBuild.VERBOSE = options.verbose
Chris Sosa62ad8522011-03-08 17:46:17 -0800225
Chris Sosa62ad8522011-03-08 17:46:17 -0800226 package_list = None
227 if options.packages:
228 package_list = options.packages.split(':')
229
Chris Sosa62ad8522011-03-08 17:46:17 -0800230 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:
Ralph Nathan446aee92015-03-23 14:44:56 -0700237 logging.warning('Missing --overlays argument')
Chris Sosadad0d322011-01-31 16:37:33 -0800238 overlays = {
Mike Frysingere65f3752014-12-08 00:46:39 -0500239 '%s/private-overlays/chromeos-overlay' % options.srcroot: [],
240 '%s/third_party/chromiumos-overlay' % options.srcroot: [],
Chris Sosadad0d322011-01-31 16:37:33 -0800241 }
242
David James97d95872012-11-16 15:09:56 -0800243 manifest = git.ManifestCheckout.Cached(options.srcroot)
Ryan Cui4656a3c2011-05-24 12:30:30 -0700244
Mike Frysingere8dcbfd2015-03-08 21:45:52 -0400245 if options.command == 'commit':
Alex Deymo075c2292014-09-04 18:31:50 -0700246 portage_util.BuildEBuildDictionary(overlays, options.all, package_list)
David James84e953c2013-04-23 18:44:06 -0700247
David James15ed1302013-04-25 09:21:19 -0700248 # Contains the array of packages we actually revved.
249 revved_packages = []
250 new_package_atoms = []
David Jamesa8457b52011-05-28 00:03:20 -0700251
David James41124af2015-06-04 21:13:25 -0700252 for overlay in overlays:
253 ebuilds = overlays[overlay]
254 if not os.path.isdir(overlay):
255 logging.warning('Skipping %s' % overlay)
256 continue
Chris Sosadad0d322011-01-31 16:37:33 -0800257
David James41124af2015-06-04 21:13:25 -0700258 # Note we intentionally work from the non push tracking branch;
259 # everything built thus far has been against it (meaning, http mirrors),
260 # thus we should honor that. During the actual push, the code switches
261 # to the correct urls, and does an appropriate rebasing.
262 tracking_branch = git.GetTrackingBranchViaManifest(
263 overlay, manifest=manifest).ref
Chris Sosadad0d322011-01-31 16:37:33 -0800264
David James41124af2015-06-04 21:13:25 -0700265 if options.command == 'push':
266 PushChange(constants.STABLE_EBUILD_BRANCH, tracking_branch,
267 options.dryrun, cwd=overlay)
268 elif options.command == 'commit':
269 existing_commit = git.GetGitRepoRevision(overlay)
270 work_branch = GitBranch(constants.STABLE_EBUILD_BRANCH, tracking_branch,
271 cwd=overlay)
272 work_branch.CreateBranch()
273 if not work_branch.Exists():
274 cros_build_lib.Die('Unable to create stabilizing branch in %s' %
275 overlay)
Brian Harring609dc4e2012-05-07 02:17:44 -0700276
David James41124af2015-06-04 21:13:25 -0700277 # In the case of uprevving overlays that have patches applied to them,
278 # include the patched changes in the stabilizing branch.
279 git.RunGit(overlay, ['rebase', existing_commit])
David James15ed1302013-04-25 09:21:19 -0700280
David James41124af2015-06-04 21:13:25 -0700281 messages = []
282 for ebuild in ebuilds:
283 if options.verbose:
284 logging.info('Working on %s', ebuild.package)
285 try:
286 new_package = ebuild.RevWorkOnEBuild(options.srcroot, manifest)
287 if new_package:
288 revved_packages.append(ebuild.package)
289 new_package_atoms.append('=%s' % new_package)
290 messages.append(_GIT_COMMIT_MESSAGE % ebuild.package)
291 except (OSError, IOError):
292 logging.warning(
293 'Cannot rev %s\n'
294 'Note you will have to go into %s '
295 'and reset the git repo yourself.' % (ebuild.package, overlay))
296 raise
David James15ed1302013-04-25 09:21:19 -0700297
David James41124af2015-06-04 21:13:25 -0700298 if messages:
299 portage_util.EBuild.CommitChange('\n\n'.join(messages), overlay)
David James15ed1302013-04-25 09:21:19 -0700300
Mike Frysingere8dcbfd2015-03-08 21:45:52 -0400301 if options.command == 'commit':
David James41124af2015-06-04 21:13:25 -0700302 chroot_path = os.path.join(options.srcroot, constants.DEFAULT_CHROOT_DIR)
303 if os.path.exists(chroot_path):
304 CleanStalePackages(options.srcroot, options.boards.split(':'),
305 new_package_atoms)
David James15ed1302013-04-25 09:21:19 -0700306 if options.drop_file:
307 osutils.WriteFile(options.drop_file, ' '.join(revved_packages))