Home | History | Annotate | Download | only in tools
      1 # Copyright (C) 2012 The Android Open Source Project
      2 #
      3 # Licensed under the Apache License, Version 2.0 (the "License");
      4 # you may not use this file except in compliance with the License.
      5 # You may obtain a copy of the License at
      6 #
      7 #      http://www.apache.org/licenses/LICENSE-2.0
      8 #
      9 # Unless required by applicable law or agreed to in writing, software
     10 # distributed under the License is distributed on an "AS IS" BASIS,
     11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 # See the License for the specific language governing permissions and
     13 # limitations under the License.
     14 
     15 """Common data/functions for the Chromium merging scripts."""
     16 
     17 import logging
     18 import os
     19 import re
     20 import subprocess
     21 
     22 
     23 REPOSITORY_ROOT = os.path.join(os.environ['ANDROID_BUILD_TOP'],
     24                                'external/chromium_org')
     25 
     26 
     27 # Whitelist of projects that need to be merged to build WebView. We don't need
     28 # the other upstream repositories used to build the actual Chrome app.
     29 # Different stages of the merge process need different ways of looking at the
     30 # list, so we construct different combinations below.
     31 
     32 THIRD_PARTY_PROJECTS_WITH_FLAT_HISTORY = [
     33     'third_party/WebKit',
     34 ]
     35 
     36 THIRD_PARTY_PROJECTS_WITH_FULL_HISTORY = [
     37     'sdch/open-vcdiff',
     38     'testing/gtest',
     39     'third_party/angle',
     40     'third_party/eyesfree/src/android/java/src/com/googlecode/eyesfree/braille',
     41     'third_party/freetype',
     42     'third_party/icu',
     43     'third_party/leveldatabase/src',
     44     'third_party/libjingle/source/talk',
     45     'third_party/libphonenumber/src/phonenumbers',
     46     'third_party/libphonenumber/src/resources',
     47     'third_party/mesa/src',
     48     'third_party/openssl',
     49     'third_party/opus/src',
     50     'third_party/ots',
     51     'third_party/sfntly/cpp/src',
     52     'third_party/skia/include',
     53     'third_party/skia/gyp',
     54     'third_party/skia/src',
     55     'third_party/smhasher/src',
     56     'third_party/yasm/source/patched-yasm',
     57     'tools/grit',
     58     'tools/gyp',
     59     'v8',
     60 ]
     61 
     62 PROJECTS_WITH_FLAT_HISTORY = ['.'] + THIRD_PARTY_PROJECTS_WITH_FLAT_HISTORY
     63 PROJECTS_WITH_FULL_HISTORY = THIRD_PARTY_PROJECTS_WITH_FULL_HISTORY
     64 
     65 THIRD_PARTY_PROJECTS = (THIRD_PARTY_PROJECTS_WITH_FLAT_HISTORY +
     66                         THIRD_PARTY_PROJECTS_WITH_FULL_HISTORY)
     67 
     68 ALL_PROJECTS = ['.'] + THIRD_PARTY_PROJECTS
     69 
     70 
     71 # Directories to be removed when flattening history.
     72 PRUNE_WHEN_FLATTENING = {
     73     'third_party/WebKit': [
     74         'LayoutTests',
     75     ],
     76 }
     77 
     78 
     79 # Only projects that have their history flattened can have directories pruned.
     80 assert all(p in PROJECTS_WITH_FLAT_HISTORY for p in PRUNE_WHEN_FLATTENING)
     81 
     82 
     83 class MergeError(Exception):
     84   """Used to signal an error that prevents the merge from being completed."""
     85 
     86 
     87 class CommandError(MergeError):
     88   """This exception is raised when a process run by GetCommandStdout fails."""
     89 
     90   def __init__(self, returncode, cmd, cwd, stdout, stderr):
     91     super(CommandError, self).__init__()
     92     self.returncode = returncode
     93     self.cmd = cmd
     94     self.cwd = cwd
     95     self.stdout = stdout
     96     self.stderr = stderr
     97 
     98   def __str__(self):
     99     return ("Command '%s' returned non-zero exit status %d. cwd was '%s'.\n\n"
    100             "===STDOUT===\n%s\n===STDERR===\n%s\n" %
    101             (self.cmd, self.returncode, self.cwd, self.stdout, self.stderr))
    102 
    103 
    104 class TemporaryMergeError(MergeError):
    105   """A merge error that can potentially be resolved by trying again later."""
    106 
    107 
    108 def GetCommandStdout(args, cwd=REPOSITORY_ROOT, ignore_errors=False):
    109   """Gets stdout from runnng the specified shell command.
    110 
    111   Similar to subprocess.check_output() except that it can capture stdout and
    112   stderr separately for better error reporting.
    113 
    114   Args:
    115     args: The command and its arguments as an iterable.
    116     cwd: The working directory to use. Defaults to REPOSITORY_ROOT.
    117     ignore_errors: Ignore the command's return code and stderr.
    118   Returns:
    119     stdout from running the command.
    120   Raises:
    121     CommandError: if the command exited with a nonzero status.
    122   """
    123   p = subprocess.Popen(args=args, cwd=cwd, stdout=subprocess.PIPE,
    124                        stderr=subprocess.PIPE)
    125   stdout, stderr = p.communicate()
    126   if p.returncode == 0 or ignore_errors:
    127     return stdout
    128   else:
    129     raise CommandError(p.returncode, ' '.join(args), cwd, stdout, stderr)
    130 
    131 
    132 def CheckNoConflictsAndCommitMerge(commit_message, unattended=False,
    133                                    cwd=REPOSITORY_ROOT):
    134   """Checks for conflicts and commits once they are resolved.
    135 
    136   Certain conflicts are resolved automatically; if any remain, the user is
    137   prompted to resolve them. The user can specify a custom commit message.
    138 
    139   Args:
    140     commit_message: The default commit message.
    141     unattended: If running unattended, abort on conflicts.
    142     cwd: Working directory to use.
    143   Raises:
    144     TemporaryMergeError: If there are conflicts in unattended mode.
    145   """
    146   status = GetCommandStdout(['git', 'status', '--porcelain'], cwd=cwd)
    147   conflicts_deleted_by_us = re.findall(r'^(?:DD|DU) ([^\n]+)$', status,
    148                                        flags=re.MULTILINE)
    149   if conflicts_deleted_by_us:
    150     logging.info('Keeping ours for the following locally deleted files.\n  %s',
    151                  '\n  '.join(conflicts_deleted_by_us))
    152     GetCommandStdout(['git', 'rm', '-rf', '--ignore-unmatch'] +
    153                      conflicts_deleted_by_us, cwd=cwd)
    154 
    155   # If upstream renames a file we have deleted then it will conflict, but
    156   # we shouldn't just blindly delete these files as they may have been renamed
    157   # into a directory we don't delete. Let them get re-added; they will get
    158   # re-deleted if they are still in a directory we delete.
    159   conflicts_renamed_by_them = re.findall(r'^UA ([^\n]+)$', status,
    160                                          flags=re.MULTILINE)
    161   if conflicts_renamed_by_them:
    162     logging.info('Adding theirs for the following locally deleted files.\n %s',
    163                  '\n  '.join(conflicts_renamed_by_them))
    164     GetCommandStdout(['git', 'add', '-f'] + conflicts_renamed_by_them, cwd=cwd)
    165 
    166   while True:
    167     status = GetCommandStdout(['git', 'status', '--porcelain'], cwd=cwd)
    168     conflicts = re.findall(r'^((DD|AU|UD|UA|DU|AA|UU) [^\n]+)$', status,
    169                            flags=re.MULTILINE)
    170     if not conflicts:
    171       break
    172     if unattended:
    173       GetCommandStdout(['git', 'reset', '--hard'], cwd=cwd)
    174       raise TemporaryMergeError('Cannot resolve merge conflicts.')
    175     conflicts_string = '\n'.join([x[0] for x in conflicts])
    176     new_commit_message = raw_input(
    177         ('The following conflicts exist and must be resolved.\n\n%s\n\nWhen '
    178          'done, enter a commit message or press enter to use the default '
    179          '(\'%s\').\n\n') % (conflicts_string, commit_message))
    180     if new_commit_message:
    181       commit_message = new_commit_message
    182 
    183   GetCommandStdout(['git', 'commit', '-m', commit_message], cwd=cwd)
    184