Upstream version 8.36.161.0
[platform/framework/web/crosswalk.git] / src / third_party / chromite / scripts / cros_mark_as_stable.py
1 #!/usr/bin/python
2
3 # Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
4 # 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
9 import optparse
10 import os
11 import sys
12
13 from chromite.buildbot import constants
14 from chromite.buildbot import portage_utilities
15 from chromite.lib import cros_build_lib
16 from chromite.lib import git
17 from chromite.lib import osutils
18 from chromite.lib import parallel
19
20
21 # Commit message for uprevving Portage packages.
22 _GIT_COMMIT_MESSAGE = 'Marking 9999 ebuild for %s as stable.'
23
24 # Dictionary of valid commands with usage information.
25 COMMAND_DICTIONARY = {
26     'commit': 'Marks given ebuilds as stable locally',
27     'push': 'Pushes previous marking of ebuilds to remote repo',
28 }
29
30
31 # ======================= Global Helper Functions ========================
32
33
34 def CleanStalePackages(boards, package_atoms):
35   """Cleans up stale package info from a previous build.
36
37   Args:
38     boards: Boards to clean the packages from.
39     package_atoms: A list of package atoms to unmerge.
40   """
41   if package_atoms:
42     cros_build_lib.Info('Cleaning up stale packages %s.' % package_atoms)
43
44   # First unmerge all the packages for a board, then eclean it.
45   # We need these two steps to run in order (unmerge/eclean),
46   # but we can let all the boards run in parallel.
47   def _CleanStalePackages(board):
48     if board:
49       suffix = '-' + board
50       runcmd = cros_build_lib.RunCommand
51     else:
52       suffix = ''
53       runcmd = cros_build_lib.SudoRunCommand
54
55     emerge, eclean = 'emerge' + suffix, 'eclean' + suffix
56     if not osutils.FindMissingBinaries([emerge, eclean]):
57       if package_atoms:
58         # If nothing was found to be unmerged, emerge will exit(1).
59         result = runcmd([emerge, '-q', '--unmerge'] + package_atoms,
60                         extra_env={'CLEAN_DELAY': '0'}, error_code_ok=True)
61         if not result.returncode in (0, 1):
62           raise cros_build_lib.RunCommandError('unexpected error', result)
63       runcmd([eclean, '-d', 'packages'],
64              redirect_stdout=True, redirect_stderr=True)
65
66   tasks = []
67   for board in boards:
68     tasks.append([board])
69   tasks.append([None])
70
71   parallel.RunTasksInProcessPool(_CleanStalePackages, tasks)
72
73
74 # TODO(build): This code needs to be gutted and rebased to cros_build_lib.
75 def _DoWeHaveLocalCommits(stable_branch, tracking_branch, cwd):
76   """Returns true if there are local commits."""
77   current_branch = git.GetCurrentBranch(cwd)
78
79   if current_branch != stable_branch:
80     return False
81   output = git.RunGit(
82       cwd, ['rev-parse', 'HEAD', tracking_branch]).output.split()
83   return output[0] != output[1]
84
85
86 def _CheckSaneArguments(command, options):
87   """Checks to make sure the flags are sane.  Dies if arguments are not sane."""
88   if not command in COMMAND_DICTIONARY.keys():
89     _PrintUsageAndDie('%s is not a valid command' % command)
90   if not options.packages and command == 'commit' and not options.all:
91     _PrintUsageAndDie('Please specify at least one package')
92   if options.boards:
93     cros_build_lib.AssertInsideChroot()
94   if not os.path.isdir(options.srcroot):
95     _PrintUsageAndDie('srcroot is not a valid path')
96   options.srcroot = os.path.abspath(options.srcroot)
97
98
99 def _PrintUsageAndDie(error_message=''):
100   """Prints optional error_message the usage and returns an error exit code."""
101   command_usage = 'Commands: \n'
102   # Add keys and usage information from dictionary.
103   commands = sorted(COMMAND_DICTIONARY.keys())
104   for command in commands:
105     command_usage += '  %s: %s\n' % (command, COMMAND_DICTIONARY[command])
106   commands_str = '|'.join(commands)
107   cros_build_lib.Warning('Usage: %s FLAGS [%s]\n\n%s' % (
108       sys.argv[0], commands_str, command_usage))
109   if error_message:
110     cros_build_lib.Die(error_message)
111   else:
112     sys.exit(1)
113
114
115 # ======================= End Global Helper Functions ========================
116
117
118 def PushChange(stable_branch, tracking_branch, dryrun, cwd):
119   """Pushes commits in the stable_branch to the remote git repository.
120
121   Pushes local commits from calls to CommitChange to the remote git
122   repository specified by current working directory. If changes are
123   found to commit, they will be merged to the merge branch and pushed.
124   In that case, the local repository will be left on the merge branch.
125
126   Args:
127     stable_branch: The local branch with commits we want to push.
128     tracking_branch: The tracking branch of the local branch.
129     dryrun: Use git push --dryrun to emulate a push.
130     cwd: The directory to run commands in.
131
132   Raises:
133     OSError: Error occurred while pushing.
134   """
135   if not _DoWeHaveLocalCommits(stable_branch, tracking_branch, cwd):
136     cros_build_lib.Info('No work found to push in %s.  Exiting', cwd)
137     return
138
139   # 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.
142   remote, push_branch = git.GetTrackingBranch(cwd, for_push=True)
143   git.SyncPushBranch(cwd, remote, push_branch)
144
145   # Check whether any local changes remain after the sync.
146   if not _DoWeHaveLocalCommits(stable_branch, push_branch, cwd):
147     cros_build_lib.Info('All changes already pushed for %s. Exiting', cwd)
148     return
149
150   # Add a failsafe check here.  Only CLs from the 'chrome-bot' user should
151   # be involved here.  If any other CLs are found then complain.
152   # In dryruns extra CLs are normal, though, and can be ignored.
153   bad_cl_cmd = ['log', '--format=short', '--perl-regexp',
154                 '--author', '^(?!chrome-bot)', '%s..%s' % (
155                     push_branch, stable_branch)]
156   bad_cls = git.RunGit(cwd, bad_cl_cmd).output
157   if bad_cls.strip() and not dryrun:
158     cros_build_lib.Error('The Uprev stage found changes from users other'
159                          ' than chrome-bot:\n\n%s', bad_cls)
160     raise AssertionError('Unexpected CLs found during uprev stage.')
161
162   description = git.RunGit(cwd,
163       ['log', '--format=format:%s%n%n%b', '%s..%s' % (
164        push_branch, stable_branch)]).output
165   description = 'Marking set of ebuilds as stable\n\n%s' % description
166   cros_build_lib.Info('For %s, using description %s', cwd, description)
167   git.CreatePushBranch(constants.MERGE_BRANCH, cwd)
168   git.RunGit(cwd, ['merge', '--squash', stable_branch])
169   git.RunGit(cwd, ['commit', '-m', description])
170   git.RunGit(cwd, ['config', 'push.default', 'tracking'])
171   git.PushWithRetry(constants.MERGE_BRANCH, cwd, dryrun=dryrun)
172
173
174 class GitBranch(object):
175   """Wrapper class for a git branch."""
176
177   def __init__(self, branch_name, tracking_branch, cwd):
178     """Sets up variables but does not create the branch.
179
180     Args:
181       branch_name: The name of the branch.
182       tracking_branch: The associated tracking branch.
183       cwd: The git repository to work in.
184     """
185     self.branch_name = branch_name
186     self.tracking_branch = tracking_branch
187     self.cwd = cwd
188
189   def CreateBranch(self):
190     self.Checkout()
191
192   def Checkout(self, branch=None):
193     """Function used to check out to another GitBranch."""
194     if not branch:
195       branch = self.branch_name
196     if branch == self.tracking_branch or self.Exists(branch):
197       git_cmd = ['git', 'checkout', '-f', branch]
198     else:
199       git_cmd = ['repo', 'start', branch, '.']
200     cros_build_lib.RunCommand(git_cmd, print_cmd=False, cwd=self.cwd,
201                               capture_output=True)
202
203   def Exists(self, branch=None):
204     """Returns True if the branch exists."""
205     if not branch:
206       branch = self.branch_name
207     branches = git.RunGit(self.cwd, ['branch']).output
208     return branch in branches.split()
209
210
211 def main(_argv):
212   parser = optparse.OptionParser('cros_mark_as_stable OPTIONS packages')
213   parser.add_option('--all', action='store_true',
214                     help='Mark all packages as stable.')
215   parser.add_option('-b', '--boards', default='',
216                     help='Colon-separated list of boards')
217   parser.add_option('--drop_file',
218                     help='File to list packages that were revved.')
219   parser.add_option('--dryrun', action='store_true',
220                     help='Passes dry-run to git push if pushing a change.')
221   parser.add_option('-o', '--overlays',
222                     help='Colon-separated list of overlays to modify.')
223   parser.add_option('-p', '--packages',
224                     help='Colon separated list of packages to rev.')
225   parser.add_option('-r', '--srcroot',
226                     default=os.path.join(constants.SOURCE_ROOT, 'src'),
227                     help='Path to root src directory.')
228   parser.add_option('--verbose', action='store_true',
229                     help='Prints out debug info.')
230   (options, args) = parser.parse_args()
231
232   portage_utilities.EBuild.VERBOSE = options.verbose
233
234   if len(args) != 1:
235     _PrintUsageAndDie('Must specify a valid command [commit, push]')
236
237   command = args[0]
238   package_list = None
239   if options.packages:
240     package_list = options.packages.split(':')
241
242   _CheckSaneArguments(command, options)
243   if options.overlays:
244     overlays = {}
245     for path in options.overlays.split(':'):
246       if not os.path.isdir(path):
247         cros_build_lib.Die('Cannot find overlay: %s' % path)
248       overlays[path] = []
249   else:
250     cros_build_lib.Warning('Missing --overlays argument')
251     overlays = {
252       '%s/private-overlays/chromeos-overlay' % options.srcroot: [],
253       '%s/third_party/chromiumos-overlay' % options.srcroot: []
254     }
255
256   manifest = git.ManifestCheckout.Cached(options.srcroot)
257
258   if command == 'commit':
259     portage_utilities.BuildEBuildDictionary(overlays, options.all, package_list)
260
261   # Contains the array of packages we actually revved.
262   revved_packages = []
263   new_package_atoms = []
264
265   # Slight optimization hack: process the chromiumos overlay before any other
266   # cros-workon overlay first so we can do background cache generation in it.
267   # A perfect solution would walk all the overlays, figure out any dependencies
268   # between them (with layout.conf), and then process them in dependency order.
269   # However, this operation isn't slow enough to warrant that level of
270   # complexity, so we'll just special case the main overlay.
271   #
272   # Similarly, generate the cache in the portage-stable tree asap.  We know
273   # we won't have any cros-workon packages in there, so generating the cache
274   # is the only thing it'll be doing.  The chromiumos overlay instead might
275   # have revbumping to do before it can generate the cache.
276   keys = overlays.keys()
277   for overlay in ('/third_party/chromiumos-overlay',
278                   '/third_party/portage-stable'):
279     for k in keys:
280       if k.endswith(overlay):
281         keys.remove(k)
282         keys.insert(0, k)
283         break
284
285   with parallel.BackgroundTaskRunner(portage_utilities.RegenCache) as queue:
286     for overlay in keys:
287       ebuilds = overlays[overlay]
288       if not os.path.isdir(overlay):
289         cros_build_lib.Warning('Skipping %s' % overlay)
290         continue
291
292       # Note we intentionally work from the non push tracking branch;
293       # everything built thus far has been against it (meaning, http mirrors),
294       # thus we should honor that.  During the actual push, the code switches
295       # to the correct urls, and does an appropriate rebasing.
296       tracking_branch = git.GetTrackingBranchViaManifest(
297           overlay, manifest=manifest)[1]
298
299       if command == 'push':
300         PushChange(constants.STABLE_EBUILD_BRANCH, tracking_branch,
301                    options.dryrun, cwd=overlay)
302       elif command == 'commit':
303         existing_commit = git.GetGitRepoRevision(overlay)
304         work_branch = GitBranch(constants.STABLE_EBUILD_BRANCH, tracking_branch,
305                                 cwd=overlay)
306         work_branch.CreateBranch()
307         if not work_branch.Exists():
308           cros_build_lib.Die('Unable to create stabilizing branch in %s' %
309                              overlay)
310
311         # In the case of uprevving overlays that have patches applied to them,
312         # include the patched changes in the stabilizing branch.
313         git.RunGit(overlay, ['rebase', existing_commit])
314
315         messages = []
316         for ebuild in ebuilds:
317           if options.verbose:
318             cros_build_lib.Info('Working on %s', ebuild.package)
319           try:
320             new_package = ebuild.RevWorkOnEBuild(options.srcroot, manifest)
321             if new_package:
322               revved_packages.append(ebuild.package)
323               new_package_atoms.append('=%s' % new_package)
324               messages.append(_GIT_COMMIT_MESSAGE % ebuild.package)
325           except (OSError, IOError):
326             cros_build_lib.Warning(
327                 'Cannot rev %s\n'
328                 'Note you will have to go into %s '
329                 'and reset the git repo yourself.' % (ebuild.package, overlay))
330             raise
331
332         if messages:
333           portage_utilities.EBuild.CommitChange('\n\n'.join(messages), overlay)
334
335         if cros_build_lib.IsInsideChroot():
336           # Regenerate caches if need be.  We do this all the time to
337           # catch when users make changes without updating cache files.
338           queue.put([overlay])
339
340   if command == 'commit':
341     if cros_build_lib.IsInsideChroot():
342       CleanStalePackages(options.boards.split(':'), new_package_atoms)
343     if options.drop_file:
344       osutils.WriteFile(options.drop_file, ' '.join(revved_packages))