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
44  upstream-diff java.util.ArrayList java.util.LinkedList
45
46Use a three-way merge to integrate changes from 9+181 into ArrayList:
47  upstream-diff -r 8u121-b13,ojluni,9+181 java/util/ArrayList.java
48or to investigate which version of upstream introduced a change:
49  upstream-diff -r 7u40,8u60,8u121-b13 java/util/ArrayList.java
50"""
51
52import argparse
53import os
54import os.path
55import re
56import subprocess
57import sys
58
59def run_diff(diff, repositories, rel_paths):
60    # Root of checked-out Android sources, set by the "lunch" command.
61    android_build_top = os.environ['ANDROID_BUILD_TOP']
62    # Root of repository snapshots. See go/libcore-o-verify for how you'd want to set this.
63    ojluni_upstreams = os.environ['OJLUNI_UPSTREAMS']
64    for rel_path in rel_paths:
65        # Paths end with a dot and lowercase file extension (.c, .java, ...) but
66        # fully qualified class names do not.
67        if ('/' not in rel_path) and (not re.match('.+\\.[a-z]{1,4}$', rel_path)):
68            # Assume a fully qualified class name
69            rel_path = rel_path.replace('.', '/') + '.java'
70        paths = []
71        for repository in repositories:
72            if repository == 'ojluni':
73                file_group = 'java/' if rel_path.endswith('.java') else 'native/'
74                paths.append('%s/libcore/ojluni/src/main/%s/%s'
75                             % (android_build_top, file_group, rel_path))
76            else:
77                paths.append('%s/%s/%s' % (ojluni_upstreams, repository, rel_path))
78        subprocess.call([diff] + paths)
79
80def main():
81    parser = argparse.ArgumentParser(
82        description='Compare files between libcore/ojluni and ${OJLUNI_UPSTREAMS}.',
83        formatter_class=argparse.ArgumentDefaultsHelpFormatter, # include default values in help
84    )
85    upstreams = os.environ['OJLUNI_UPSTREAMS']
86    # natsort.natsorted() would be a nicer sort order, but I'd rather avoid the dependency
87    repositories = ['ojluni'] + sorted(
88        [d for d in os.listdir(upstreams) if os.path.isdir(os.path.join(upstreams, d))]
89    )
90    parser.add_argument('-r', '--repositories', default='ojluni,expected',
91                    help='Comma-separated list of 2-3 repositories, to compare, in order; '
92                          'available repositories: ' + ' '.join(repositories) + '.')
93    parser.add_argument('-d', '--diff', default='meld',
94                        help='Application to use for diffing.')
95    parser.add_argument('rel_path', nargs="+",
96                        help='File to compare: either a relative path below libcore/ojluni/'
97                             'src/main/{java,native}, or a fully qualified class name.')
98    args = parser.parse_args()
99    repositories = args.repositories.split(',')
100    if (len(repositories) < 2):
101        print('Expected >= 2 repositories to compare, got: ' + str(repositories))
102        parser.print_help()
103        sys.exit(1)
104    run_diff(args.diff, repositories, args.rel_path)
105
106if __name__ == "__main__":
107    main()
108
109