| # Copyright (C) 2012 The Android Open Source Project |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| """Common data/functions for the Chromium merging scripts.""" |
| |
| import logging |
| import os |
| import re |
| import subprocess |
| |
| |
| REPOSITORY_ROOT = os.path.join(os.environ['ANDROID_BUILD_TOP'], |
| 'external/chromium_org') |
| |
| |
| # Whitelist of projects that need to be merged to build WebView. We don't need |
| # the other upstream repositories used to build the actual Chrome app. |
| # Different stages of the merge process need different ways of looking at the |
| # list, so we construct different combinations below. |
| |
| THIRD_PARTY_PROJECTS_WITH_FLAT_HISTORY = [ |
| 'third_party/WebKit', |
| ] |
| |
| THIRD_PARTY_PROJECTS_WITH_FULL_HISTORY = [ |
| 'sdch/open-vcdiff', |
| 'testing/gtest', |
| 'third_party/angle', |
| 'third_party/brotli/src', |
| 'third_party/eyesfree/src/android/java/src/com/googlecode/eyesfree/braille', |
| 'third_party/freetype', |
| 'third_party/icu', |
| 'third_party/leveldatabase/src', |
| 'third_party/libaddressinput/src', |
| 'third_party/libjingle/source/talk', |
| 'third_party/libphonenumber/src/phonenumbers', |
| 'third_party/libphonenumber/src/resources', |
| 'third_party/libsrtp', |
| 'third_party/libvpx', |
| 'third_party/libyuv', |
| 'third_party/mesa/src', |
| 'third_party/openmax_dl', |
| 'third_party/openssl', |
| 'third_party/opus/src', |
| 'third_party/ots', |
| 'third_party/sfntly/cpp/src', |
| 'third_party/skia', |
| 'third_party/smhasher/src', |
| 'third_party/usrsctp/usrsctplib', |
| 'third_party/webrtc', |
| 'third_party/yasm/source/patched-yasm', |
| 'tools/grit', |
| 'tools/gyp', |
| 'v8', |
| ] |
| |
| PROJECTS_WITH_FLAT_HISTORY = ['.'] + THIRD_PARTY_PROJECTS_WITH_FLAT_HISTORY |
| PROJECTS_WITH_FULL_HISTORY = THIRD_PARTY_PROJECTS_WITH_FULL_HISTORY |
| |
| THIRD_PARTY_PROJECTS = (THIRD_PARTY_PROJECTS_WITH_FLAT_HISTORY + |
| THIRD_PARTY_PROJECTS_WITH_FULL_HISTORY) |
| |
| ALL_PROJECTS = ['.'] + THIRD_PARTY_PROJECTS |
| |
| |
| # Directories to be removed when flattening history. |
| PRUNE_WHEN_FLATTENING = { |
| 'third_party/WebKit': [ |
| 'LayoutTests', |
| ], |
| } |
| |
| |
| # Only projects that have their history flattened can have directories pruned. |
| assert all(p in PROJECTS_WITH_FLAT_HISTORY for p in PRUNE_WHEN_FLATTENING) |
| |
| |
| class MergeError(Exception): |
| """Used to signal an error that prevents the merge from being completed.""" |
| |
| |
| class CommandError(MergeError): |
| """This exception is raised when a process run by GetCommandStdout fails.""" |
| |
| def __init__(self, returncode, cmd, cwd, stdout, stderr): |
| super(CommandError, self).__init__() |
| self.returncode = returncode |
| self.cmd = cmd |
| self.cwd = cwd |
| self.stdout = stdout |
| self.stderr = stderr |
| |
| def __str__(self): |
| return ("Command '%s' returned non-zero exit status %d. cwd was '%s'.\n\n" |
| "===STDOUT===\n%s\n===STDERR===\n%s\n" % |
| (self.cmd, self.returncode, self.cwd, self.stdout, self.stderr)) |
| |
| |
| class TemporaryMergeError(MergeError): |
| """A merge error that can potentially be resolved by trying again later.""" |
| |
| |
| def GetCommandStdout(args, cwd=REPOSITORY_ROOT, ignore_errors=False): |
| """Gets stdout from runnng the specified shell command. |
| |
| Similar to subprocess.check_output() except that it can capture stdout and |
| stderr separately for better error reporting. |
| |
| Args: |
| args: The command and its arguments as an iterable. |
| cwd: The working directory to use. Defaults to REPOSITORY_ROOT. |
| ignore_errors: Ignore the command's return code and stderr. |
| Returns: |
| stdout from running the command. |
| Raises: |
| CommandError: if the command exited with a nonzero status. |
| """ |
| p = subprocess.Popen(args=args, cwd=cwd, stdout=subprocess.PIPE, |
| stderr=subprocess.PIPE) |
| stdout, stderr = p.communicate() |
| if p.returncode == 0 or ignore_errors: |
| return stdout |
| else: |
| raise CommandError(p.returncode, ' '.join(args), cwd, stdout, stderr) |
| |
| |
| def CheckNoConflictsAndCommitMerge(commit_message, unattended=False, |
| cwd=REPOSITORY_ROOT): |
| """Checks for conflicts and commits once they are resolved. |
| |
| Certain conflicts are resolved automatically; if any remain, the user is |
| prompted to resolve them. The user can specify a custom commit message. |
| |
| Args: |
| commit_message: The default commit message. |
| unattended: If running unattended, abort on conflicts. |
| cwd: Working directory to use. |
| Raises: |
| TemporaryMergeError: If there are conflicts in unattended mode. |
| """ |
| status = GetCommandStdout(['git', 'status', '--porcelain'], cwd=cwd) |
| conflicts_deleted_by_us = re.findall(r'^(?:DD|DU) ([^\n]+)$', status, |
| flags=re.MULTILINE) |
| if conflicts_deleted_by_us: |
| logging.info('Keeping ours for the following locally deleted files.\n %s', |
| '\n '.join(conflicts_deleted_by_us)) |
| GetCommandStdout(['git', 'rm', '-rf', '--ignore-unmatch'] + |
| conflicts_deleted_by_us, cwd=cwd) |
| |
| # If upstream renames a file we have deleted then it will conflict, but |
| # we shouldn't just blindly delete these files as they may have been renamed |
| # into a directory we don't delete. Let them get re-added; they will get |
| # re-deleted if they are still in a directory we delete. |
| conflicts_renamed_by_them = re.findall(r'^UA ([^\n]+)$', status, |
| flags=re.MULTILINE) |
| if conflicts_renamed_by_them: |
| logging.info('Adding theirs for the following locally deleted files.\n %s', |
| '\n '.join(conflicts_renamed_by_them)) |
| GetCommandStdout(['git', 'add', '-f'] + conflicts_renamed_by_them, cwd=cwd) |
| |
| while True: |
| status = GetCommandStdout(['git', 'status', '--porcelain'], cwd=cwd) |
| conflicts = re.findall(r'^((DD|AU|UD|UA|DU|AA|UU) [^\n]+)$', status, |
| flags=re.MULTILINE) |
| if not conflicts: |
| break |
| if unattended: |
| GetCommandStdout(['git', 'reset', '--hard'], cwd=cwd) |
| raise TemporaryMergeError('Cannot resolve merge conflicts.') |
| conflicts_string = '\n'.join([x[0] for x in conflicts]) |
| new_commit_message = raw_input( |
| ('The following conflicts exist and must be resolved.\n\n%s\n\nWhen ' |
| 'done, enter a commit message or press enter to use the default ' |
| '(\'%s\').\n\n') % (conflicts_string, commit_message)) |
| if new_commit_message: |
| commit_message = new_commit_message |
| |
| GetCommandStdout(['git', 'commit', '-m', commit_message], cwd=cwd) |