1#!/usr/bin/python 2# 3# Copyright (C) 2017 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17""" 18Compares one or more corresponding files from ojluni against one or 19more upstream or from upstreams against each other. 20The repositories (default: ojluni vs. expected current upstream) and 21the diff tool (default: meld) can be specified by command line options. 22 23This tool is for libcore maintenance; if you're not maintaining libcore, 24you won't need it (and might not have access to some of the instructions 25below). 26 27The naming of the repositories (expected, ojluni, 7u40, 8u121-b13, 289b113+, 9+181) is based on the directory name where corresponding 29snapshots are stored when following the instructions at 30http://go/libcore-o-verify 31 32This in turn derives from the instructions at the top of: 33libcore/tools/upstream/src/main/java/libcore/CompareUpstreams.java 34 35Possible uses: 36 37To verify that ArrayList has been updated to the expected upstream 38and that all local patches carry change markers, we compare that 39file from ojluni against the expected upstream (the default): 40 upstream-diff java/util/ArrayList.java 41 42To verify multiple files: 43 upstream-diff java.util.ArrayList java.util.LinkedList 44 45To verify a folder: 46 upstream-diff java/util/concurrent 47 48To verify a package: 49 upstream-diff java.util.concurrent 50 51Use a three-way merge to integrate changes from 9+181 into ArrayList: 52 upstream-diff -r 8u121-b13,ojluni,9+181 java/util/ArrayList.java 53or to investigate which version of upstream introduced a change: 54 upstream-diff -r 7u40,8u60,8u121-b13 java/util/ArrayList.java 55""" 56 57import argparse 58import os 59import os.path 60import re 61import subprocess 62import sys 63 64 65def get_path_type(rel_path): 66 ext = os.path.splitext(rel_path)[-1] 67 # Check the extension and if it is a C/C++ extension then we're dealing 68 # with a native path. Otherwise we would be dealing with a java path, which 69 # can be a filename, folder name, package name, or fully qualified class 70 # name. 71 if re.match('\\.(c|cc|cpp|cxx|h|hpp|hxx|icc)$', ext): 72 return 'native' 73 return 'java' 74 75 76def normalize_java_path(rel_path): 77 if re.match('.+\\.java$', rel_path): 78 # Path ends in '.java' so a filename with its path is expected 79 return rel_path 80 81 if '/' not in rel_path: 82 # Convert package name, or fully qualified class name into path 83 rel_path = rel_path.replace('.', '/') 84 85 if any(c.isupper() for c in rel_path): 86 # If the name includes an uppercase character, we guess that this is a 87 # class rather than a package name, so the extension needs to be appended 88 # to get the full filename 89 # Note: Test packages may have upper case characters in the package name, 90 # so, if trying to diff a test package, the ".java" suffix will be added 91 # unnecessarily, causing a wrong diff input. Therefore, for test packages, 92 # the tool should be used for each file individually 93 rel_path += '.java' 94 elif rel_path[-1] != '/': 95 # No upper case characters, so this must be a folder/package 96 rel_path += '/' 97 98 return rel_path 99 100 101def run_diff(diff, repositories, rel_paths): 102 # Root of checked-out Android sources, set by the "lunch" command. 103 android_build_top = os.environ['ANDROID_BUILD_TOP'] 104 # Root of repository snapshots. See go/libcore-o-verify for how you'd want 105 # to set this. 106 ojluni_upstreams = os.environ['OJLUNI_UPSTREAMS'] 107 for rel_path in rel_paths: 108 path_type = get_path_type(rel_path) 109 if path_type == 'java': 110 rel_path = normalize_java_path(rel_path) 111 paths = [] 112 113 for repository in repositories: 114 if repository == 'ojluni': 115 paths.append('%s/libcore/ojluni/src/main/%s/%s' 116 % (android_build_top, path_type, rel_path)) 117 else: 118 paths.append('%s/%s/%s' % (ojluni_upstreams, repository, rel_path)) 119 subprocess.call([diff] + paths) 120 121 122def main(): 123 parser = argparse.ArgumentParser( 124 description='Compare files between libcore/ojluni and ${OJLUNI_UPSTREAMS}.', 125 formatter_class=argparse.ArgumentDefaultsHelpFormatter, # include default values in help 126 ) 127 upstreams = os.environ['OJLUNI_UPSTREAMS'] 128 # natsort.natsorted() would be a nicer sort order, but I'd rather avoid the dependency 129 repositories = ['ojluni'] + sorted( 130 [d for d in os.listdir(upstreams) if os.path.isdir(os.path.join(upstreams, d))] 131 ) 132 parser.add_argument('-r', '--repositories', default='ojluni,expected', 133 help='Comma-separated list of 2-3 repositories, to compare, in order; ' 134 'available repositories: ' + ' '.join(repositories) + '.') 135 parser.add_argument('-d', '--diff', default='meld', 136 help='Application to use for diffing.') 137 parser.add_argument('rel_path', nargs="+", 138 help='File to compare: either a relative path below libcore/ojluni/' 139 'src/main/{java,native}, or a fully qualified class name.') 140 args = parser.parse_args() 141 repositories = args.repositories.split(',') 142 if (len(repositories) < 2): 143 print('Expected >= 2 repositories to compare, got: ' + str(repositories)) 144 parser.print_help() 145 sys.exit(1) 146 run_diff(args.diff, repositories, args.rel_path) 147 148 149if __name__ == "__main__": 150 main() 151