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 17import logging 18import os 19import re 20import subprocess 21 22 23REPOSITORY_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 32THIRD_PARTY_PROJECTS_WITH_FLAT_HISTORY = [ 33 'third_party/WebKit', 34] 35 36THIRD_PARTY_PROJECTS_WITH_FULL_HISTORY = [ 37 'sdch/open-vcdiff', 38 'testing/gtest', 39 'third_party/angle', 40 'third_party/boringssl/src', 41 'third_party/brotli/src', 42 'third_party/eyesfree/src/android/java/src/com/googlecode/eyesfree/braille', 43 'third_party/freetype', 44 'third_party/icu', 45 'third_party/leveldatabase/src', 46 'third_party/libaddressinput/src', 47 'third_party/libjingle/source/talk', 48 'third_party/libjpeg_turbo', 49 'third_party/libphonenumber/src/phonenumbers', 50 'third_party/libphonenumber/src/resources', 51 'third_party/libsrtp', 52 'third_party/libvpx', 53 'third_party/libyuv', 54 'third_party/mesa/src', 55 'third_party/openmax_dl', 56 'third_party/opus/src', 57 'third_party/ots', 58 'third_party/sfntly/cpp/src', 59 'third_party/skia', 60 'third_party/smhasher/src', 61 'third_party/usrsctp/usrsctplib', 62 'third_party/webrtc', 63 'third_party/yasm/source/patched-yasm', 64 'tools/grit', 65 'tools/gyp', 66 'v8', 67] 68 69PROJECTS_WITH_FLAT_HISTORY = ['.'] + THIRD_PARTY_PROJECTS_WITH_FLAT_HISTORY 70PROJECTS_WITH_FULL_HISTORY = THIRD_PARTY_PROJECTS_WITH_FULL_HISTORY 71 72THIRD_PARTY_PROJECTS = (THIRD_PARTY_PROJECTS_WITH_FLAT_HISTORY + 73 THIRD_PARTY_PROJECTS_WITH_FULL_HISTORY) 74 75ALL_PROJECTS = ['.'] + THIRD_PARTY_PROJECTS 76assert(set(ALL_PROJECTS) == 77 set(PROJECTS_WITH_FLAT_HISTORY + PROJECTS_WITH_FULL_HISTORY)) 78 79# Directories to be removed when flattening history. 80PRUNE_WHEN_FLATTENING = { 81 'third_party/WebKit': [ 82 'LayoutTests', 83 ], 84} 85 86 87# Only projects that have their history flattened can have directories pruned. 88assert all(p in PROJECTS_WITH_FLAT_HISTORY for p in PRUNE_WHEN_FLATTENING) 89 90 91class MergeError(Exception): 92 """Used to signal an error that prevents the merge from being completed.""" 93 94 95class CommandError(MergeError): 96 """This exception is raised when a process run by GetCommandStdout fails.""" 97 98 def __init__(self, returncode, cmd, cwd, stdout, stderr): 99 super(CommandError, self).__init__() 100 self.returncode = returncode 101 self.cmd = cmd 102 self.cwd = cwd 103 self.stdout = stdout 104 self.stderr = stderr 105 106 def __str__(self): 107 return ("Command '%s' returned non-zero exit status %d. cwd was '%s'.\n\n" 108 "===STDOUT===\n%s\n===STDERR===\n%s\n" % 109 (self.cmd, self.returncode, self.cwd, self.stdout, self.stderr)) 110 111 112class TemporaryMergeError(MergeError): 113 """A merge error that can potentially be resolved by trying again later.""" 114 115 116def Abbrev(commitish): 117 """Returns the abbrev commitish for a given Git SHA.""" 118 return commitish[:12] 119 120 121def GetCommandStdout(args, cwd=REPOSITORY_ROOT, ignore_errors=False): 122 """Gets stdout from runnng the specified shell command. 123 124 Similar to subprocess.check_output() except that it can capture stdout and 125 stderr separately for better error reporting. 126 127 Args: 128 args: The command and its arguments as an iterable. 129 cwd: The working directory to use. Defaults to REPOSITORY_ROOT. 130 ignore_errors: Ignore the command's return code and stderr. 131 Returns: 132 A concatenation of stdout + stderr from running the command. 133 Raises: 134 CommandError: if the command exited with a nonzero status. 135 """ 136 p = subprocess.Popen(args=args, cwd=cwd, stdout=subprocess.PIPE, 137 stderr=subprocess.PIPE) 138 stdout, stderr = p.communicate() 139 output = stdout + ('\n===STDERR===\n' if stderr and stdout else '') + stderr 140 if p.returncode == 0 or ignore_errors: 141 return output 142 else: 143 raise CommandError(p.returncode, ' '.join(args), cwd, stdout, stderr) 144 145 146def CheckNoConflictsAndCommitMerge(commit_message, unattended=False, 147 cwd=REPOSITORY_ROOT): 148 """Checks for conflicts and commits once they are resolved. 149 150 Certain conflicts are resolved automatically; if any remain, the user is 151 prompted to resolve them. The user can specify a custom commit message. 152 153 Args: 154 commit_message: The default commit message. 155 unattended: If running unattended, abort on conflicts. 156 cwd: Working directory to use. 157 Raises: 158 TemporaryMergeError: If there are conflicts in unattended mode. 159 """ 160 status = GetCommandStdout(['git', 'status', '--porcelain'], cwd=cwd) 161 conflicts_deleted_by_us = re.findall(r'^(?:DD|DU) ([^\n]+)$', status, 162 flags=re.MULTILINE) 163 if conflicts_deleted_by_us: 164 logging.info('Keeping ours for the following locally deleted files.\n %s', 165 '\n '.join(conflicts_deleted_by_us)) 166 GetCommandStdout(['git', 'rm', '-rf', '--ignore-unmatch'] + 167 conflicts_deleted_by_us, cwd=cwd) 168 169 # If upstream renames a file we have deleted then it will conflict, but 170 # we shouldn't just blindly delete these files as they may have been renamed 171 # into a directory we don't delete. Let them get re-added; they will get 172 # re-deleted if they are still in a directory we delete. 173 conflicts_renamed_by_them = re.findall(r'^UA ([^\n]+)$', status, 174 flags=re.MULTILINE) 175 if conflicts_renamed_by_them: 176 logging.info('Adding theirs for the following locally deleted files.\n %s', 177 '\n '.join(conflicts_renamed_by_them)) 178 GetCommandStdout(['git', 'add', '-f'] + conflicts_renamed_by_them, cwd=cwd) 179 180 while True: 181 status = GetCommandStdout(['git', 'status', '--porcelain'], cwd=cwd) 182 conflicts = re.findall(r'^((DD|AU|UD|UA|DU|AA|UU) [^\n]+)$', status, 183 flags=re.MULTILINE) 184 if not conflicts: 185 break 186 if unattended: 187 GetCommandStdout(['git', 'reset', '--hard'], cwd=cwd) 188 raise TemporaryMergeError('Cannot resolve merge conflicts.') 189 conflicts_string = '\n'.join([x[0] for x in conflicts]) 190 new_commit_message = raw_input( 191 ('The following conflicts exist and must be resolved.\n\n%s\n\nWhen ' 192 'done, enter a commit message or press enter to use the default ' 193 '(\'%s\').\n\n') % (conflicts_string, commit_message)) 194 if new_commit_message: 195 commit_message = new_commit_message 196 197 GetCommandStdout(['git', 'commit', '-m', commit_message], cwd=cwd) 198