xref: /aosp_15_r20/build/soong/scripts/hiddenapi/verify_overlaps.py (revision 333d2b3687b3a337dbcca9d65000bca186795e39)
1*333d2b36SAndroid Build Coastguard Worker#!/usr/bin/env python
2*333d2b36SAndroid Build Coastguard Worker#
3*333d2b36SAndroid Build Coastguard Worker# Copyright (C) 2018 The Android Open Source Project
4*333d2b36SAndroid Build Coastguard Worker#
5*333d2b36SAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
6*333d2b36SAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
7*333d2b36SAndroid Build Coastguard Worker# You may obtain a copy of the License at
8*333d2b36SAndroid Build Coastguard Worker#
9*333d2b36SAndroid Build Coastguard Worker#      http://www.apache.org/licenses/LICENSE-2.0
10*333d2b36SAndroid Build Coastguard Worker#
11*333d2b36SAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
12*333d2b36SAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
13*333d2b36SAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14*333d2b36SAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
15*333d2b36SAndroid Build Coastguard Worker# limitations under the License.
16*333d2b36SAndroid Build Coastguard Worker"""Verify that one set of hidden API flags is a subset of another."""
17*333d2b36SAndroid Build Coastguard Worker
18*333d2b36SAndroid Build Coastguard Workerimport argparse
19*333d2b36SAndroid Build Coastguard Workerimport csv
20*333d2b36SAndroid Build Coastguard Workerimport sys
21*333d2b36SAndroid Build Coastguard Workerfrom itertools import chain
22*333d2b36SAndroid Build Coastguard Worker
23*333d2b36SAndroid Build Coastguard Workerfrom signature_trie import signature_trie
24*333d2b36SAndroid Build Coastguard Worker
25*333d2b36SAndroid Build Coastguard Worker
26*333d2b36SAndroid Build Coastguard Workerdef dict_reader(csv_file):
27*333d2b36SAndroid Build Coastguard Worker    return csv.DictReader(
28*333d2b36SAndroid Build Coastguard Worker        csv_file, delimiter=",", quotechar="|", fieldnames=["signature"])
29*333d2b36SAndroid Build Coastguard Worker
30*333d2b36SAndroid Build Coastguard Worker
31*333d2b36SAndroid Build Coastguard Workerdef read_flag_trie_from_file(file):
32*333d2b36SAndroid Build Coastguard Worker    with open(file, "r", encoding="utf8") as stream:
33*333d2b36SAndroid Build Coastguard Worker        return read_flag_trie_from_stream(stream)
34*333d2b36SAndroid Build Coastguard Worker
35*333d2b36SAndroid Build Coastguard Worker
36*333d2b36SAndroid Build Coastguard Workerdef read_flag_trie_from_stream(stream):
37*333d2b36SAndroid Build Coastguard Worker    trie = signature_trie()
38*333d2b36SAndroid Build Coastguard Worker    reader = dict_reader(stream)
39*333d2b36SAndroid Build Coastguard Worker    for row in reader:
40*333d2b36SAndroid Build Coastguard Worker        signature = row["signature"]
41*333d2b36SAndroid Build Coastguard Worker        trie.add(signature, row)
42*333d2b36SAndroid Build Coastguard Worker    return trie
43*333d2b36SAndroid Build Coastguard Worker
44*333d2b36SAndroid Build Coastguard Worker
45*333d2b36SAndroid Build Coastguard Workerdef extract_subset_from_monolithic_flags_as_dict_from_file(
46*333d2b36SAndroid Build Coastguard Worker        monolithic_trie, patterns_file):
47*333d2b36SAndroid Build Coastguard Worker    """Extract a subset of flags from the dict of monolithic flags.
48*333d2b36SAndroid Build Coastguard Worker
49*333d2b36SAndroid Build Coastguard Worker    :param monolithic_trie: the trie containing all the monolithic flags.
50*333d2b36SAndroid Build Coastguard Worker    :param patterns_file: a file containing a list of signature patterns that
51*333d2b36SAndroid Build Coastguard Worker    define the subset.
52*333d2b36SAndroid Build Coastguard Worker    :return: the dict from signature to row.
53*333d2b36SAndroid Build Coastguard Worker    """
54*333d2b36SAndroid Build Coastguard Worker    with open(patterns_file, "r", encoding="utf8") as stream:
55*333d2b36SAndroid Build Coastguard Worker        return extract_subset_from_monolithic_flags_as_dict_from_stream(
56*333d2b36SAndroid Build Coastguard Worker            monolithic_trie, stream)
57*333d2b36SAndroid Build Coastguard Worker
58*333d2b36SAndroid Build Coastguard Worker
59*333d2b36SAndroid Build Coastguard Workerdef extract_subset_from_monolithic_flags_as_dict_from_stream(
60*333d2b36SAndroid Build Coastguard Worker        monolithic_trie, stream):
61*333d2b36SAndroid Build Coastguard Worker    """Extract a subset of flags from the trie of monolithic flags.
62*333d2b36SAndroid Build Coastguard Worker
63*333d2b36SAndroid Build Coastguard Worker    :param monolithic_trie: the trie containing all the monolithic flags.
64*333d2b36SAndroid Build Coastguard Worker    :param stream: a stream containing a list of signature patterns that define
65*333d2b36SAndroid Build Coastguard Worker    the subset.
66*333d2b36SAndroid Build Coastguard Worker    :return: the dict from signature to row.
67*333d2b36SAndroid Build Coastguard Worker    """
68*333d2b36SAndroid Build Coastguard Worker    dict_signature_to_row = {}
69*333d2b36SAndroid Build Coastguard Worker    for pattern in stream:
70*333d2b36SAndroid Build Coastguard Worker        pattern = pattern.rstrip()
71*333d2b36SAndroid Build Coastguard Worker        rows = monolithic_trie.get_matching_rows(pattern)
72*333d2b36SAndroid Build Coastguard Worker        for row in rows:
73*333d2b36SAndroid Build Coastguard Worker            signature = row["signature"]
74*333d2b36SAndroid Build Coastguard Worker            dict_signature_to_row[signature] = row
75*333d2b36SAndroid Build Coastguard Worker    return dict_signature_to_row
76*333d2b36SAndroid Build Coastguard Worker
77*333d2b36SAndroid Build Coastguard Worker
78*333d2b36SAndroid Build Coastguard Workerdef read_signature_csv_from_stream_as_dict(stream):
79*333d2b36SAndroid Build Coastguard Worker    """Read the csv contents from the stream into a dict.
80*333d2b36SAndroid Build Coastguard Worker
81*333d2b36SAndroid Build Coastguard Worker    The first column is assumed to be the signature and used as the
82*333d2b36SAndroid Build Coastguard Worker    key.
83*333d2b36SAndroid Build Coastguard Worker
84*333d2b36SAndroid Build Coastguard Worker    The whole row is stored as the value.
85*333d2b36SAndroid Build Coastguard Worker    :param stream: the csv contents to read
86*333d2b36SAndroid Build Coastguard Worker    :return: the dict from signature to row.
87*333d2b36SAndroid Build Coastguard Worker    """
88*333d2b36SAndroid Build Coastguard Worker    dict_signature_to_row = {}
89*333d2b36SAndroid Build Coastguard Worker    reader = dict_reader(stream)
90*333d2b36SAndroid Build Coastguard Worker    for row in reader:
91*333d2b36SAndroid Build Coastguard Worker        signature = row["signature"]
92*333d2b36SAndroid Build Coastguard Worker        dict_signature_to_row[signature] = row
93*333d2b36SAndroid Build Coastguard Worker    return dict_signature_to_row
94*333d2b36SAndroid Build Coastguard Worker
95*333d2b36SAndroid Build Coastguard Worker
96*333d2b36SAndroid Build Coastguard Workerdef read_signature_csv_from_file_as_dict(csv_file):
97*333d2b36SAndroid Build Coastguard Worker    """Read the csvFile into a dict.
98*333d2b36SAndroid Build Coastguard Worker
99*333d2b36SAndroid Build Coastguard Worker    The first column is assumed to be the signature and used as the
100*333d2b36SAndroid Build Coastguard Worker    key.
101*333d2b36SAndroid Build Coastguard Worker
102*333d2b36SAndroid Build Coastguard Worker    The whole row is stored as the value.
103*333d2b36SAndroid Build Coastguard Worker    :param csv_file: the csv file to read
104*333d2b36SAndroid Build Coastguard Worker    :return: the dict from signature to row.
105*333d2b36SAndroid Build Coastguard Worker    """
106*333d2b36SAndroid Build Coastguard Worker    with open(csv_file, "r", encoding="utf8") as f:
107*333d2b36SAndroid Build Coastguard Worker        return read_signature_csv_from_stream_as_dict(f)
108*333d2b36SAndroid Build Coastguard Worker
109*333d2b36SAndroid Build Coastguard Worker
110*333d2b36SAndroid Build Coastguard Workerdef compare_signature_flags(monolithic_flags_dict, modular_flags_dict,
111*333d2b36SAndroid Build Coastguard Worker                            implementation_flags):
112*333d2b36SAndroid Build Coastguard Worker    """Compare the signature flags between the two dicts.
113*333d2b36SAndroid Build Coastguard Worker
114*333d2b36SAndroid Build Coastguard Worker    :param monolithic_flags_dict: the dict containing the subset of the
115*333d2b36SAndroid Build Coastguard Worker    monolithic flags that should be equal to the modular flags.
116*333d2b36SAndroid Build Coastguard Worker    :param modular_flags_dict:the dict containing the flags produced by a single
117*333d2b36SAndroid Build Coastguard Worker    bootclasspath_fragment module.
118*333d2b36SAndroid Build Coastguard Worker    :return: list of mismatches., each mismatch is a tuple where the first item
119*333d2b36SAndroid Build Coastguard Worker    is the signature, and the second and third items are lists of the flags from
120*333d2b36SAndroid Build Coastguard Worker    modular dict, and monolithic dict respectively.
121*333d2b36SAndroid Build Coastguard Worker    """
122*333d2b36SAndroid Build Coastguard Worker    mismatching_signatures = []
123*333d2b36SAndroid Build Coastguard Worker    # Create a sorted set of all the signatures from both the monolithic and
124*333d2b36SAndroid Build Coastguard Worker    # modular dicts.
125*333d2b36SAndroid Build Coastguard Worker    all_signatures = sorted(
126*333d2b36SAndroid Build Coastguard Worker        set(chain(monolithic_flags_dict.keys(), modular_flags_dict.keys())))
127*333d2b36SAndroid Build Coastguard Worker    for signature in all_signatures:
128*333d2b36SAndroid Build Coastguard Worker        monolithic_row = monolithic_flags_dict.get(signature, {})
129*333d2b36SAndroid Build Coastguard Worker        monolithic_flags = monolithic_row.get(None, [])
130*333d2b36SAndroid Build Coastguard Worker        if signature in modular_flags_dict:
131*333d2b36SAndroid Build Coastguard Worker            modular_row = modular_flags_dict.get(signature, {})
132*333d2b36SAndroid Build Coastguard Worker            modular_flags = modular_row.get(None, [])
133*333d2b36SAndroid Build Coastguard Worker        else:
134*333d2b36SAndroid Build Coastguard Worker            modular_flags = implementation_flags
135*333d2b36SAndroid Build Coastguard Worker        if monolithic_flags != modular_flags:
136*333d2b36SAndroid Build Coastguard Worker            mismatching_signatures.append(
137*333d2b36SAndroid Build Coastguard Worker                (signature, modular_flags, monolithic_flags))
138*333d2b36SAndroid Build Coastguard Worker    return mismatching_signatures
139*333d2b36SAndroid Build Coastguard Worker
140*333d2b36SAndroid Build Coastguard Worker
141*333d2b36SAndroid Build Coastguard Workerdef main(argv):
142*333d2b36SAndroid Build Coastguard Worker    args_parser = argparse.ArgumentParser(
143*333d2b36SAndroid Build Coastguard Worker        description="Verify that sets of hidden API flags are each a subset of "
144*333d2b36SAndroid Build Coastguard Worker        "the monolithic flag file. For each module this uses the provided "
145*333d2b36SAndroid Build Coastguard Worker        "signature patterns to select a subset of the monolithic flags and "
146*333d2b36SAndroid Build Coastguard Worker        "then it compares that subset against the filtered flags provided by "
147*333d2b36SAndroid Build Coastguard Worker        "the module. If the module's filtered flags does not contain flags for "
148*333d2b36SAndroid Build Coastguard Worker        "a signature then it is assumed to have been filtered out because it "
149*333d2b36SAndroid Build Coastguard Worker        "was not part of an API and so is assumed to have the implementation "
150*333d2b36SAndroid Build Coastguard Worker        "flags.")
151*333d2b36SAndroid Build Coastguard Worker    args_parser.add_argument(
152*333d2b36SAndroid Build Coastguard Worker        "--monolithic-flags", help="The monolithic flag file")
153*333d2b36SAndroid Build Coastguard Worker    args_parser.add_argument(
154*333d2b36SAndroid Build Coastguard Worker        "--module-flags",
155*333d2b36SAndroid Build Coastguard Worker        action="append",
156*333d2b36SAndroid Build Coastguard Worker        help="A colon separated pair of paths. The first is a path to a "
157*333d2b36SAndroid Build Coastguard Worker        "filtered set of flags, and the second is a path to a set of "
158*333d2b36SAndroid Build Coastguard Worker        "signature patterns that identify the set of classes belonging to "
159*333d2b36SAndroid Build Coastguard Worker        "a single bootclasspath_fragment module. Specify once for each module "
160*333d2b36SAndroid Build Coastguard Worker        "that needs to be checked.")
161*333d2b36SAndroid Build Coastguard Worker    args_parser.add_argument(
162*333d2b36SAndroid Build Coastguard Worker        "--implementation-flag",
163*333d2b36SAndroid Build Coastguard Worker        action="append",
164*333d2b36SAndroid Build Coastguard Worker        help="A flag in the set of flags that identifies a signature which is "
165*333d2b36SAndroid Build Coastguard Worker        "not part of an API, i.e. is the signature of a private implementation "
166*333d2b36SAndroid Build Coastguard Worker        "member. Specify as many times as necessary to define the "
167*333d2b36SAndroid Build Coastguard Worker        "implementation flag set. If this is not specified then the "
168*333d2b36SAndroid Build Coastguard Worker        "implementation flag set is empty.")
169*333d2b36SAndroid Build Coastguard Worker    args = args_parser.parse_args(argv[1:])
170*333d2b36SAndroid Build Coastguard Worker
171*333d2b36SAndroid Build Coastguard Worker    # Read in all the flags into the trie
172*333d2b36SAndroid Build Coastguard Worker    monolithic_flags_path = args.monolithic_flags
173*333d2b36SAndroid Build Coastguard Worker    monolithic_trie = read_flag_trie_from_file(monolithic_flags_path)
174*333d2b36SAndroid Build Coastguard Worker
175*333d2b36SAndroid Build Coastguard Worker    implementation_flags = args.implementation_flag or []
176*333d2b36SAndroid Build Coastguard Worker
177*333d2b36SAndroid Build Coastguard Worker    # For each subset specified on the command line, create dicts for the flags
178*333d2b36SAndroid Build Coastguard Worker    # provided by the subset and the corresponding flags from the complete set
179*333d2b36SAndroid Build Coastguard Worker    # of flags and compare them.
180*333d2b36SAndroid Build Coastguard Worker    failed = False
181*333d2b36SAndroid Build Coastguard Worker    module_pairs = args.module_flags or []
182*333d2b36SAndroid Build Coastguard Worker    for modular_pair in module_pairs:
183*333d2b36SAndroid Build Coastguard Worker        parts = modular_pair.split(":")
184*333d2b36SAndroid Build Coastguard Worker        modular_flags_path = parts[0]
185*333d2b36SAndroid Build Coastguard Worker        modular_patterns_path = parts[1]
186*333d2b36SAndroid Build Coastguard Worker        modular_flags_dict = read_signature_csv_from_file_as_dict(
187*333d2b36SAndroid Build Coastguard Worker            modular_flags_path)
188*333d2b36SAndroid Build Coastguard Worker        monolithic_flags_subset_dict = \
189*333d2b36SAndroid Build Coastguard Worker            extract_subset_from_monolithic_flags_as_dict_from_file(
190*333d2b36SAndroid Build Coastguard Worker                monolithic_trie, modular_patterns_path)
191*333d2b36SAndroid Build Coastguard Worker        mismatching_signatures = compare_signature_flags(
192*333d2b36SAndroid Build Coastguard Worker            monolithic_flags_subset_dict, modular_flags_dict,
193*333d2b36SAndroid Build Coastguard Worker            implementation_flags)
194*333d2b36SAndroid Build Coastguard Worker        if mismatching_signatures:
195*333d2b36SAndroid Build Coastguard Worker            failed = True
196*333d2b36SAndroid Build Coastguard Worker            print("ERROR: Hidden API flags are inconsistent:")
197*333d2b36SAndroid Build Coastguard Worker            print("< " + modular_flags_path)
198*333d2b36SAndroid Build Coastguard Worker            print("> " + monolithic_flags_path)
199*333d2b36SAndroid Build Coastguard Worker            for mismatch in mismatching_signatures:
200*333d2b36SAndroid Build Coastguard Worker                signature = mismatch[0]
201*333d2b36SAndroid Build Coastguard Worker                print()
202*333d2b36SAndroid Build Coastguard Worker                print("< " + ",".join([signature] + mismatch[1]))
203*333d2b36SAndroid Build Coastguard Worker                print("> " + ",".join([signature] + mismatch[2]))
204*333d2b36SAndroid Build Coastguard Worker
205*333d2b36SAndroid Build Coastguard Worker    if failed:
206*333d2b36SAndroid Build Coastguard Worker        sys.exit(1)
207*333d2b36SAndroid Build Coastguard Worker
208*333d2b36SAndroid Build Coastguard Worker
209*333d2b36SAndroid Build Coastguard Workerif __name__ == "__main__":
210*333d2b36SAndroid Build Coastguard Worker    main(sys.argv)
211