blob: 3f3a583238a98b8017e181e16405bb6b4bb1ed40 [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
Aviv Keshetb7519e12016-10-04 00:50:00 -070011from chromite.lib 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 output = git.RunGit(
Paul Hobbs72d8e392015-10-21 17:24:23 -070083 cwd, ['rev-parse', stable_branch, 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 Sosadad0d322011-01-31 16:37:33 -080087# ======================= End Global Helper Functions ========================
88
89
Ningning Xia52009062016-05-09 14:33:51 -070090def PushChange(stable_branch, tracking_branch, dryrun, cwd,
91 staging_branch=None):
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.
Ningning Xia52009062016-05-09 14:33:51 -0700104 staging_branch: The staging branch to push for a failed PFQ run
Mike Frysinger1a736a82013-12-12 01:50:59 -0500105
Chris Sosadad0d322011-01-31 16:37:33 -0800106 Raises:
Mike Frysinger5cd8c742013-10-11 14:43:01 -0400107 OSError: Error occurred while pushing.
Chris Sosadad0d322011-01-31 16:37:33 -0800108 """
David James47959632015-10-23 07:56:01 -0700109 if not git.DoesCommitExistInRepo(cwd, stable_branch):
110 logging.debug('No branch created for %s. Exiting', cwd)
111 return
112
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400113 if not _DoWeHaveLocalCommits(stable_branch, tracking_branch, cwd):
David James47959632015-10-23 07:56:01 -0700114 logging.debug('No work found to push in %s. Exiting', cwd)
Chris Sosadad0d322011-01-31 16:37:33 -0800115 return
116
David James66009462012-03-25 10:08:38 -0700117 # For the commit queue, our local branch may contain commits that were
118 # just tested and pushed during the CommitQueueCompletion stage. Sync
119 # and rebase our local branch on top of the remote commits.
Paul Hobbs72d8e392015-10-21 17:24:23 -0700120 remote_ref = git.GetTrackingBranch(cwd,
121 branch=stable_branch,
122 for_push=True)
Paul Hobbs1e46be82015-10-30 13:46:02 -0700123 # SyncPushBranch rebases HEAD onto the updated remote. We need to checkout
124 # stable_branch here in order to update it.
125 git.RunGit(cwd, ['checkout', stable_branch])
Don Garrett99449592015-03-25 11:01:30 -0700126 git.SyncPushBranch(cwd, remote_ref.remote, remote_ref.ref)
David James66009462012-03-25 10:08:38 -0700127
128 # Check whether any local changes remain after the sync.
Don Garrett99449592015-03-25 11:01:30 -0700129 if not _DoWeHaveLocalCommits(stable_branch, remote_ref.ref, cwd):
Ralph Nathan03047282015-03-23 11:09:32 -0700130 logging.info('All changes already pushed for %s. Exiting', cwd)
David James66009462012-03-25 10:08:38 -0700131 return
132
Matt Tennantcb522052013-11-25 14:23:43 -0800133 # Add a failsafe check here. Only CLs from the 'chrome-bot' user should
134 # be involved here. If any other CLs are found then complain.
135 # In dryruns extra CLs are normal, though, and can be ignored.
136 bad_cl_cmd = ['log', '--format=short', '--perl-regexp',
137 '--author', '^(?!chrome-bot)', '%s..%s' % (
Don Garrett99449592015-03-25 11:01:30 -0700138 remote_ref.ref, stable_branch)]
Matt Tennantcb522052013-11-25 14:23:43 -0800139 bad_cls = git.RunGit(cwd, bad_cl_cmd).output
140 if bad_cls.strip() and not dryrun:
Ralph Nathan59900422015-03-24 10:41:17 -0700141 logging.error('The Uprev stage found changes from users other than '
142 'chrome-bot:\n\n%s', bad_cls)
Matt Tennantcb522052013-11-25 14:23:43 -0800143 raise AssertionError('Unexpected CLs found during uprev stage.')
144
Lev Rumyantsev25352ba2016-09-07 15:53:58 -0700145 if staging_branch is not None:
146 logging.info('PFQ FAILED. Pushing uprev change to staging branch %s',
147 staging_branch)
148
Mike Frysingere65f3752014-12-08 00:46:39 -0500149 description = git.RunGit(
150 cwd,
151 ['log', '--format=format:%s%n%n%b',
Don Garrett99449592015-03-25 11:01:30 -0700152 '%s..%s' % (remote_ref.ref, stable_branch)]).output
Gabe Black71e963e2014-10-28 20:19:59 -0700153 description = '%s\n\n%s' % (GIT_COMMIT_SUBJECT, description)
Ralph Nathan03047282015-03-23 11:09:32 -0700154 logging.info('For %s, using description %s', cwd, description)
Paul Hobbsf52ea8f2015-10-21 17:24:23 -0700155 git.CreatePushBranch(constants.MERGE_BRANCH, cwd,
156 remote_push_branch=remote_ref)
David James97d95872012-11-16 15:09:56 -0800157 git.RunGit(cwd, ['merge', '--squash', stable_branch])
158 git.RunGit(cwd, ['commit', '-m', description])
159 git.RunGit(cwd, ['config', 'push.default', 'tracking'])
Ningning Xia52009062016-05-09 14:33:51 -0700160 git.PushWithRetry(constants.MERGE_BRANCH, cwd, dryrun=dryrun,
161 staging_branch=staging_branch)
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):
David Jamesc7c4ff52013-09-18 17:57:13 -0700168 """Sets up variables but does not create the branch.
169
Mike Frysinger5cd8c742013-10-11 14:43:01 -0400170 Args:
David Jamesc7c4ff52013-09-18 17:57:13 -0700171 branch_name: The name of the branch.
172 tracking_branch: The associated tracking branch.
173 cwd: The git repository to work in.
174 """
Chris Sosadad0d322011-01-31 16:37:33 -0800175 self.branch_name = branch_name
176 self.tracking_branch = tracking_branch
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400177 self.cwd = cwd
Chris Sosadad0d322011-01-31 16:37:33 -0800178
179 def CreateBranch(self):
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400180 self.Checkout()
Chris Sosadad0d322011-01-31 16:37:33 -0800181
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400182 def Checkout(self, branch=None):
Chris Sosadad0d322011-01-31 16:37:33 -0800183 """Function used to check out to another GitBranch."""
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400184 if not branch:
185 branch = self.branch_name
186 if branch == self.tracking_branch or self.Exists(branch):
187 git_cmd = ['git', 'checkout', '-f', branch]
Chris Sosadad0d322011-01-31 16:37:33 -0800188 else:
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400189 git_cmd = ['repo', 'start', branch, '.']
Yu-Ju Hong3add4432014-01-30 11:46:15 -0800190 cros_build_lib.RunCommand(git_cmd, print_cmd=False, cwd=self.cwd,
191 capture_output=True)
Chris Sosadad0d322011-01-31 16:37:33 -0800192
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400193 def Exists(self, branch=None):
Chris Sosadad0d322011-01-31 16:37:33 -0800194 """Returns True if the branch exists."""
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400195 if not branch:
196 branch = self.branch_name
David James67d73252013-09-19 17:33:12 -0700197 branches = git.RunGit(self.cwd, ['branch']).output
Mike Frysinger2ebe3732012-05-08 17:04:12 -0400198 return branch in branches.split()
Chris Sosadad0d322011-01-31 16:37:33 -0800199
200
Mike Frysingere8dcbfd2015-03-08 21:45:52 -0400201def GetParser():
202 """Creates the argparse parser."""
203 parser = commandline.ArgumentParser()
204 parser.add_argument('--all', action='store_true',
205 help='Mark all packages as stable.')
206 parser.add_argument('-b', '--boards', default='',
207 help='Colon-separated list of boards.')
208 parser.add_argument('--drop_file',
209 help='File to list packages that were revved.')
210 parser.add_argument('--dryrun', action='store_true',
211 help='Passes dry-run to git push if pushing a change.')
Bertrand SIMONNET6af54302015-08-05 10:12:49 -0700212 parser.add_argument('--force', action='store_true',
213 help='Force the stabilization of blacklisted packages. '
214 '(only compatible with -p)')
Mike Frysingere8dcbfd2015-03-08 21:45:52 -0400215 parser.add_argument('-o', '--overlays',
216 help='Colon-separated list of overlays to modify.')
217 parser.add_argument('-p', '--packages',
218 help='Colon separated list of packages to rev.')
219 parser.add_argument('-r', '--srcroot', type='path',
220 default=os.path.join(constants.SOURCE_ROOT, 'src'),
221 help='Path to root src directory.')
222 parser.add_argument('--verbose', action='store_true',
223 help='Prints out debug info.')
Ningning Xia52009062016-05-09 14:33:51 -0700224 parser.add_argument('--staging_branch',
225 help='The staging branch to push changes')
Mike Frysingere8dcbfd2015-03-08 21:45:52 -0400226 parser.add_argument('command', choices=COMMAND_DICTIONARY.keys(),
227 help='Command to run.')
228 return parser
229
230
231def main(argv):
232 parser = GetParser()
233 options = parser.parse_args(argv)
Bertrand SIMONNET6af54302015-08-05 10:12:49 -0700234 options.Freeze()
Mike Frysingere8dcbfd2015-03-08 21:45:52 -0400235
Bertrand SIMONNET6af54302015-08-05 10:12:49 -0700236 if options.command == 'commit':
237 if not options.packages and not options.all:
238 parser.error('Please specify at least one package (--packages)')
239 if options.force and options.all:
240 parser.error('Cannot use --force with --all. You must specify a list of '
241 'packages you want to force uprev.')
242
Mike Frysingere8dcbfd2015-03-08 21:45:52 -0400243 if not os.path.isdir(options.srcroot):
244 parser.error('srcroot is not a valid path: %s' % options.srcroot)
Mike Frysingere8dcbfd2015-03-08 21:45:52 -0400245
Alex Deymo075c2292014-09-04 18:31:50 -0700246 portage_util.EBuild.VERBOSE = options.verbose
Chris Sosa62ad8522011-03-08 17:46:17 -0800247
Chris Sosa62ad8522011-03-08 17:46:17 -0800248 package_list = None
249 if options.packages:
250 package_list = options.packages.split(':')
251
Chris Sosa62ad8522011-03-08 17:46:17 -0800252 if options.overlays:
Chris Sosadad0d322011-01-31 16:37:33 -0800253 overlays = {}
Chris Sosa62ad8522011-03-08 17:46:17 -0800254 for path in options.overlays.split(':'):
Ryan Cui05a31ba2011-05-31 17:47:37 -0700255 if not os.path.isdir(path):
Chris Sosac13bba52011-05-24 15:14:09 -0700256 cros_build_lib.Die('Cannot find overlay: %s' % path)
Chris Sosadad0d322011-01-31 16:37:33 -0800257 overlays[path] = []
258 else:
Ralph Nathan446aee92015-03-23 14:44:56 -0700259 logging.warning('Missing --overlays argument')
Chris Sosadad0d322011-01-31 16:37:33 -0800260 overlays = {
Mike Frysingere65f3752014-12-08 00:46:39 -0500261 '%s/private-overlays/chromeos-overlay' % options.srcroot: [],
262 '%s/third_party/chromiumos-overlay' % options.srcroot: [],
Chris Sosadad0d322011-01-31 16:37:33 -0800263 }
264
David James97d95872012-11-16 15:09:56 -0800265 manifest = git.ManifestCheckout.Cached(options.srcroot)
Ryan Cui4656a3c2011-05-24 12:30:30 -0700266
Mike Frysingere8dcbfd2015-03-08 21:45:52 -0400267 if options.command == 'commit':
Bertrand SIMONNET6af54302015-08-05 10:12:49 -0700268 portage_util.BuildEBuildDictionary(overlays, options.all, package_list,
269 allow_blacklisted=options.force)
David James84e953c2013-04-23 18:44:06 -0700270
David James15ed1302013-04-25 09:21:19 -0700271 # Contains the array of packages we actually revved.
272 revved_packages = []
273 new_package_atoms = []
David Jamesa8457b52011-05-28 00:03:20 -0700274
David James41124af2015-06-04 21:13:25 -0700275 for overlay in overlays:
276 ebuilds = overlays[overlay]
277 if not os.path.isdir(overlay):
278 logging.warning('Skipping %s' % overlay)
279 continue
Chris Sosadad0d322011-01-31 16:37:33 -0800280
David James41124af2015-06-04 21:13:25 -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.
285 tracking_branch = git.GetTrackingBranchViaManifest(
286 overlay, manifest=manifest).ref
Chris Sosadad0d322011-01-31 16:37:33 -0800287
David James41124af2015-06-04 21:13:25 -0700288 if options.command == 'push':
289 PushChange(constants.STABLE_EBUILD_BRANCH, tracking_branch,
Ningning Xia52009062016-05-09 14:33:51 -0700290 options.dryrun, cwd=overlay,
291 staging_branch=options.staging_branch)
David James41124af2015-06-04 21:13:25 -0700292 elif options.command == 'commit':
293 existing_commit = git.GetGitRepoRevision(overlay)
294 work_branch = GitBranch(constants.STABLE_EBUILD_BRANCH, tracking_branch,
295 cwd=overlay)
296 work_branch.CreateBranch()
297 if not work_branch.Exists():
298 cros_build_lib.Die('Unable to create stabilizing branch in %s' %
299 overlay)
Brian Harring609dc4e2012-05-07 02:17:44 -0700300
David James41124af2015-06-04 21:13:25 -0700301 # In the case of uprevving overlays that have patches applied to them,
302 # include the patched changes in the stabilizing branch.
303 git.RunGit(overlay, ['rebase', existing_commit])
David James15ed1302013-04-25 09:21:19 -0700304
David James41124af2015-06-04 21:13:25 -0700305 messages = []
306 for ebuild in ebuilds:
307 if options.verbose:
Aviv Keshet845602e2016-10-13 23:44:34 -0700308 logging.info('Working on %s, info %s', ebuild.package,
309 ebuild.cros_workon_vars)
David James41124af2015-06-04 21:13:25 -0700310 try:
311 new_package = ebuild.RevWorkOnEBuild(options.srcroot, manifest)
312 if new_package:
313 revved_packages.append(ebuild.package)
314 new_package_atoms.append('=%s' % new_package)
315 messages.append(_GIT_COMMIT_MESSAGE % ebuild.package)
316 except (OSError, IOError):
317 logging.warning(
318 'Cannot rev %s\n'
319 'Note you will have to go into %s '
320 'and reset the git repo yourself.' % (ebuild.package, overlay))
321 raise
David James15ed1302013-04-25 09:21:19 -0700322
David James41124af2015-06-04 21:13:25 -0700323 if messages:
324 portage_util.EBuild.CommitChange('\n\n'.join(messages), overlay)
David James15ed1302013-04-25 09:21:19 -0700325
Mike Frysingere8dcbfd2015-03-08 21:45:52 -0400326 if options.command == 'commit':
David James41124af2015-06-04 21:13:25 -0700327 chroot_path = os.path.join(options.srcroot, constants.DEFAULT_CHROOT_DIR)
328 if os.path.exists(chroot_path):
329 CleanStalePackages(options.srcroot, options.boards.split(':'),
330 new_package_atoms)
David James15ed1302013-04-25 09:21:19 -0700331 if options.drop_file:
332 osutils.WriteFile(options.drop_file, ' '.join(revved_packages))