xref: /aosp_15_r20/build/soong/scripts/hiddenapi/analyze_bcpf.py (revision 333d2b3687b3a337dbcca9d65000bca186795e39)
1*333d2b36SAndroid Build Coastguard Worker#!/usr/bin/env -S python -u
2*333d2b36SAndroid Build Coastguard Worker#
3*333d2b36SAndroid Build Coastguard Worker# Copyright (C) 2022 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"""Analyze bootclasspath_fragment usage."""
17*333d2b36SAndroid Build Coastguard Workerimport argparse
18*333d2b36SAndroid Build Coastguard Workerimport dataclasses
19*333d2b36SAndroid Build Coastguard Workerimport enum
20*333d2b36SAndroid Build Coastguard Workerimport json
21*333d2b36SAndroid Build Coastguard Workerimport logging
22*333d2b36SAndroid Build Coastguard Workerimport os
23*333d2b36SAndroid Build Coastguard Workerimport re
24*333d2b36SAndroid Build Coastguard Workerimport shutil
25*333d2b36SAndroid Build Coastguard Workerimport subprocess
26*333d2b36SAndroid Build Coastguard Workerimport tempfile
27*333d2b36SAndroid Build Coastguard Workerimport textwrap
28*333d2b36SAndroid Build Coastguard Workerimport typing
29*333d2b36SAndroid Build Coastguard Workerfrom enum import Enum
30*333d2b36SAndroid Build Coastguard Worker
31*333d2b36SAndroid Build Coastguard Workerimport sys
32*333d2b36SAndroid Build Coastguard Worker
33*333d2b36SAndroid Build Coastguard Workerfrom signature_trie import signature_trie
34*333d2b36SAndroid Build Coastguard Worker
35*333d2b36SAndroid Build Coastguard Worker_STUB_FLAGS_FILE = "out/soong/hiddenapi/hiddenapi-stub-flags.txt"
36*333d2b36SAndroid Build Coastguard Worker
37*333d2b36SAndroid Build Coastguard Worker_FLAGS_FILE = "out/soong/hiddenapi/hiddenapi-flags.csv"
38*333d2b36SAndroid Build Coastguard Worker
39*333d2b36SAndroid Build Coastguard Worker_INCONSISTENT_FLAGS = "ERROR: Hidden API flags are inconsistent:"
40*333d2b36SAndroid Build Coastguard Worker
41*333d2b36SAndroid Build Coastguard Worker
42*333d2b36SAndroid Build Coastguard Workerclass BuildOperation:
43*333d2b36SAndroid Build Coastguard Worker
44*333d2b36SAndroid Build Coastguard Worker    def __init__(self, popen):
45*333d2b36SAndroid Build Coastguard Worker        self.popen = popen
46*333d2b36SAndroid Build Coastguard Worker        self.returncode = None
47*333d2b36SAndroid Build Coastguard Worker
48*333d2b36SAndroid Build Coastguard Worker    def lines(self):
49*333d2b36SAndroid Build Coastguard Worker        """Return an iterator over the lines output by the build operation.
50*333d2b36SAndroid Build Coastguard Worker
51*333d2b36SAndroid Build Coastguard Worker        The lines have had any trailing white space, including the newline
52*333d2b36SAndroid Build Coastguard Worker        stripped.
53*333d2b36SAndroid Build Coastguard Worker        """
54*333d2b36SAndroid Build Coastguard Worker        return newline_stripping_iter(self.popen.stdout.readline)
55*333d2b36SAndroid Build Coastguard Worker
56*333d2b36SAndroid Build Coastguard Worker    def wait(self, *args, **kwargs):
57*333d2b36SAndroid Build Coastguard Worker        self.popen.wait(*args, **kwargs)
58*333d2b36SAndroid Build Coastguard Worker        self.returncode = self.popen.returncode
59*333d2b36SAndroid Build Coastguard Worker
60*333d2b36SAndroid Build Coastguard Worker
61*333d2b36SAndroid Build Coastguard Worker@dataclasses.dataclass()
62*333d2b36SAndroid Build Coastguard Workerclass FlagDiffs:
63*333d2b36SAndroid Build Coastguard Worker    """Encapsulates differences in flags reported by the build"""
64*333d2b36SAndroid Build Coastguard Worker
65*333d2b36SAndroid Build Coastguard Worker    # Map from member signature to the (module flags, monolithic flags)
66*333d2b36SAndroid Build Coastguard Worker    diffs: typing.Dict[str, typing.Tuple[str, str]]
67*333d2b36SAndroid Build Coastguard Worker
68*333d2b36SAndroid Build Coastguard Worker
69*333d2b36SAndroid Build Coastguard Worker@dataclasses.dataclass()
70*333d2b36SAndroid Build Coastguard Workerclass ModuleInfo:
71*333d2b36SAndroid Build Coastguard Worker    """Provides access to the generated module-info.json file.
72*333d2b36SAndroid Build Coastguard Worker
73*333d2b36SAndroid Build Coastguard Worker    This is used to find the location of the file within which specific modules
74*333d2b36SAndroid Build Coastguard Worker    are defined.
75*333d2b36SAndroid Build Coastguard Worker    """
76*333d2b36SAndroid Build Coastguard Worker
77*333d2b36SAndroid Build Coastguard Worker    modules: typing.Dict[str, typing.Dict[str, typing.Any]]
78*333d2b36SAndroid Build Coastguard Worker
79*333d2b36SAndroid Build Coastguard Worker    @staticmethod
80*333d2b36SAndroid Build Coastguard Worker    def load(filename):
81*333d2b36SAndroid Build Coastguard Worker        with open(filename, "r", encoding="utf8") as f:
82*333d2b36SAndroid Build Coastguard Worker            j = json.load(f)
83*333d2b36SAndroid Build Coastguard Worker            return ModuleInfo(j)
84*333d2b36SAndroid Build Coastguard Worker
85*333d2b36SAndroid Build Coastguard Worker    def _module(self, module_name):
86*333d2b36SAndroid Build Coastguard Worker        """Find module by name in module-info.json file"""
87*333d2b36SAndroid Build Coastguard Worker        if module_name in self.modules:
88*333d2b36SAndroid Build Coastguard Worker            return self.modules[module_name]
89*333d2b36SAndroid Build Coastguard Worker
90*333d2b36SAndroid Build Coastguard Worker        raise Exception(f"Module {module_name} could not be found")
91*333d2b36SAndroid Build Coastguard Worker
92*333d2b36SAndroid Build Coastguard Worker    def module_path(self, module_name):
93*333d2b36SAndroid Build Coastguard Worker        module = self._module(module_name)
94*333d2b36SAndroid Build Coastguard Worker        # The "path" is actually a list of paths, one for each class of module
95*333d2b36SAndroid Build Coastguard Worker        # but as the modules are all created from bp files if a module does
96*333d2b36SAndroid Build Coastguard Worker        # create multiple classes of make modules they should all have the same
97*333d2b36SAndroid Build Coastguard Worker        # path.
98*333d2b36SAndroid Build Coastguard Worker        paths = module["path"]
99*333d2b36SAndroid Build Coastguard Worker        unique_paths = set(paths)
100*333d2b36SAndroid Build Coastguard Worker        if len(unique_paths) != 1:
101*333d2b36SAndroid Build Coastguard Worker            raise Exception(f"Expected module '{module_name}' to have a "
102*333d2b36SAndroid Build Coastguard Worker                            f"single unique path but found {unique_paths}")
103*333d2b36SAndroid Build Coastguard Worker        return paths[0]
104*333d2b36SAndroid Build Coastguard Worker
105*333d2b36SAndroid Build Coastguard Worker
106*333d2b36SAndroid Build Coastguard Workerdef extract_indent(line):
107*333d2b36SAndroid Build Coastguard Worker    return re.match(r"([ \t]*)", line).group(1)
108*333d2b36SAndroid Build Coastguard Worker
109*333d2b36SAndroid Build Coastguard Worker
110*333d2b36SAndroid Build Coastguard Worker_SPECIAL_PLACEHOLDER: str = "SPECIAL_PLACEHOLDER"
111*333d2b36SAndroid Build Coastguard Worker
112*333d2b36SAndroid Build Coastguard Worker
113*333d2b36SAndroid Build Coastguard Worker@dataclasses.dataclass
114*333d2b36SAndroid Build Coastguard Workerclass BpModifyRunner:
115*333d2b36SAndroid Build Coastguard Worker
116*333d2b36SAndroid Build Coastguard Worker    bpmodify_path: str
117*333d2b36SAndroid Build Coastguard Worker
118*333d2b36SAndroid Build Coastguard Worker    def add_values_to_property(self, property_name, values, module_name,
119*333d2b36SAndroid Build Coastguard Worker                               bp_file):
120*333d2b36SAndroid Build Coastguard Worker        cmd = [
121*333d2b36SAndroid Build Coastguard Worker            self.bpmodify_path, "-a", values, "-property", property_name, "-m",
122*333d2b36SAndroid Build Coastguard Worker            module_name, "-w", bp_file, bp_file
123*333d2b36SAndroid Build Coastguard Worker        ]
124*333d2b36SAndroid Build Coastguard Worker
125*333d2b36SAndroid Build Coastguard Worker        logging.debug(" ".join(cmd))
126*333d2b36SAndroid Build Coastguard Worker        subprocess.run(
127*333d2b36SAndroid Build Coastguard Worker            cmd,
128*333d2b36SAndroid Build Coastguard Worker            stderr=subprocess.STDOUT,
129*333d2b36SAndroid Build Coastguard Worker            stdout=log_stream_for_subprocess(),
130*333d2b36SAndroid Build Coastguard Worker            check=True)
131*333d2b36SAndroid Build Coastguard Worker
132*333d2b36SAndroid Build Coastguard Worker
133*333d2b36SAndroid Build Coastguard Worker@dataclasses.dataclass
134*333d2b36SAndroid Build Coastguard Workerclass FileChange:
135*333d2b36SAndroid Build Coastguard Worker    path: str
136*333d2b36SAndroid Build Coastguard Worker
137*333d2b36SAndroid Build Coastguard Worker    description: str
138*333d2b36SAndroid Build Coastguard Worker
139*333d2b36SAndroid Build Coastguard Worker    def __lt__(self, other):
140*333d2b36SAndroid Build Coastguard Worker        return self.path < other.path
141*333d2b36SAndroid Build Coastguard Worker
142*333d2b36SAndroid Build Coastguard Worker
143*333d2b36SAndroid Build Coastguard Workerclass PropertyChangeAction(Enum):
144*333d2b36SAndroid Build Coastguard Worker    """Allowable actions that are supported by HiddenApiPropertyChange."""
145*333d2b36SAndroid Build Coastguard Worker
146*333d2b36SAndroid Build Coastguard Worker    # New values are appended to any existing values.
147*333d2b36SAndroid Build Coastguard Worker    APPEND = 1
148*333d2b36SAndroid Build Coastguard Worker
149*333d2b36SAndroid Build Coastguard Worker    # New values replace any existing values.
150*333d2b36SAndroid Build Coastguard Worker    REPLACE = 2
151*333d2b36SAndroid Build Coastguard Worker
152*333d2b36SAndroid Build Coastguard Worker
153*333d2b36SAndroid Build Coastguard Worker@dataclasses.dataclass
154*333d2b36SAndroid Build Coastguard Workerclass HiddenApiPropertyChange:
155*333d2b36SAndroid Build Coastguard Worker
156*333d2b36SAndroid Build Coastguard Worker    property_name: str
157*333d2b36SAndroid Build Coastguard Worker
158*333d2b36SAndroid Build Coastguard Worker    values: typing.List[str]
159*333d2b36SAndroid Build Coastguard Worker
160*333d2b36SAndroid Build Coastguard Worker    property_comment: str = ""
161*333d2b36SAndroid Build Coastguard Worker
162*333d2b36SAndroid Build Coastguard Worker    # The action that indicates how this change is applied.
163*333d2b36SAndroid Build Coastguard Worker    action: PropertyChangeAction = PropertyChangeAction.APPEND
164*333d2b36SAndroid Build Coastguard Worker
165*333d2b36SAndroid Build Coastguard Worker    def snippet(self, indent):
166*333d2b36SAndroid Build Coastguard Worker        snippet = "\n"
167*333d2b36SAndroid Build Coastguard Worker        snippet += format_comment_as_text(self.property_comment, indent)
168*333d2b36SAndroid Build Coastguard Worker        snippet += f"{indent}{self.property_name}: ["
169*333d2b36SAndroid Build Coastguard Worker        if self.values:
170*333d2b36SAndroid Build Coastguard Worker            snippet += "\n"
171*333d2b36SAndroid Build Coastguard Worker            for value in self.values:
172*333d2b36SAndroid Build Coastguard Worker                snippet += f'{indent}    "{value}",\n'
173*333d2b36SAndroid Build Coastguard Worker            snippet += f"{indent}"
174*333d2b36SAndroid Build Coastguard Worker        snippet += "],\n"
175*333d2b36SAndroid Build Coastguard Worker        return snippet
176*333d2b36SAndroid Build Coastguard Worker
177*333d2b36SAndroid Build Coastguard Worker    def fix_bp_file(self, bcpf_bp_file, bcpf, bpmodify_runner: BpModifyRunner):
178*333d2b36SAndroid Build Coastguard Worker        # Add an additional placeholder value to identify the modification that
179*333d2b36SAndroid Build Coastguard Worker        # bpmodify makes.
180*333d2b36SAndroid Build Coastguard Worker        bpmodify_values = [_SPECIAL_PLACEHOLDER]
181*333d2b36SAndroid Build Coastguard Worker
182*333d2b36SAndroid Build Coastguard Worker        if self.action == PropertyChangeAction.APPEND:
183*333d2b36SAndroid Build Coastguard Worker            # If adding the values to the existing values then pass the new
184*333d2b36SAndroid Build Coastguard Worker            # values to bpmodify.
185*333d2b36SAndroid Build Coastguard Worker            bpmodify_values.extend(self.values)
186*333d2b36SAndroid Build Coastguard Worker        elif self.action == PropertyChangeAction.REPLACE:
187*333d2b36SAndroid Build Coastguard Worker            # If replacing the existing values then it is not possible to use
188*333d2b36SAndroid Build Coastguard Worker            # bpmodify for that directly. It could be used twice to remove the
189*333d2b36SAndroid Build Coastguard Worker            # existing property and then add a new one but that does not remove
190*333d2b36SAndroid Build Coastguard Worker            # any related comments and loses the position of the existing
191*333d2b36SAndroid Build Coastguard Worker            # property as the new property is always added to the end of the
192*333d2b36SAndroid Build Coastguard Worker            # containing block.
193*333d2b36SAndroid Build Coastguard Worker            #
194*333d2b36SAndroid Build Coastguard Worker            # So, instead of passing the new values to bpmodify this this just
195*333d2b36SAndroid Build Coastguard Worker            # adds an extra placeholder to force bpmodify to format the list
196*333d2b36SAndroid Build Coastguard Worker            # across multiple lines to ensure a consistent structure for the
197*333d2b36SAndroid Build Coastguard Worker            # code that removes all the existing values and adds the new ones.
198*333d2b36SAndroid Build Coastguard Worker            #
199*333d2b36SAndroid Build Coastguard Worker            # This placeholder has to be different to the other placeholder as
200*333d2b36SAndroid Build Coastguard Worker            # bpmodify dedups values.
201*333d2b36SAndroid Build Coastguard Worker            bpmodify_values.append(_SPECIAL_PLACEHOLDER + "_REPLACE")
202*333d2b36SAndroid Build Coastguard Worker        else:
203*333d2b36SAndroid Build Coastguard Worker            raise ValueError(f"unknown action {self.action}")
204*333d2b36SAndroid Build Coastguard Worker
205*333d2b36SAndroid Build Coastguard Worker        packages = ",".join(bpmodify_values)
206*333d2b36SAndroid Build Coastguard Worker        bpmodify_runner.add_values_to_property(
207*333d2b36SAndroid Build Coastguard Worker            f"hidden_api.{self.property_name}", packages, bcpf, bcpf_bp_file)
208*333d2b36SAndroid Build Coastguard Worker
209*333d2b36SAndroid Build Coastguard Worker        with open(bcpf_bp_file, "r", encoding="utf8") as tio:
210*333d2b36SAndroid Build Coastguard Worker            lines = tio.readlines()
211*333d2b36SAndroid Build Coastguard Worker            lines = [line.rstrip("\n") for line in lines]
212*333d2b36SAndroid Build Coastguard Worker
213*333d2b36SAndroid Build Coastguard Worker        if self.fixup_bpmodify_changes(bcpf_bp_file, lines):
214*333d2b36SAndroid Build Coastguard Worker            with open(bcpf_bp_file, "w", encoding="utf8") as tio:
215*333d2b36SAndroid Build Coastguard Worker                for line in lines:
216*333d2b36SAndroid Build Coastguard Worker                    print(line, file=tio)
217*333d2b36SAndroid Build Coastguard Worker
218*333d2b36SAndroid Build Coastguard Worker    def fixup_bpmodify_changes(self, bcpf_bp_file, lines):
219*333d2b36SAndroid Build Coastguard Worker        """Fixup the output of bpmodify.
220*333d2b36SAndroid Build Coastguard Worker
221*333d2b36SAndroid Build Coastguard Worker        The bpmodify tool does not support all the capabilities that this needs
222*333d2b36SAndroid Build Coastguard Worker        so it is used to do what it can, including marking the place in the
223*333d2b36SAndroid Build Coastguard Worker        Android.bp file where it makes its changes and then this gets passed a
224*333d2b36SAndroid Build Coastguard Worker        list of lines from that file which it then modifies to complete the
225*333d2b36SAndroid Build Coastguard Worker        change.
226*333d2b36SAndroid Build Coastguard Worker
227*333d2b36SAndroid Build Coastguard Worker        This analyzes the list of lines to find the indices of the significant
228*333d2b36SAndroid Build Coastguard Worker        lines and then applies some changes. As those changes can insert and
229*333d2b36SAndroid Build Coastguard Worker        delete lines (changing the indices of following lines) the changes are
230*333d2b36SAndroid Build Coastguard Worker        generally done in reverse order starting from the end and working
231*333d2b36SAndroid Build Coastguard Worker        towards the beginning. That ensures that the changes do not invalidate
232*333d2b36SAndroid Build Coastguard Worker        the indices of following lines.
233*333d2b36SAndroid Build Coastguard Worker        """
234*333d2b36SAndroid Build Coastguard Worker
235*333d2b36SAndroid Build Coastguard Worker        # Find the line containing the placeholder that has been inserted.
236*333d2b36SAndroid Build Coastguard Worker        place_holder_index = -1
237*333d2b36SAndroid Build Coastguard Worker        for i, line in enumerate(lines):
238*333d2b36SAndroid Build Coastguard Worker            if _SPECIAL_PLACEHOLDER in line:
239*333d2b36SAndroid Build Coastguard Worker                place_holder_index = i
240*333d2b36SAndroid Build Coastguard Worker                break
241*333d2b36SAndroid Build Coastguard Worker        if place_holder_index == -1:
242*333d2b36SAndroid Build Coastguard Worker            logging.debug("Could not find %s in %s", _SPECIAL_PLACEHOLDER,
243*333d2b36SAndroid Build Coastguard Worker                          bcpf_bp_file)
244*333d2b36SAndroid Build Coastguard Worker            return False
245*333d2b36SAndroid Build Coastguard Worker
246*333d2b36SAndroid Build Coastguard Worker        # Remove the place holder. Do this before inserting the comment as that
247*333d2b36SAndroid Build Coastguard Worker        # would change the location of the place holder in the list.
248*333d2b36SAndroid Build Coastguard Worker        place_holder_line = lines[place_holder_index]
249*333d2b36SAndroid Build Coastguard Worker        if place_holder_line.endswith("],"):
250*333d2b36SAndroid Build Coastguard Worker            place_holder_line = place_holder_line.replace(
251*333d2b36SAndroid Build Coastguard Worker                f'"{_SPECIAL_PLACEHOLDER}"', "")
252*333d2b36SAndroid Build Coastguard Worker            lines[place_holder_index] = place_holder_line
253*333d2b36SAndroid Build Coastguard Worker        else:
254*333d2b36SAndroid Build Coastguard Worker            del lines[place_holder_index]
255*333d2b36SAndroid Build Coastguard Worker
256*333d2b36SAndroid Build Coastguard Worker        # Scan forward to the end of the property block to remove a blank line
257*333d2b36SAndroid Build Coastguard Worker        # that bpmodify inserts.
258*333d2b36SAndroid Build Coastguard Worker        end_property_array_index = -1
259*333d2b36SAndroid Build Coastguard Worker        for i in range(place_holder_index, len(lines)):
260*333d2b36SAndroid Build Coastguard Worker            line = lines[i]
261*333d2b36SAndroid Build Coastguard Worker            if line.endswith("],"):
262*333d2b36SAndroid Build Coastguard Worker                end_property_array_index = i
263*333d2b36SAndroid Build Coastguard Worker                break
264*333d2b36SAndroid Build Coastguard Worker        if end_property_array_index == -1:
265*333d2b36SAndroid Build Coastguard Worker            logging.debug("Could not find end of property array in %s",
266*333d2b36SAndroid Build Coastguard Worker                          bcpf_bp_file)
267*333d2b36SAndroid Build Coastguard Worker            return False
268*333d2b36SAndroid Build Coastguard Worker
269*333d2b36SAndroid Build Coastguard Worker        # If bdmodify inserted a blank line afterwards then remove it.
270*333d2b36SAndroid Build Coastguard Worker        if (not lines[end_property_array_index + 1] and
271*333d2b36SAndroid Build Coastguard Worker                lines[end_property_array_index + 2].endswith("},")):
272*333d2b36SAndroid Build Coastguard Worker            del lines[end_property_array_index + 1]
273*333d2b36SAndroid Build Coastguard Worker
274*333d2b36SAndroid Build Coastguard Worker        # Scan back to find the preceding property line.
275*333d2b36SAndroid Build Coastguard Worker        property_line_index = -1
276*333d2b36SAndroid Build Coastguard Worker        for i in range(place_holder_index, 0, -1):
277*333d2b36SAndroid Build Coastguard Worker            line = lines[i]
278*333d2b36SAndroid Build Coastguard Worker            if line.lstrip().startswith(f"{self.property_name}: ["):
279*333d2b36SAndroid Build Coastguard Worker                property_line_index = i
280*333d2b36SAndroid Build Coastguard Worker                break
281*333d2b36SAndroid Build Coastguard Worker        if property_line_index == -1:
282*333d2b36SAndroid Build Coastguard Worker            logging.debug("Could not find property line in %s", bcpf_bp_file)
283*333d2b36SAndroid Build Coastguard Worker            return False
284*333d2b36SAndroid Build Coastguard Worker
285*333d2b36SAndroid Build Coastguard Worker        # If this change is replacing the existing values then they need to be
286*333d2b36SAndroid Build Coastguard Worker        # removed and replaced with the new values. That will change the lines
287*333d2b36SAndroid Build Coastguard Worker        # after the property but it is necessary to do here as the following
288*333d2b36SAndroid Build Coastguard Worker        # code operates on earlier lines.
289*333d2b36SAndroid Build Coastguard Worker        if self.action == PropertyChangeAction.REPLACE:
290*333d2b36SAndroid Build Coastguard Worker            # This removes the existing values and replaces them with the new
291*333d2b36SAndroid Build Coastguard Worker            # values.
292*333d2b36SAndroid Build Coastguard Worker            indent = extract_indent(lines[property_line_index + 1])
293*333d2b36SAndroid Build Coastguard Worker            insert = [f'{indent}"{x}",' for x in self.values]
294*333d2b36SAndroid Build Coastguard Worker            lines[property_line_index + 1:end_property_array_index] = insert
295*333d2b36SAndroid Build Coastguard Worker            if not self.values:
296*333d2b36SAndroid Build Coastguard Worker                # If the property has no values then merge the ], onto the
297*333d2b36SAndroid Build Coastguard Worker                # same line as the property name.
298*333d2b36SAndroid Build Coastguard Worker                del lines[property_line_index + 1]
299*333d2b36SAndroid Build Coastguard Worker                lines[property_line_index] = lines[property_line_index] + "],"
300*333d2b36SAndroid Build Coastguard Worker
301*333d2b36SAndroid Build Coastguard Worker        # Only insert a comment if the property does not already have a comment.
302*333d2b36SAndroid Build Coastguard Worker        line_preceding_property = lines[(property_line_index - 1)]
303*333d2b36SAndroid Build Coastguard Worker        if (self.property_comment and
304*333d2b36SAndroid Build Coastguard Worker                not re.match("([ \t]+)// ", line_preceding_property)):
305*333d2b36SAndroid Build Coastguard Worker            # Extract the indent from the property line and use it to format the
306*333d2b36SAndroid Build Coastguard Worker            # comment.
307*333d2b36SAndroid Build Coastguard Worker            indent = extract_indent(lines[property_line_index])
308*333d2b36SAndroid Build Coastguard Worker            comment_lines = format_comment_as_lines(self.property_comment,
309*333d2b36SAndroid Build Coastguard Worker                                                    indent)
310*333d2b36SAndroid Build Coastguard Worker
311*333d2b36SAndroid Build Coastguard Worker            # If the line before the comment is not blank then insert an extra
312*333d2b36SAndroid Build Coastguard Worker            # blank line at the beginning of the comment.
313*333d2b36SAndroid Build Coastguard Worker            if line_preceding_property:
314*333d2b36SAndroid Build Coastguard Worker                comment_lines.insert(0, "")
315*333d2b36SAndroid Build Coastguard Worker
316*333d2b36SAndroid Build Coastguard Worker            # Insert the comment before the property.
317*333d2b36SAndroid Build Coastguard Worker            lines[property_line_index:property_line_index] = comment_lines
318*333d2b36SAndroid Build Coastguard Worker        return True
319*333d2b36SAndroid Build Coastguard Worker
320*333d2b36SAndroid Build Coastguard Worker
321*333d2b36SAndroid Build Coastguard Worker@dataclasses.dataclass()
322*333d2b36SAndroid Build Coastguard Workerclass PackagePropertyReason:
323*333d2b36SAndroid Build Coastguard Worker    """Provides the reasons why a package was added to a specific property.
324*333d2b36SAndroid Build Coastguard Worker
325*333d2b36SAndroid Build Coastguard Worker    A split package is one that contains classes from the bootclasspath_fragment
326*333d2b36SAndroid Build Coastguard Worker    and other bootclasspath modules. So, for a split package this contains the
327*333d2b36SAndroid Build Coastguard Worker    corresponding lists of classes.
328*333d2b36SAndroid Build Coastguard Worker
329*333d2b36SAndroid Build Coastguard Worker    A single package is one that contains classes sub-packages from the
330*333d2b36SAndroid Build Coastguard Worker    For a split package this contains a list of classes in that package that are
331*333d2b36SAndroid Build Coastguard Worker    provided by the bootclasspath_fragment and a list of classes
332*333d2b36SAndroid Build Coastguard Worker    """
333*333d2b36SAndroid Build Coastguard Worker
334*333d2b36SAndroid Build Coastguard Worker    # The list of classes/sub-packages that is provided by the
335*333d2b36SAndroid Build Coastguard Worker    # bootclasspath_fragment.
336*333d2b36SAndroid Build Coastguard Worker    bcpf: typing.List[str]
337*333d2b36SAndroid Build Coastguard Worker
338*333d2b36SAndroid Build Coastguard Worker    # The list of classes/sub-packages that is provided by other modules on the
339*333d2b36SAndroid Build Coastguard Worker    # bootclasspath.
340*333d2b36SAndroid Build Coastguard Worker    other: typing.List[str]
341*333d2b36SAndroid Build Coastguard Worker
342*333d2b36SAndroid Build Coastguard Worker
343*333d2b36SAndroid Build Coastguard Worker@dataclasses.dataclass()
344*333d2b36SAndroid Build Coastguard Workerclass Result:
345*333d2b36SAndroid Build Coastguard Worker    """Encapsulates the result of the analysis."""
346*333d2b36SAndroid Build Coastguard Worker
347*333d2b36SAndroid Build Coastguard Worker    # The diffs in the flags.
348*333d2b36SAndroid Build Coastguard Worker    diffs: typing.Optional[FlagDiffs] = None
349*333d2b36SAndroid Build Coastguard Worker
350*333d2b36SAndroid Build Coastguard Worker    # A map from package name to the reason why it belongs in the
351*333d2b36SAndroid Build Coastguard Worker    # split_packages property.
352*333d2b36SAndroid Build Coastguard Worker    split_packages: typing.Dict[str, PackagePropertyReason] = dataclasses.field(
353*333d2b36SAndroid Build Coastguard Worker        default_factory=dict)
354*333d2b36SAndroid Build Coastguard Worker
355*333d2b36SAndroid Build Coastguard Worker    # A map from package name to the reason why it belongs in the
356*333d2b36SAndroid Build Coastguard Worker    # single_packages property.
357*333d2b36SAndroid Build Coastguard Worker    single_packages: typing.Dict[str,
358*333d2b36SAndroid Build Coastguard Worker                                 PackagePropertyReason] = dataclasses.field(
359*333d2b36SAndroid Build Coastguard Worker                                     default_factory=dict)
360*333d2b36SAndroid Build Coastguard Worker
361*333d2b36SAndroid Build Coastguard Worker    # The list of packages to add to the package_prefixes property.
362*333d2b36SAndroid Build Coastguard Worker    package_prefixes: typing.List[str] = dataclasses.field(default_factory=list)
363*333d2b36SAndroid Build Coastguard Worker
364*333d2b36SAndroid Build Coastguard Worker    # The bootclasspath_fragment hidden API properties changes.
365*333d2b36SAndroid Build Coastguard Worker    property_changes: typing.List[HiddenApiPropertyChange] = dataclasses.field(
366*333d2b36SAndroid Build Coastguard Worker        default_factory=list)
367*333d2b36SAndroid Build Coastguard Worker
368*333d2b36SAndroid Build Coastguard Worker    # The list of file changes.
369*333d2b36SAndroid Build Coastguard Worker    file_changes: typing.List[FileChange] = dataclasses.field(
370*333d2b36SAndroid Build Coastguard Worker        default_factory=list)
371*333d2b36SAndroid Build Coastguard Worker
372*333d2b36SAndroid Build Coastguard Worker
373*333d2b36SAndroid Build Coastguard Workerclass ClassProvider(enum.Enum):
374*333d2b36SAndroid Build Coastguard Worker    """The source of a class found during the hidden API processing"""
375*333d2b36SAndroid Build Coastguard Worker    BCPF = "bcpf"
376*333d2b36SAndroid Build Coastguard Worker    OTHER = "other"
377*333d2b36SAndroid Build Coastguard Worker
378*333d2b36SAndroid Build Coastguard Worker
379*333d2b36SAndroid Build Coastguard Worker# A fake member to use when using the signature trie to compute the package
380*333d2b36SAndroid Build Coastguard Worker# properties from hidden API flags. This is needed because while that
381*333d2b36SAndroid Build Coastguard Worker# computation only cares about classes the trie expects a class to be an
382*333d2b36SAndroid Build Coastguard Worker# interior node but without a member it makes the class a leaf node. That causes
383*333d2b36SAndroid Build Coastguard Worker# problems when analyzing inner classes as the outer class is a leaf node for
384*333d2b36SAndroid Build Coastguard Worker# its own entry but is used as an interior node for inner classes.
385*333d2b36SAndroid Build Coastguard Worker_FAKE_MEMBER = ";->fake()V"
386*333d2b36SAndroid Build Coastguard Worker
387*333d2b36SAndroid Build Coastguard Worker
388*333d2b36SAndroid Build Coastguard Worker@dataclasses.dataclass()
389*333d2b36SAndroid Build Coastguard Workerclass BcpfAnalyzer:
390*333d2b36SAndroid Build Coastguard Worker    # Path to this tool.
391*333d2b36SAndroid Build Coastguard Worker    tool_path: str
392*333d2b36SAndroid Build Coastguard Worker
393*333d2b36SAndroid Build Coastguard Worker    # Directory pointed to by ANDROID_BUILD_OUT
394*333d2b36SAndroid Build Coastguard Worker    top_dir: str
395*333d2b36SAndroid Build Coastguard Worker
396*333d2b36SAndroid Build Coastguard Worker    # Directory pointed to by OUT_DIR of {top_dir}/out if that is not set.
397*333d2b36SAndroid Build Coastguard Worker    out_dir: str
398*333d2b36SAndroid Build Coastguard Worker
399*333d2b36SAndroid Build Coastguard Worker    # Directory pointed to by ANDROID_PRODUCT_OUT.
400*333d2b36SAndroid Build Coastguard Worker    product_out_dir: str
401*333d2b36SAndroid Build Coastguard Worker
402*333d2b36SAndroid Build Coastguard Worker    # The name of the bootclasspath_fragment module.
403*333d2b36SAndroid Build Coastguard Worker    bcpf: str
404*333d2b36SAndroid Build Coastguard Worker
405*333d2b36SAndroid Build Coastguard Worker    # The name of the apex module containing {bcpf}, only used for
406*333d2b36SAndroid Build Coastguard Worker    # informational purposes.
407*333d2b36SAndroid Build Coastguard Worker    apex: str
408*333d2b36SAndroid Build Coastguard Worker
409*333d2b36SAndroid Build Coastguard Worker    # The name of the sdk module containing {bcpf}, only used for
410*333d2b36SAndroid Build Coastguard Worker    # informational purposes.
411*333d2b36SAndroid Build Coastguard Worker    sdk: str
412*333d2b36SAndroid Build Coastguard Worker
413*333d2b36SAndroid Build Coastguard Worker    # If true then this will attempt to automatically fix any issues that are
414*333d2b36SAndroid Build Coastguard Worker    # found.
415*333d2b36SAndroid Build Coastguard Worker    fix: bool = False
416*333d2b36SAndroid Build Coastguard Worker
417*333d2b36SAndroid Build Coastguard Worker    # All the signatures, loaded from all-flags.csv, initialized by
418*333d2b36SAndroid Build Coastguard Worker    # load_all_flags().
419*333d2b36SAndroid Build Coastguard Worker    _signatures: typing.Set[str] = dataclasses.field(default_factory=set)
420*333d2b36SAndroid Build Coastguard Worker
421*333d2b36SAndroid Build Coastguard Worker    # All the classes, loaded from all-flags.csv, initialized by
422*333d2b36SAndroid Build Coastguard Worker    # load_all_flags().
423*333d2b36SAndroid Build Coastguard Worker    _classes: typing.Set[str] = dataclasses.field(default_factory=set)
424*333d2b36SAndroid Build Coastguard Worker
425*333d2b36SAndroid Build Coastguard Worker    # Information loaded from module-info.json, initialized by
426*333d2b36SAndroid Build Coastguard Worker    # load_module_info().
427*333d2b36SAndroid Build Coastguard Worker    module_info: ModuleInfo = None
428*333d2b36SAndroid Build Coastguard Worker
429*333d2b36SAndroid Build Coastguard Worker    @staticmethod
430*333d2b36SAndroid Build Coastguard Worker    def reformat_report_test(text):
431*333d2b36SAndroid Build Coastguard Worker        return re.sub(r"(.)\n([^\s])", r"\1 \2", text)
432*333d2b36SAndroid Build Coastguard Worker
433*333d2b36SAndroid Build Coastguard Worker    def report(self, text="", **kwargs):
434*333d2b36SAndroid Build Coastguard Worker        # Concatenate lines that are not separated by a blank line together to
435*333d2b36SAndroid Build Coastguard Worker        # eliminate formatting applied to the supplied text to adhere to python
436*333d2b36SAndroid Build Coastguard Worker        # line length limitations.
437*333d2b36SAndroid Build Coastguard Worker        text = self.reformat_report_test(text)
438*333d2b36SAndroid Build Coastguard Worker        logging.info("%s", text, **kwargs)
439*333d2b36SAndroid Build Coastguard Worker
440*333d2b36SAndroid Build Coastguard Worker    def report_dedent(self, text, **kwargs):
441*333d2b36SAndroid Build Coastguard Worker        text = textwrap.dedent(text)
442*333d2b36SAndroid Build Coastguard Worker        self.report(text, **kwargs)
443*333d2b36SAndroid Build Coastguard Worker
444*333d2b36SAndroid Build Coastguard Worker    def run_command(self, cmd, *args, **kwargs):
445*333d2b36SAndroid Build Coastguard Worker        cmd_line = " ".join(cmd)
446*333d2b36SAndroid Build Coastguard Worker        logging.debug("Running %s", cmd_line)
447*333d2b36SAndroid Build Coastguard Worker        subprocess.run(
448*333d2b36SAndroid Build Coastguard Worker            cmd,
449*333d2b36SAndroid Build Coastguard Worker            *args,
450*333d2b36SAndroid Build Coastguard Worker            check=True,
451*333d2b36SAndroid Build Coastguard Worker            cwd=self.top_dir,
452*333d2b36SAndroid Build Coastguard Worker            stderr=subprocess.STDOUT,
453*333d2b36SAndroid Build Coastguard Worker            stdout=log_stream_for_subprocess(),
454*333d2b36SAndroid Build Coastguard Worker            text=True,
455*333d2b36SAndroid Build Coastguard Worker            **kwargs)
456*333d2b36SAndroid Build Coastguard Worker
457*333d2b36SAndroid Build Coastguard Worker    @property
458*333d2b36SAndroid Build Coastguard Worker    def signatures(self):
459*333d2b36SAndroid Build Coastguard Worker        if not self._signatures:
460*333d2b36SAndroid Build Coastguard Worker            raise Exception("signatures has not been initialized")
461*333d2b36SAndroid Build Coastguard Worker        return self._signatures
462*333d2b36SAndroid Build Coastguard Worker
463*333d2b36SAndroid Build Coastguard Worker    @property
464*333d2b36SAndroid Build Coastguard Worker    def classes(self):
465*333d2b36SAndroid Build Coastguard Worker        if not self._classes:
466*333d2b36SAndroid Build Coastguard Worker            raise Exception("classes has not been initialized")
467*333d2b36SAndroid Build Coastguard Worker        return self._classes
468*333d2b36SAndroid Build Coastguard Worker
469*333d2b36SAndroid Build Coastguard Worker    def load_all_flags(self):
470*333d2b36SAndroid Build Coastguard Worker        all_flags = self.find_bootclasspath_fragment_output_file(
471*333d2b36SAndroid Build Coastguard Worker            "all-flags.csv")
472*333d2b36SAndroid Build Coastguard Worker
473*333d2b36SAndroid Build Coastguard Worker        # Extract the set of signatures and a separate set of classes produced
474*333d2b36SAndroid Build Coastguard Worker        # by the bootclasspath_fragment.
475*333d2b36SAndroid Build Coastguard Worker        with open(all_flags, "r", encoding="utf8") as f:
476*333d2b36SAndroid Build Coastguard Worker            for line in newline_stripping_iter(f.readline):
477*333d2b36SAndroid Build Coastguard Worker                signature = self.line_to_signature(line)
478*333d2b36SAndroid Build Coastguard Worker                self._signatures.add(signature)
479*333d2b36SAndroid Build Coastguard Worker                class_name = self.signature_to_class(signature)
480*333d2b36SAndroid Build Coastguard Worker                self._classes.add(class_name)
481*333d2b36SAndroid Build Coastguard Worker
482*333d2b36SAndroid Build Coastguard Worker    def load_module_info(self):
483*333d2b36SAndroid Build Coastguard Worker        module_info_file = os.path.join(self.product_out_dir,
484*333d2b36SAndroid Build Coastguard Worker                                        "module-info.json")
485*333d2b36SAndroid Build Coastguard Worker        self.report(f"\nMaking sure that {module_info_file} is up to date.\n")
486*333d2b36SAndroid Build Coastguard Worker        output = self.build_file_read_output(module_info_file)
487*333d2b36SAndroid Build Coastguard Worker        lines = output.lines()
488*333d2b36SAndroid Build Coastguard Worker        for line in lines:
489*333d2b36SAndroid Build Coastguard Worker            logging.debug("%s", line)
490*333d2b36SAndroid Build Coastguard Worker        output.wait(timeout=10)
491*333d2b36SAndroid Build Coastguard Worker        if output.returncode:
492*333d2b36SAndroid Build Coastguard Worker            raise Exception(f"Error building {module_info_file}")
493*333d2b36SAndroid Build Coastguard Worker        abs_module_info_file = os.path.join(self.top_dir, module_info_file)
494*333d2b36SAndroid Build Coastguard Worker        self.module_info = ModuleInfo.load(abs_module_info_file)
495*333d2b36SAndroid Build Coastguard Worker
496*333d2b36SAndroid Build Coastguard Worker    @staticmethod
497*333d2b36SAndroid Build Coastguard Worker    def line_to_signature(line):
498*333d2b36SAndroid Build Coastguard Worker        return line.split(",")[0]
499*333d2b36SAndroid Build Coastguard Worker
500*333d2b36SAndroid Build Coastguard Worker    @staticmethod
501*333d2b36SAndroid Build Coastguard Worker    def signature_to_class(signature):
502*333d2b36SAndroid Build Coastguard Worker        return signature.split(";->")[0]
503*333d2b36SAndroid Build Coastguard Worker
504*333d2b36SAndroid Build Coastguard Worker    @staticmethod
505*333d2b36SAndroid Build Coastguard Worker    def to_parent_package(pkg_or_class):
506*333d2b36SAndroid Build Coastguard Worker        return pkg_or_class.rsplit("/", 1)[0]
507*333d2b36SAndroid Build Coastguard Worker
508*333d2b36SAndroid Build Coastguard Worker    def module_path(self, module_name):
509*333d2b36SAndroid Build Coastguard Worker        return self.module_info.module_path(module_name)
510*333d2b36SAndroid Build Coastguard Worker
511*333d2b36SAndroid Build Coastguard Worker    def module_out_dir(self, module_name):
512*333d2b36SAndroid Build Coastguard Worker        module_path = self.module_path(module_name)
513*333d2b36SAndroid Build Coastguard Worker        return os.path.join(self.out_dir, "soong/.intermediates", module_path,
514*333d2b36SAndroid Build Coastguard Worker                            module_name)
515*333d2b36SAndroid Build Coastguard Worker
516*333d2b36SAndroid Build Coastguard Worker    def find_bootclasspath_fragment_output_file(self, basename, required=True):
517*333d2b36SAndroid Build Coastguard Worker        # Find the output file of the bootclasspath_fragment with the specified
518*333d2b36SAndroid Build Coastguard Worker        # base name.
519*333d2b36SAndroid Build Coastguard Worker        found_file = ""
520*333d2b36SAndroid Build Coastguard Worker        bcpf_out_dir = self.module_out_dir(self.bcpf)
521*333d2b36SAndroid Build Coastguard Worker        for (dirpath, _, filenames) in os.walk(bcpf_out_dir):
522*333d2b36SAndroid Build Coastguard Worker            for f in filenames:
523*333d2b36SAndroid Build Coastguard Worker                if f == basename:
524*333d2b36SAndroid Build Coastguard Worker                    found_file = os.path.join(dirpath, f)
525*333d2b36SAndroid Build Coastguard Worker                    break
526*333d2b36SAndroid Build Coastguard Worker        if not found_file and required:
527*333d2b36SAndroid Build Coastguard Worker            raise Exception(f"Could not find {basename} in {bcpf_out_dir}")
528*333d2b36SAndroid Build Coastguard Worker        return found_file
529*333d2b36SAndroid Build Coastguard Worker
530*333d2b36SAndroid Build Coastguard Worker    def analyze(self):
531*333d2b36SAndroid Build Coastguard Worker        """Analyze a bootclasspath_fragment module.
532*333d2b36SAndroid Build Coastguard Worker
533*333d2b36SAndroid Build Coastguard Worker        Provides help in resolving any existing issues and provides
534*333d2b36SAndroid Build Coastguard Worker        optimizations that can be applied.
535*333d2b36SAndroid Build Coastguard Worker        """
536*333d2b36SAndroid Build Coastguard Worker        self.report(f"Analyzing bootclasspath_fragment module {self.bcpf}")
537*333d2b36SAndroid Build Coastguard Worker        self.report_dedent(f"""
538*333d2b36SAndroid Build Coastguard Worker            Run this tool to help initialize a bootclasspath_fragment module.
539*333d2b36SAndroid Build Coastguard Worker            Before you start make sure that:
540*333d2b36SAndroid Build Coastguard Worker
541*333d2b36SAndroid Build Coastguard Worker            1. The current checkout is up to date.
542*333d2b36SAndroid Build Coastguard Worker
543*333d2b36SAndroid Build Coastguard Worker            2. The environment has been initialized using lunch, e.g.
544*333d2b36SAndroid Build Coastguard Worker               lunch aosp_arm64-userdebug
545*333d2b36SAndroid Build Coastguard Worker
546*333d2b36SAndroid Build Coastguard Worker            3. You have added a bootclasspath_fragment module to the appropriate
547*333d2b36SAndroid Build Coastguard Worker            Android.bp file. Something like this:
548*333d2b36SAndroid Build Coastguard Worker
549*333d2b36SAndroid Build Coastguard Worker               bootclasspath_fragment {{
550*333d2b36SAndroid Build Coastguard Worker                 name: "{self.bcpf}",
551*333d2b36SAndroid Build Coastguard Worker                 contents: [
552*333d2b36SAndroid Build Coastguard Worker                   "...",
553*333d2b36SAndroid Build Coastguard Worker                 ],
554*333d2b36SAndroid Build Coastguard Worker
555*333d2b36SAndroid Build Coastguard Worker                 // The bootclasspath_fragments that provide APIs on which this
556*333d2b36SAndroid Build Coastguard Worker                 // depends.
557*333d2b36SAndroid Build Coastguard Worker                 fragments: [
558*333d2b36SAndroid Build Coastguard Worker                   {{
559*333d2b36SAndroid Build Coastguard Worker                     apex: "com.android.art",
560*333d2b36SAndroid Build Coastguard Worker                     module: "art-bootclasspath-fragment",
561*333d2b36SAndroid Build Coastguard Worker                   }},
562*333d2b36SAndroid Build Coastguard Worker                 ],
563*333d2b36SAndroid Build Coastguard Worker               }}
564*333d2b36SAndroid Build Coastguard Worker
565*333d2b36SAndroid Build Coastguard Worker            4. You have added it to the platform_bootclasspath module in
566*333d2b36SAndroid Build Coastguard Worker            frameworks/base/boot/Android.bp. Something like this:
567*333d2b36SAndroid Build Coastguard Worker
568*333d2b36SAndroid Build Coastguard Worker               platform_bootclasspath {{
569*333d2b36SAndroid Build Coastguard Worker                 name: "platform-bootclasspath",
570*333d2b36SAndroid Build Coastguard Worker                 fragments: [
571*333d2b36SAndroid Build Coastguard Worker                   ...
572*333d2b36SAndroid Build Coastguard Worker                   {{
573*333d2b36SAndroid Build Coastguard Worker                     apex: "{self.apex}",
574*333d2b36SAndroid Build Coastguard Worker                     module: "{self.bcpf}",
575*333d2b36SAndroid Build Coastguard Worker                   }},
576*333d2b36SAndroid Build Coastguard Worker                 ],
577*333d2b36SAndroid Build Coastguard Worker               }}
578*333d2b36SAndroid Build Coastguard Worker
579*333d2b36SAndroid Build Coastguard Worker            5. You have added an sdk module. Something like this:
580*333d2b36SAndroid Build Coastguard Worker
581*333d2b36SAndroid Build Coastguard Worker               sdk {{
582*333d2b36SAndroid Build Coastguard Worker                 name: "{self.sdk}",
583*333d2b36SAndroid Build Coastguard Worker                 bootclasspath_fragments: ["{self.bcpf}"],
584*333d2b36SAndroid Build Coastguard Worker               }}
585*333d2b36SAndroid Build Coastguard Worker            """)
586*333d2b36SAndroid Build Coastguard Worker
587*333d2b36SAndroid Build Coastguard Worker        # Make sure that the module-info.json file is up to date.
588*333d2b36SAndroid Build Coastguard Worker        self.load_module_info()
589*333d2b36SAndroid Build Coastguard Worker
590*333d2b36SAndroid Build Coastguard Worker        self.report_dedent("""
591*333d2b36SAndroid Build Coastguard Worker            Cleaning potentially stale files.
592*333d2b36SAndroid Build Coastguard Worker            """)
593*333d2b36SAndroid Build Coastguard Worker        # Remove the out/soong/hiddenapi files.
594*333d2b36SAndroid Build Coastguard Worker        shutil.rmtree(f"{self.out_dir}/soong/hiddenapi", ignore_errors=True)
595*333d2b36SAndroid Build Coastguard Worker
596*333d2b36SAndroid Build Coastguard Worker        # Remove any bootclasspath_fragment output files.
597*333d2b36SAndroid Build Coastguard Worker        shutil.rmtree(self.module_out_dir(self.bcpf), ignore_errors=True)
598*333d2b36SAndroid Build Coastguard Worker
599*333d2b36SAndroid Build Coastguard Worker        self.build_monolithic_stubs_flags()
600*333d2b36SAndroid Build Coastguard Worker
601*333d2b36SAndroid Build Coastguard Worker        result = Result()
602*333d2b36SAndroid Build Coastguard Worker
603*333d2b36SAndroid Build Coastguard Worker        self.build_monolithic_flags(result)
604*333d2b36SAndroid Build Coastguard Worker        self.analyze_hiddenapi_package_properties(result)
605*333d2b36SAndroid Build Coastguard Worker        self.explain_how_to_check_signature_patterns()
606*333d2b36SAndroid Build Coastguard Worker
607*333d2b36SAndroid Build Coastguard Worker        # If there were any changes that need to be made to the Android.bp
608*333d2b36SAndroid Build Coastguard Worker        # file then either apply or report them.
609*333d2b36SAndroid Build Coastguard Worker        if result.property_changes:
610*333d2b36SAndroid Build Coastguard Worker            bcpf_dir = self.module_info.module_path(self.bcpf)
611*333d2b36SAndroid Build Coastguard Worker            bcpf_bp_file = os.path.join(self.top_dir, bcpf_dir, "Android.bp")
612*333d2b36SAndroid Build Coastguard Worker            if self.fix:
613*333d2b36SAndroid Build Coastguard Worker                tool_dir = os.path.dirname(self.tool_path)
614*333d2b36SAndroid Build Coastguard Worker                bpmodify_path = os.path.join(tool_dir, "bpmodify")
615*333d2b36SAndroid Build Coastguard Worker                bpmodify_runner = BpModifyRunner(bpmodify_path)
616*333d2b36SAndroid Build Coastguard Worker                for property_change in result.property_changes:
617*333d2b36SAndroid Build Coastguard Worker                    property_change.fix_bp_file(bcpf_bp_file, self.bcpf,
618*333d2b36SAndroid Build Coastguard Worker                                                bpmodify_runner)
619*333d2b36SAndroid Build Coastguard Worker
620*333d2b36SAndroid Build Coastguard Worker                result.file_changes.append(
621*333d2b36SAndroid Build Coastguard Worker                    self.new_file_change(
622*333d2b36SAndroid Build Coastguard Worker                        bcpf_bp_file,
623*333d2b36SAndroid Build Coastguard Worker                        f"Updated hidden_api properties of '{self.bcpf}'"))
624*333d2b36SAndroid Build Coastguard Worker
625*333d2b36SAndroid Build Coastguard Worker            else:
626*333d2b36SAndroid Build Coastguard Worker                hiddenapi_snippet = ""
627*333d2b36SAndroid Build Coastguard Worker                for property_change in result.property_changes:
628*333d2b36SAndroid Build Coastguard Worker                    hiddenapi_snippet += property_change.snippet("        ")
629*333d2b36SAndroid Build Coastguard Worker
630*333d2b36SAndroid Build Coastguard Worker                # Remove leading and trailing blank lines.
631*333d2b36SAndroid Build Coastguard Worker                hiddenapi_snippet = hiddenapi_snippet.strip("\n")
632*333d2b36SAndroid Build Coastguard Worker
633*333d2b36SAndroid Build Coastguard Worker                result.file_changes.append(
634*333d2b36SAndroid Build Coastguard Worker                    self.new_file_change(
635*333d2b36SAndroid Build Coastguard Worker                        bcpf_bp_file, f"""
636*333d2b36SAndroid Build Coastguard WorkerAdd the following snippet into the {self.bcpf} bootclasspath_fragment module
637*333d2b36SAndroid Build Coastguard Workerin the {bcpf_dir}/Android.bp file. If the hidden_api block already exists then
638*333d2b36SAndroid Build Coastguard Workermerge these properties into it.
639*333d2b36SAndroid Build Coastguard Worker
640*333d2b36SAndroid Build Coastguard Worker    hidden_api: {{
641*333d2b36SAndroid Build Coastguard Worker{hiddenapi_snippet}
642*333d2b36SAndroid Build Coastguard Worker    }},
643*333d2b36SAndroid Build Coastguard Worker"""))
644*333d2b36SAndroid Build Coastguard Worker
645*333d2b36SAndroid Build Coastguard Worker        if result.file_changes:
646*333d2b36SAndroid Build Coastguard Worker            if self.fix:
647*333d2b36SAndroid Build Coastguard Worker                file_change_message = textwrap.dedent("""
648*333d2b36SAndroid Build Coastguard Worker                    The following files were modified by this script:
649*333d2b36SAndroid Build Coastguard Worker                    """)
650*333d2b36SAndroid Build Coastguard Worker            else:
651*333d2b36SAndroid Build Coastguard Worker                file_change_message = textwrap.dedent("""
652*333d2b36SAndroid Build Coastguard Worker                    The following modifications need to be made:
653*333d2b36SAndroid Build Coastguard Worker                    """)
654*333d2b36SAndroid Build Coastguard Worker
655*333d2b36SAndroid Build Coastguard Worker            self.report(file_change_message)
656*333d2b36SAndroid Build Coastguard Worker            result.file_changes.sort()
657*333d2b36SAndroid Build Coastguard Worker            for file_change in result.file_changes:
658*333d2b36SAndroid Build Coastguard Worker                self.report(f"    {file_change.path}")
659*333d2b36SAndroid Build Coastguard Worker                self.report(f"        {file_change.description}")
660*333d2b36SAndroid Build Coastguard Worker                self.report()
661*333d2b36SAndroid Build Coastguard Worker
662*333d2b36SAndroid Build Coastguard Worker            if not self.fix:
663*333d2b36SAndroid Build Coastguard Worker                self.report_dedent("""
664*333d2b36SAndroid Build Coastguard Worker                    Run the command again with the --fix option to automatically
665*333d2b36SAndroid Build Coastguard Worker                    make the above changes.
666*333d2b36SAndroid Build Coastguard Worker                    """.lstrip("\n"))
667*333d2b36SAndroid Build Coastguard Worker
668*333d2b36SAndroid Build Coastguard Worker    def new_file_change(self, file, description):
669*333d2b36SAndroid Build Coastguard Worker        return FileChange(
670*333d2b36SAndroid Build Coastguard Worker            path=os.path.relpath(file, self.top_dir), description=description)
671*333d2b36SAndroid Build Coastguard Worker
672*333d2b36SAndroid Build Coastguard Worker    def check_inconsistent_flag_lines(self, significant, module_line,
673*333d2b36SAndroid Build Coastguard Worker                                      monolithic_line, separator_line):
674*333d2b36SAndroid Build Coastguard Worker        if not (module_line.startswith("< ") and
675*333d2b36SAndroid Build Coastguard Worker                monolithic_line.startswith("> ") and not separator_line):
676*333d2b36SAndroid Build Coastguard Worker            # Something went wrong.
677*333d2b36SAndroid Build Coastguard Worker            self.report("Invalid build output detected:")
678*333d2b36SAndroid Build Coastguard Worker            self.report(f"  module_line: '{module_line}'")
679*333d2b36SAndroid Build Coastguard Worker            self.report(f"  monolithic_line: '{monolithic_line}'")
680*333d2b36SAndroid Build Coastguard Worker            self.report(f"  separator_line: '{separator_line}'")
681*333d2b36SAndroid Build Coastguard Worker            sys.exit(1)
682*333d2b36SAndroid Build Coastguard Worker
683*333d2b36SAndroid Build Coastguard Worker        if significant:
684*333d2b36SAndroid Build Coastguard Worker            logging.debug("%s", module_line)
685*333d2b36SAndroid Build Coastguard Worker            logging.debug("%s", monolithic_line)
686*333d2b36SAndroid Build Coastguard Worker            logging.debug("%s", separator_line)
687*333d2b36SAndroid Build Coastguard Worker
688*333d2b36SAndroid Build Coastguard Worker    def scan_inconsistent_flags_report(self, lines):
689*333d2b36SAndroid Build Coastguard Worker        """Scans a hidden API flags report
690*333d2b36SAndroid Build Coastguard Worker
691*333d2b36SAndroid Build Coastguard Worker        The hidden API inconsistent flags report which looks something like
692*333d2b36SAndroid Build Coastguard Worker        this.
693*333d2b36SAndroid Build Coastguard Worker
694*333d2b36SAndroid Build Coastguard Worker        < out/soong/.intermediates/.../filtered-stub-flags.csv
695*333d2b36SAndroid Build Coastguard Worker        > out/soong/hiddenapi/hiddenapi-stub-flags.txt
696*333d2b36SAndroid Build Coastguard Worker
697*333d2b36SAndroid Build Coastguard Worker        < Landroid/compat/Compatibility;->clearOverrides()V
698*333d2b36SAndroid Build Coastguard Worker        > Landroid/compat/Compatibility;->clearOverrides()V,core-platform-api
699*333d2b36SAndroid Build Coastguard Worker
700*333d2b36SAndroid Build Coastguard Worker        """
701*333d2b36SAndroid Build Coastguard Worker
702*333d2b36SAndroid Build Coastguard Worker        # The basic format of an entry in the inconsistent flags report is:
703*333d2b36SAndroid Build Coastguard Worker        #   <module specific flag>
704*333d2b36SAndroid Build Coastguard Worker        #   <monolithic flag>
705*333d2b36SAndroid Build Coastguard Worker        #   <separator>
706*333d2b36SAndroid Build Coastguard Worker        #
707*333d2b36SAndroid Build Coastguard Worker        # Wrap the lines iterator in an iterator which returns a tuple
708*333d2b36SAndroid Build Coastguard Worker        # consisting of the three separate lines.
709*333d2b36SAndroid Build Coastguard Worker        triples = zip(lines, lines, lines)
710*333d2b36SAndroid Build Coastguard Worker
711*333d2b36SAndroid Build Coastguard Worker        module_line, monolithic_line, separator_line = next(triples)
712*333d2b36SAndroid Build Coastguard Worker        significant = False
713*333d2b36SAndroid Build Coastguard Worker        bcpf_dir = self.module_info.module_path(self.bcpf)
714*333d2b36SAndroid Build Coastguard Worker        if os.path.join(bcpf_dir, self.bcpf) in module_line:
715*333d2b36SAndroid Build Coastguard Worker            # These errors are related to the bcpf being analyzed so
716*333d2b36SAndroid Build Coastguard Worker            # keep them.
717*333d2b36SAndroid Build Coastguard Worker            significant = True
718*333d2b36SAndroid Build Coastguard Worker        else:
719*333d2b36SAndroid Build Coastguard Worker            self.report(f"Filtering out errors related to {module_line}")
720*333d2b36SAndroid Build Coastguard Worker
721*333d2b36SAndroid Build Coastguard Worker        self.check_inconsistent_flag_lines(significant, module_line,
722*333d2b36SAndroid Build Coastguard Worker                                           monolithic_line, separator_line)
723*333d2b36SAndroid Build Coastguard Worker
724*333d2b36SAndroid Build Coastguard Worker        diffs = {}
725*333d2b36SAndroid Build Coastguard Worker        for module_line, monolithic_line, separator_line in triples:
726*333d2b36SAndroid Build Coastguard Worker            self.check_inconsistent_flag_lines(significant, module_line,
727*333d2b36SAndroid Build Coastguard Worker                                               monolithic_line, "")
728*333d2b36SAndroid Build Coastguard Worker
729*333d2b36SAndroid Build Coastguard Worker            module_parts = module_line.removeprefix("< ").split(",")
730*333d2b36SAndroid Build Coastguard Worker            module_signature = module_parts[0]
731*333d2b36SAndroid Build Coastguard Worker            module_flags = module_parts[1:]
732*333d2b36SAndroid Build Coastguard Worker
733*333d2b36SAndroid Build Coastguard Worker            monolithic_parts = monolithic_line.removeprefix("> ").split(",")
734*333d2b36SAndroid Build Coastguard Worker            monolithic_signature = monolithic_parts[0]
735*333d2b36SAndroid Build Coastguard Worker            monolithic_flags = monolithic_parts[1:]
736*333d2b36SAndroid Build Coastguard Worker
737*333d2b36SAndroid Build Coastguard Worker            if module_signature != monolithic_signature:
738*333d2b36SAndroid Build Coastguard Worker                # Something went wrong.
739*333d2b36SAndroid Build Coastguard Worker                self.report("Inconsistent signatures detected:")
740*333d2b36SAndroid Build Coastguard Worker                self.report(f"  module_signature: '{module_signature}'")
741*333d2b36SAndroid Build Coastguard Worker                self.report(f"  monolithic_signature: '{monolithic_signature}'")
742*333d2b36SAndroid Build Coastguard Worker                sys.exit(1)
743*333d2b36SAndroid Build Coastguard Worker
744*333d2b36SAndroid Build Coastguard Worker            diffs[module_signature] = (module_flags, monolithic_flags)
745*333d2b36SAndroid Build Coastguard Worker
746*333d2b36SAndroid Build Coastguard Worker            if separator_line:
747*333d2b36SAndroid Build Coastguard Worker                # If the separator line is not blank then it is the end of the
748*333d2b36SAndroid Build Coastguard Worker                # current report, and possibly the start of another.
749*333d2b36SAndroid Build Coastguard Worker                return separator_line, diffs
750*333d2b36SAndroid Build Coastguard Worker
751*333d2b36SAndroid Build Coastguard Worker        return "", diffs
752*333d2b36SAndroid Build Coastguard Worker
753*333d2b36SAndroid Build Coastguard Worker    def build_file_read_output(self, filename):
754*333d2b36SAndroid Build Coastguard Worker        # Make sure the filename is relative to top if possible as the build
755*333d2b36SAndroid Build Coastguard Worker        # may be using relative paths as the target.
756*333d2b36SAndroid Build Coastguard Worker        rel_filename = filename.removeprefix(self.top_dir)
757*333d2b36SAndroid Build Coastguard Worker        cmd = ["build/soong/soong_ui.bash", "--make-mode", rel_filename]
758*333d2b36SAndroid Build Coastguard Worker        cmd_line = " ".join(cmd)
759*333d2b36SAndroid Build Coastguard Worker        logging.debug("%s", cmd_line)
760*333d2b36SAndroid Build Coastguard Worker        # pylint: disable=consider-using-with
761*333d2b36SAndroid Build Coastguard Worker        output = subprocess.Popen(
762*333d2b36SAndroid Build Coastguard Worker            cmd,
763*333d2b36SAndroid Build Coastguard Worker            cwd=self.top_dir,
764*333d2b36SAndroid Build Coastguard Worker            stderr=subprocess.STDOUT,
765*333d2b36SAndroid Build Coastguard Worker            stdout=subprocess.PIPE,
766*333d2b36SAndroid Build Coastguard Worker            text=True,
767*333d2b36SAndroid Build Coastguard Worker        )
768*333d2b36SAndroid Build Coastguard Worker        return BuildOperation(popen=output)
769*333d2b36SAndroid Build Coastguard Worker
770*333d2b36SAndroid Build Coastguard Worker    def build_hiddenapi_flags(self, filename):
771*333d2b36SAndroid Build Coastguard Worker        output = self.build_file_read_output(filename)
772*333d2b36SAndroid Build Coastguard Worker
773*333d2b36SAndroid Build Coastguard Worker        lines = output.lines()
774*333d2b36SAndroid Build Coastguard Worker        diffs = None
775*333d2b36SAndroid Build Coastguard Worker        for line in lines:
776*333d2b36SAndroid Build Coastguard Worker            logging.debug("%s", line)
777*333d2b36SAndroid Build Coastguard Worker            while line == _INCONSISTENT_FLAGS:
778*333d2b36SAndroid Build Coastguard Worker                line, diffs = self.scan_inconsistent_flags_report(lines)
779*333d2b36SAndroid Build Coastguard Worker
780*333d2b36SAndroid Build Coastguard Worker        output.wait(timeout=10)
781*333d2b36SAndroid Build Coastguard Worker        if output.returncode != 0:
782*333d2b36SAndroid Build Coastguard Worker            logging.debug("Command failed with %s", output.returncode)
783*333d2b36SAndroid Build Coastguard Worker        else:
784*333d2b36SAndroid Build Coastguard Worker            logging.debug("Command succeeded")
785*333d2b36SAndroid Build Coastguard Worker
786*333d2b36SAndroid Build Coastguard Worker        return diffs
787*333d2b36SAndroid Build Coastguard Worker
788*333d2b36SAndroid Build Coastguard Worker    def build_monolithic_stubs_flags(self):
789*333d2b36SAndroid Build Coastguard Worker        self.report_dedent(f"""
790*333d2b36SAndroid Build Coastguard Worker            Attempting to build {_STUB_FLAGS_FILE} to verify that the
791*333d2b36SAndroid Build Coastguard Worker            bootclasspath_fragment has the correct API stubs available...
792*333d2b36SAndroid Build Coastguard Worker            """)
793*333d2b36SAndroid Build Coastguard Worker
794*333d2b36SAndroid Build Coastguard Worker        # Build the hiddenapi-stubs-flags.txt file.
795*333d2b36SAndroid Build Coastguard Worker        diffs = self.build_hiddenapi_flags(_STUB_FLAGS_FILE)
796*333d2b36SAndroid Build Coastguard Worker        if diffs:
797*333d2b36SAndroid Build Coastguard Worker            self.report_dedent(f"""
798*333d2b36SAndroid Build Coastguard Worker                There is a discrepancy between the stub API derived flags
799*333d2b36SAndroid Build Coastguard Worker                created by the bootclasspath_fragment and the
800*333d2b36SAndroid Build Coastguard Worker                platform_bootclasspath. See preceding error messages to see
801*333d2b36SAndroid Build Coastguard Worker                which flags are inconsistent. The inconsistencies can occur for
802*333d2b36SAndroid Build Coastguard Worker                a couple of reasons:
803*333d2b36SAndroid Build Coastguard Worker
804*333d2b36SAndroid Build Coastguard Worker                If you are building against prebuilts of the Android SDK, e.g.
805*333d2b36SAndroid Build Coastguard Worker                by using TARGET_BUILD_APPS then the prebuilt versions of the
806*333d2b36SAndroid Build Coastguard Worker                APIs this bootclasspath_fragment depends upon are out of date
807*333d2b36SAndroid Build Coastguard Worker                and need updating. See go/update-prebuilts for help.
808*333d2b36SAndroid Build Coastguard Worker
809*333d2b36SAndroid Build Coastguard Worker                Otherwise, this is happening because there are some stub APIs
810*333d2b36SAndroid Build Coastguard Worker                that are either provided by or used by the contents of the
811*333d2b36SAndroid Build Coastguard Worker                bootclasspath_fragment but which are not available to it. There
812*333d2b36SAndroid Build Coastguard Worker                are 4 ways to handle this:
813*333d2b36SAndroid Build Coastguard Worker
814*333d2b36SAndroid Build Coastguard Worker                1. A java_sdk_library in the contents property will
815*333d2b36SAndroid Build Coastguard Worker                automatically make its stub APIs available to the
816*333d2b36SAndroid Build Coastguard Worker                bootclasspath_fragment so nothing needs to be done.
817*333d2b36SAndroid Build Coastguard Worker
818*333d2b36SAndroid Build Coastguard Worker                2. If the API provided by the bootclasspath_fragment is created
819*333d2b36SAndroid Build Coastguard Worker                by an api_only java_sdk_library (or a java_library that compiles
820*333d2b36SAndroid Build Coastguard Worker                files generated by a separate droidstubs module then it cannot
821*333d2b36SAndroid Build Coastguard Worker                be added to the contents and instead must be added to the
822*333d2b36SAndroid Build Coastguard Worker                api.stubs property, e.g.
823*333d2b36SAndroid Build Coastguard Worker
824*333d2b36SAndroid Build Coastguard Worker                   bootclasspath_fragment {{
825*333d2b36SAndroid Build Coastguard Worker                     name: "{self.bcpf}",
826*333d2b36SAndroid Build Coastguard Worker                     ...
827*333d2b36SAndroid Build Coastguard Worker                     api: {{
828*333d2b36SAndroid Build Coastguard Worker                       stubs: ["$MODULE-api-only"],"
829*333d2b36SAndroid Build Coastguard Worker                     }},
830*333d2b36SAndroid Build Coastguard Worker                   }}
831*333d2b36SAndroid Build Coastguard Worker
832*333d2b36SAndroid Build Coastguard Worker                3. If the contents use APIs provided by another
833*333d2b36SAndroid Build Coastguard Worker                bootclasspath_fragment then it needs to be added to the
834*333d2b36SAndroid Build Coastguard Worker                fragments property, e.g.
835*333d2b36SAndroid Build Coastguard Worker
836*333d2b36SAndroid Build Coastguard Worker                   bootclasspath_fragment {{
837*333d2b36SAndroid Build Coastguard Worker                     name: "{self.bcpf}",
838*333d2b36SAndroid Build Coastguard Worker                     ...
839*333d2b36SAndroid Build Coastguard Worker                     // The bootclasspath_fragments that provide APIs on which this depends.
840*333d2b36SAndroid Build Coastguard Worker                     fragments: [
841*333d2b36SAndroid Build Coastguard Worker                       ...
842*333d2b36SAndroid Build Coastguard Worker                       {{
843*333d2b36SAndroid Build Coastguard Worker                         apex: "com.android.other",
844*333d2b36SAndroid Build Coastguard Worker                         module: "com.android.other-bootclasspath-fragment",
845*333d2b36SAndroid Build Coastguard Worker                       }},
846*333d2b36SAndroid Build Coastguard Worker                     ],
847*333d2b36SAndroid Build Coastguard Worker                   }}
848*333d2b36SAndroid Build Coastguard Worker
849*333d2b36SAndroid Build Coastguard Worker                4. If the contents use APIs from a module that is not part of
850*333d2b36SAndroid Build Coastguard Worker                another bootclasspath_fragment then it must be added to the
851*333d2b36SAndroid Build Coastguard Worker                additional_stubs property, e.g.
852*333d2b36SAndroid Build Coastguard Worker
853*333d2b36SAndroid Build Coastguard Worker                   bootclasspath_fragment {{
854*333d2b36SAndroid Build Coastguard Worker                     name: "{self.bcpf}",
855*333d2b36SAndroid Build Coastguard Worker                     ...
856*333d2b36SAndroid Build Coastguard Worker                     additional_stubs: ["android-non-updatable"],
857*333d2b36SAndroid Build Coastguard Worker                   }}
858*333d2b36SAndroid Build Coastguard Worker
859*333d2b36SAndroid Build Coastguard Worker                   Like the api.stubs property these are typically
860*333d2b36SAndroid Build Coastguard Worker                   java_sdk_library modules but can be java_library too.
861*333d2b36SAndroid Build Coastguard Worker
862*333d2b36SAndroid Build Coastguard Worker                   Note: The "android-non-updatable" is treated as if it was a
863*333d2b36SAndroid Build Coastguard Worker                   java_sdk_library which it is not at the moment but will be in
864*333d2b36SAndroid Build Coastguard Worker                   future.
865*333d2b36SAndroid Build Coastguard Worker                """)
866*333d2b36SAndroid Build Coastguard Worker
867*333d2b36SAndroid Build Coastguard Worker        return diffs
868*333d2b36SAndroid Build Coastguard Worker
869*333d2b36SAndroid Build Coastguard Worker    def build_monolithic_flags(self, result):
870*333d2b36SAndroid Build Coastguard Worker        self.report_dedent(f"""
871*333d2b36SAndroid Build Coastguard Worker            Attempting to build {_FLAGS_FILE} to verify that the
872*333d2b36SAndroid Build Coastguard Worker            bootclasspath_fragment has the correct hidden API flags...
873*333d2b36SAndroid Build Coastguard Worker            """)
874*333d2b36SAndroid Build Coastguard Worker
875*333d2b36SAndroid Build Coastguard Worker        # Build the hiddenapi-flags.csv file and extract any differences in
876*333d2b36SAndroid Build Coastguard Worker        # the flags between this bootclasspath_fragment and the monolithic
877*333d2b36SAndroid Build Coastguard Worker        # files.
878*333d2b36SAndroid Build Coastguard Worker        result.diffs = self.build_hiddenapi_flags(_FLAGS_FILE)
879*333d2b36SAndroid Build Coastguard Worker
880*333d2b36SAndroid Build Coastguard Worker        # Load information from the bootclasspath_fragment's all-flags.csv file.
881*333d2b36SAndroid Build Coastguard Worker        self.load_all_flags()
882*333d2b36SAndroid Build Coastguard Worker
883*333d2b36SAndroid Build Coastguard Worker        if result.diffs:
884*333d2b36SAndroid Build Coastguard Worker            self.report_dedent(f"""
885*333d2b36SAndroid Build Coastguard Worker                There is a discrepancy between the hidden API flags created by
886*333d2b36SAndroid Build Coastguard Worker                the bootclasspath_fragment and the platform_bootclasspath. See
887*333d2b36SAndroid Build Coastguard Worker                preceding error messages to see which flags are inconsistent.
888*333d2b36SAndroid Build Coastguard Worker                The inconsistencies can occur for a couple of reasons:
889*333d2b36SAndroid Build Coastguard Worker
890*333d2b36SAndroid Build Coastguard Worker                If you are building against prebuilts of this
891*333d2b36SAndroid Build Coastguard Worker                bootclasspath_fragment then the prebuilt version of the sdk
892*333d2b36SAndroid Build Coastguard Worker                snapshot (specifically the hidden API flag files) are
893*333d2b36SAndroid Build Coastguard Worker                inconsistent with the prebuilt version of the apex {self.apex}.
894*333d2b36SAndroid Build Coastguard Worker                Please ensure that they are both updated from the same build.
895*333d2b36SAndroid Build Coastguard Worker
896*333d2b36SAndroid Build Coastguard Worker                1. There are custom hidden API flags specified in the one of the
897*333d2b36SAndroid Build Coastguard Worker                files in frameworks/base/boot/hiddenapi which apply to the
898*333d2b36SAndroid Build Coastguard Worker                bootclasspath_fragment but which are not supplied to the
899*333d2b36SAndroid Build Coastguard Worker                bootclasspath_fragment module.
900*333d2b36SAndroid Build Coastguard Worker
901*333d2b36SAndroid Build Coastguard Worker                2. The bootclasspath_fragment specifies invalid
902*333d2b36SAndroid Build Coastguard Worker                "split_packages", "single_packages" and/of "package_prefixes"
903*333d2b36SAndroid Build Coastguard Worker                properties that match packages and classes that it does not
904*333d2b36SAndroid Build Coastguard Worker                provide.
905*333d2b36SAndroid Build Coastguard Worker                """)
906*333d2b36SAndroid Build Coastguard Worker
907*333d2b36SAndroid Build Coastguard Worker            # Check to see if there are any hiddenapi related properties that
908*333d2b36SAndroid Build Coastguard Worker            # need to be added to the
909*333d2b36SAndroid Build Coastguard Worker            self.report_dedent("""
910*333d2b36SAndroid Build Coastguard Worker                Checking custom hidden API flags....
911*333d2b36SAndroid Build Coastguard Worker                """)
912*333d2b36SAndroid Build Coastguard Worker            self.check_frameworks_base_boot_hidden_api_files(result)
913*333d2b36SAndroid Build Coastguard Worker
914*333d2b36SAndroid Build Coastguard Worker    def report_hidden_api_flag_file_changes(self, result, property_name,
915*333d2b36SAndroid Build Coastguard Worker                                            flags_file, rel_bcpf_flags_file,
916*333d2b36SAndroid Build Coastguard Worker                                            bcpf_flags_file):
917*333d2b36SAndroid Build Coastguard Worker        matched_signatures = set()
918*333d2b36SAndroid Build Coastguard Worker        # Open the flags file to read the flags from.
919*333d2b36SAndroid Build Coastguard Worker        with open(flags_file, "r", encoding="utf8") as f:
920*333d2b36SAndroid Build Coastguard Worker            for signature in newline_stripping_iter(f.readline):
921*333d2b36SAndroid Build Coastguard Worker                if signature in self.signatures:
922*333d2b36SAndroid Build Coastguard Worker                    # The signature is provided by the bootclasspath_fragment so
923*333d2b36SAndroid Build Coastguard Worker                    # it will need to be moved to the bootclasspath_fragment
924*333d2b36SAndroid Build Coastguard Worker                    # specific file.
925*333d2b36SAndroid Build Coastguard Worker                    matched_signatures.add(signature)
926*333d2b36SAndroid Build Coastguard Worker
927*333d2b36SAndroid Build Coastguard Worker        # If the bootclasspath_fragment specific flags file is not empty
928*333d2b36SAndroid Build Coastguard Worker        # then it contains flags. That could either be new flags just moved
929*333d2b36SAndroid Build Coastguard Worker        # from frameworks/base or previous contents of the file. In either
930*333d2b36SAndroid Build Coastguard Worker        # case the file must not be removed.
931*333d2b36SAndroid Build Coastguard Worker        if matched_signatures:
932*333d2b36SAndroid Build Coastguard Worker            insert = textwrap.indent("\n".join(matched_signatures),
933*333d2b36SAndroid Build Coastguard Worker                                     "            ")
934*333d2b36SAndroid Build Coastguard Worker            result.file_changes.append(
935*333d2b36SAndroid Build Coastguard Worker                self.new_file_change(
936*333d2b36SAndroid Build Coastguard Worker                    flags_file, f"""Remove the following entries:
937*333d2b36SAndroid Build Coastguard Worker{insert}
938*333d2b36SAndroid Build Coastguard Worker"""))
939*333d2b36SAndroid Build Coastguard Worker
940*333d2b36SAndroid Build Coastguard Worker            result.file_changes.append(
941*333d2b36SAndroid Build Coastguard Worker                self.new_file_change(
942*333d2b36SAndroid Build Coastguard Worker                    bcpf_flags_file, f"""Add the following entries:
943*333d2b36SAndroid Build Coastguard Worker{insert}
944*333d2b36SAndroid Build Coastguard Worker"""))
945*333d2b36SAndroid Build Coastguard Worker
946*333d2b36SAndroid Build Coastguard Worker            result.property_changes.append(
947*333d2b36SAndroid Build Coastguard Worker                HiddenApiPropertyChange(
948*333d2b36SAndroid Build Coastguard Worker                    property_name=property_name,
949*333d2b36SAndroid Build Coastguard Worker                    values=[rel_bcpf_flags_file],
950*333d2b36SAndroid Build Coastguard Worker                ))
951*333d2b36SAndroid Build Coastguard Worker
952*333d2b36SAndroid Build Coastguard Worker    def fix_hidden_api_flag_files(self, result, property_name, flags_file,
953*333d2b36SAndroid Build Coastguard Worker                                  rel_bcpf_flags_file, bcpf_flags_file):
954*333d2b36SAndroid Build Coastguard Worker        # Read the file in frameworks/base/boot/hiddenapi/<file> copy any
955*333d2b36SAndroid Build Coastguard Worker        # flags that relate to the bootclasspath_fragment into a local
956*333d2b36SAndroid Build Coastguard Worker        # file in the hiddenapi subdirectory.
957*333d2b36SAndroid Build Coastguard Worker        tmp_flags_file = flags_file + ".tmp"
958*333d2b36SAndroid Build Coastguard Worker
959*333d2b36SAndroid Build Coastguard Worker        # Make sure the directory containing the bootclasspath_fragment specific
960*333d2b36SAndroid Build Coastguard Worker        # hidden api flags exists.
961*333d2b36SAndroid Build Coastguard Worker        os.makedirs(os.path.dirname(bcpf_flags_file), exist_ok=True)
962*333d2b36SAndroid Build Coastguard Worker
963*333d2b36SAndroid Build Coastguard Worker        bcpf_flags_file_exists = os.path.exists(bcpf_flags_file)
964*333d2b36SAndroid Build Coastguard Worker
965*333d2b36SAndroid Build Coastguard Worker        matched_signatures = set()
966*333d2b36SAndroid Build Coastguard Worker        # Open the flags file to read the flags from.
967*333d2b36SAndroid Build Coastguard Worker        with open(flags_file, "r", encoding="utf8") as f:
968*333d2b36SAndroid Build Coastguard Worker            # Open a temporary file to write the flags (minus any removed
969*333d2b36SAndroid Build Coastguard Worker            # flags).
970*333d2b36SAndroid Build Coastguard Worker            with open(tmp_flags_file, "w", encoding="utf8") as t:
971*333d2b36SAndroid Build Coastguard Worker                # Open the bootclasspath_fragment file for append just in
972*333d2b36SAndroid Build Coastguard Worker                # case it already exists.
973*333d2b36SAndroid Build Coastguard Worker                with open(bcpf_flags_file, "a", encoding="utf8") as b:
974*333d2b36SAndroid Build Coastguard Worker                    for line in iter(f.readline, ""):
975*333d2b36SAndroid Build Coastguard Worker                        signature = line.rstrip()
976*333d2b36SAndroid Build Coastguard Worker                        if signature in self.signatures:
977*333d2b36SAndroid Build Coastguard Worker                            # The signature is provided by the
978*333d2b36SAndroid Build Coastguard Worker                            # bootclasspath_fragment so write it to the new
979*333d2b36SAndroid Build Coastguard Worker                            # bootclasspath_fragment specific file.
980*333d2b36SAndroid Build Coastguard Worker                            print(line, file=b, end="")
981*333d2b36SAndroid Build Coastguard Worker                            matched_signatures.add(signature)
982*333d2b36SAndroid Build Coastguard Worker                        else:
983*333d2b36SAndroid Build Coastguard Worker                            # The signature is NOT provided by the
984*333d2b36SAndroid Build Coastguard Worker                            # bootclasspath_fragment. Copy it to the new
985*333d2b36SAndroid Build Coastguard Worker                            # monolithic file.
986*333d2b36SAndroid Build Coastguard Worker                            print(line, file=t, end="")
987*333d2b36SAndroid Build Coastguard Worker
988*333d2b36SAndroid Build Coastguard Worker        # If the bootclasspath_fragment specific flags file is not empty
989*333d2b36SAndroid Build Coastguard Worker        # then it contains flags. That could either be new flags just moved
990*333d2b36SAndroid Build Coastguard Worker        # from frameworks/base or previous contents of the file. In either
991*333d2b36SAndroid Build Coastguard Worker        # case the file must not be removed.
992*333d2b36SAndroid Build Coastguard Worker        if matched_signatures:
993*333d2b36SAndroid Build Coastguard Worker            # There are custom flags related to the bootclasspath_fragment
994*333d2b36SAndroid Build Coastguard Worker            # so replace the frameworks/base/boot/hiddenapi file with the
995*333d2b36SAndroid Build Coastguard Worker            # file that does not contain those flags.
996*333d2b36SAndroid Build Coastguard Worker            shutil.move(tmp_flags_file, flags_file)
997*333d2b36SAndroid Build Coastguard Worker
998*333d2b36SAndroid Build Coastguard Worker            result.file_changes.append(
999*333d2b36SAndroid Build Coastguard Worker                self.new_file_change(flags_file,
1000*333d2b36SAndroid Build Coastguard Worker                                     f"Removed '{self.bcpf}' specific entries"))
1001*333d2b36SAndroid Build Coastguard Worker
1002*333d2b36SAndroid Build Coastguard Worker            result.property_changes.append(
1003*333d2b36SAndroid Build Coastguard Worker                HiddenApiPropertyChange(
1004*333d2b36SAndroid Build Coastguard Worker                    property_name=property_name,
1005*333d2b36SAndroid Build Coastguard Worker                    values=[rel_bcpf_flags_file],
1006*333d2b36SAndroid Build Coastguard Worker                ))
1007*333d2b36SAndroid Build Coastguard Worker
1008*333d2b36SAndroid Build Coastguard Worker            # Make sure that the files are sorted.
1009*333d2b36SAndroid Build Coastguard Worker            self.run_command([
1010*333d2b36SAndroid Build Coastguard Worker                "tools/platform-compat/hiddenapi/sort_api.sh",
1011*333d2b36SAndroid Build Coastguard Worker                bcpf_flags_file,
1012*333d2b36SAndroid Build Coastguard Worker            ])
1013*333d2b36SAndroid Build Coastguard Worker
1014*333d2b36SAndroid Build Coastguard Worker            if bcpf_flags_file_exists:
1015*333d2b36SAndroid Build Coastguard Worker                desc = f"Added '{self.bcpf}' specific entries"
1016*333d2b36SAndroid Build Coastguard Worker            else:
1017*333d2b36SAndroid Build Coastguard Worker                desc = f"Created with '{self.bcpf}' specific entries"
1018*333d2b36SAndroid Build Coastguard Worker            result.file_changes.append(
1019*333d2b36SAndroid Build Coastguard Worker                self.new_file_change(bcpf_flags_file, desc))
1020*333d2b36SAndroid Build Coastguard Worker        else:
1021*333d2b36SAndroid Build Coastguard Worker            # There are no custom flags related to the
1022*333d2b36SAndroid Build Coastguard Worker            # bootclasspath_fragment so clean up the working files.
1023*333d2b36SAndroid Build Coastguard Worker            os.remove(tmp_flags_file)
1024*333d2b36SAndroid Build Coastguard Worker            if not bcpf_flags_file_exists:
1025*333d2b36SAndroid Build Coastguard Worker                os.remove(bcpf_flags_file)
1026*333d2b36SAndroid Build Coastguard Worker
1027*333d2b36SAndroid Build Coastguard Worker    def check_frameworks_base_boot_hidden_api_files(self, result):
1028*333d2b36SAndroid Build Coastguard Worker        hiddenapi_dir = os.path.join(self.top_dir,
1029*333d2b36SAndroid Build Coastguard Worker                                     "frameworks/base/boot/hiddenapi")
1030*333d2b36SAndroid Build Coastguard Worker        for basename in sorted(os.listdir(hiddenapi_dir)):
1031*333d2b36SAndroid Build Coastguard Worker            if not (basename.startswith("hiddenapi-") and
1032*333d2b36SAndroid Build Coastguard Worker                    basename.endswith(".txt")):
1033*333d2b36SAndroid Build Coastguard Worker                continue
1034*333d2b36SAndroid Build Coastguard Worker
1035*333d2b36SAndroid Build Coastguard Worker            flags_file = os.path.join(hiddenapi_dir, basename)
1036*333d2b36SAndroid Build Coastguard Worker
1037*333d2b36SAndroid Build Coastguard Worker            logging.debug("Checking %s for flags related to %s", flags_file,
1038*333d2b36SAndroid Build Coastguard Worker                          self.bcpf)
1039*333d2b36SAndroid Build Coastguard Worker
1040*333d2b36SAndroid Build Coastguard Worker            # Map the file name in frameworks/base/boot/hiddenapi into a
1041*333d2b36SAndroid Build Coastguard Worker            # slightly more meaningful name for use by the
1042*333d2b36SAndroid Build Coastguard Worker            # bootclasspath_fragment.
1043*333d2b36SAndroid Build Coastguard Worker            if basename == "hiddenapi-max-target-o.txt":
1044*333d2b36SAndroid Build Coastguard Worker                basename = "hiddenapi-max-target-o-low-priority.txt"
1045*333d2b36SAndroid Build Coastguard Worker            elif basename == "hiddenapi-max-target-r-loprio.txt":
1046*333d2b36SAndroid Build Coastguard Worker                basename = "hiddenapi-max-target-r-low-priority.txt"
1047*333d2b36SAndroid Build Coastguard Worker
1048*333d2b36SAndroid Build Coastguard Worker            property_name = basename.removeprefix("hiddenapi-")
1049*333d2b36SAndroid Build Coastguard Worker            property_name = property_name.removesuffix(".txt")
1050*333d2b36SAndroid Build Coastguard Worker            property_name = property_name.replace("-", "_")
1051*333d2b36SAndroid Build Coastguard Worker
1052*333d2b36SAndroid Build Coastguard Worker            rel_bcpf_flags_file = f"hiddenapi/{basename}"
1053*333d2b36SAndroid Build Coastguard Worker            bcpf_dir = self.module_info.module_path(self.bcpf)
1054*333d2b36SAndroid Build Coastguard Worker            bcpf_flags_file = os.path.join(self.top_dir, bcpf_dir,
1055*333d2b36SAndroid Build Coastguard Worker                                           rel_bcpf_flags_file)
1056*333d2b36SAndroid Build Coastguard Worker
1057*333d2b36SAndroid Build Coastguard Worker            if self.fix:
1058*333d2b36SAndroid Build Coastguard Worker                self.fix_hidden_api_flag_files(result, property_name,
1059*333d2b36SAndroid Build Coastguard Worker                                               flags_file, rel_bcpf_flags_file,
1060*333d2b36SAndroid Build Coastguard Worker                                               bcpf_flags_file)
1061*333d2b36SAndroid Build Coastguard Worker            else:
1062*333d2b36SAndroid Build Coastguard Worker                self.report_hidden_api_flag_file_changes(
1063*333d2b36SAndroid Build Coastguard Worker                    result, property_name, flags_file, rel_bcpf_flags_file,
1064*333d2b36SAndroid Build Coastguard Worker                    bcpf_flags_file)
1065*333d2b36SAndroid Build Coastguard Worker
1066*333d2b36SAndroid Build Coastguard Worker    @staticmethod
1067*333d2b36SAndroid Build Coastguard Worker    def split_package_comment(split_packages):
1068*333d2b36SAndroid Build Coastguard Worker        if split_packages:
1069*333d2b36SAndroid Build Coastguard Worker            return textwrap.dedent("""
1070*333d2b36SAndroid Build Coastguard Worker                The following packages contain classes from other modules on the
1071*333d2b36SAndroid Build Coastguard Worker                bootclasspath. That means that the hidden API flags for this
1072*333d2b36SAndroid Build Coastguard Worker                module has to explicitly list every single class this module
1073*333d2b36SAndroid Build Coastguard Worker                provides in that package to differentiate them from the classes
1074*333d2b36SAndroid Build Coastguard Worker                provided by other modules. That can include private classes that
1075*333d2b36SAndroid Build Coastguard Worker                are not part of the API.
1076*333d2b36SAndroid Build Coastguard Worker            """).strip("\n")
1077*333d2b36SAndroid Build Coastguard Worker
1078*333d2b36SAndroid Build Coastguard Worker        return "This module does not contain any split packages."
1079*333d2b36SAndroid Build Coastguard Worker
1080*333d2b36SAndroid Build Coastguard Worker    @staticmethod
1081*333d2b36SAndroid Build Coastguard Worker    def package_prefixes_comment():
1082*333d2b36SAndroid Build Coastguard Worker        return textwrap.dedent("""
1083*333d2b36SAndroid Build Coastguard Worker            The following packages and all their subpackages currently only
1084*333d2b36SAndroid Build Coastguard Worker            contain classes from this bootclasspath_fragment. Listing a package
1085*333d2b36SAndroid Build Coastguard Worker            here won't prevent other bootclasspath modules from adding classes
1086*333d2b36SAndroid Build Coastguard Worker            in any of those packages but it will prevent them from adding those
1087*333d2b36SAndroid Build Coastguard Worker            classes into an API surface, e.g. public, system, etc.. Doing so
1088*333d2b36SAndroid Build Coastguard Worker            will result in a build failure due to inconsistent flags.
1089*333d2b36SAndroid Build Coastguard Worker        """).strip("\n")
1090*333d2b36SAndroid Build Coastguard Worker
1091*333d2b36SAndroid Build Coastguard Worker    def analyze_hiddenapi_package_properties(self, result):
1092*333d2b36SAndroid Build Coastguard Worker        self.compute_hiddenapi_package_properties(result)
1093*333d2b36SAndroid Build Coastguard Worker
1094*333d2b36SAndroid Build Coastguard Worker        def indent_lines(lines):
1095*333d2b36SAndroid Build Coastguard Worker            return "\n".join([f"        {cls}" for cls in lines])
1096*333d2b36SAndroid Build Coastguard Worker
1097*333d2b36SAndroid Build Coastguard Worker        # TODO(b/202154151): Find those classes in split packages that are not
1098*333d2b36SAndroid Build Coastguard Worker        #  part of an API, i.e. are an internal implementation class, and so
1099*333d2b36SAndroid Build Coastguard Worker        #  can, and should, be safely moved out of the split packages.
1100*333d2b36SAndroid Build Coastguard Worker
1101*333d2b36SAndroid Build Coastguard Worker        split_packages = result.split_packages.keys()
1102*333d2b36SAndroid Build Coastguard Worker        result.property_changes.append(
1103*333d2b36SAndroid Build Coastguard Worker            HiddenApiPropertyChange(
1104*333d2b36SAndroid Build Coastguard Worker                property_name="split_packages",
1105*333d2b36SAndroid Build Coastguard Worker                values=split_packages,
1106*333d2b36SAndroid Build Coastguard Worker                property_comment=self.split_package_comment(split_packages),
1107*333d2b36SAndroid Build Coastguard Worker                action=PropertyChangeAction.REPLACE,
1108*333d2b36SAndroid Build Coastguard Worker            ))
1109*333d2b36SAndroid Build Coastguard Worker
1110*333d2b36SAndroid Build Coastguard Worker        if split_packages:
1111*333d2b36SAndroid Build Coastguard Worker            self.report_dedent(f"""
1112*333d2b36SAndroid Build Coastguard Worker                bootclasspath_fragment {self.bcpf} contains classes in packages
1113*333d2b36SAndroid Build Coastguard Worker                that also contain classes provided by other bootclasspath
1114*333d2b36SAndroid Build Coastguard Worker                modules. Those packages are called split packages. Split
1115*333d2b36SAndroid Build Coastguard Worker                packages should be avoided where possible but are often
1116*333d2b36SAndroid Build Coastguard Worker                unavoidable when modularizing existing code.
1117*333d2b36SAndroid Build Coastguard Worker
1118*333d2b36SAndroid Build Coastguard Worker                The hidden api processing needs to know which packages are split
1119*333d2b36SAndroid Build Coastguard Worker                (and conversely which are not) so that it can optimize the
1120*333d2b36SAndroid Build Coastguard Worker                hidden API flags to remove unnecessary implementation details.
1121*333d2b36SAndroid Build Coastguard Worker
1122*333d2b36SAndroid Build Coastguard Worker                By default (for backwards compatibility) the
1123*333d2b36SAndroid Build Coastguard Worker                bootclasspath_fragment assumes that all packages are split
1124*333d2b36SAndroid Build Coastguard Worker                unless one of the package_prefixes or split_packages properties
1125*333d2b36SAndroid Build Coastguard Worker                are specified. While that is safe it is not optimal and can lead
1126*333d2b36SAndroid Build Coastguard Worker                to unnecessary implementation details leaking into the hidden
1127*333d2b36SAndroid Build Coastguard Worker                API flags. Adding an empty split_packages property allows the
1128*333d2b36SAndroid Build Coastguard Worker                flags to be optimized and remove any unnecessary implementation
1129*333d2b36SAndroid Build Coastguard Worker                details.
1130*333d2b36SAndroid Build Coastguard Worker                """)
1131*333d2b36SAndroid Build Coastguard Worker
1132*333d2b36SAndroid Build Coastguard Worker            for package in split_packages:
1133*333d2b36SAndroid Build Coastguard Worker                reason = result.split_packages[package]
1134*333d2b36SAndroid Build Coastguard Worker                self.report(f"""
1135*333d2b36SAndroid Build Coastguard Worker    Package {package} is split because while this bootclasspath_fragment
1136*333d2b36SAndroid Build Coastguard Worker    provides the following classes:
1137*333d2b36SAndroid Build Coastguard Worker{indent_lines(reason.bcpf)}
1138*333d2b36SAndroid Build Coastguard Worker
1139*333d2b36SAndroid Build Coastguard Worker    Other module(s) on the bootclasspath provides the following classes in
1140*333d2b36SAndroid Build Coastguard Worker    that package:
1141*333d2b36SAndroid Build Coastguard Worker{indent_lines(reason.other)}
1142*333d2b36SAndroid Build Coastguard Worker""")
1143*333d2b36SAndroid Build Coastguard Worker
1144*333d2b36SAndroid Build Coastguard Worker        single_packages = result.single_packages.keys()
1145*333d2b36SAndroid Build Coastguard Worker        if single_packages:
1146*333d2b36SAndroid Build Coastguard Worker            result.property_changes.append(
1147*333d2b36SAndroid Build Coastguard Worker                HiddenApiPropertyChange(
1148*333d2b36SAndroid Build Coastguard Worker                    property_name="single_packages",
1149*333d2b36SAndroid Build Coastguard Worker                    values=single_packages,
1150*333d2b36SAndroid Build Coastguard Worker                    property_comment=textwrap.dedent("""
1151*333d2b36SAndroid Build Coastguard Worker                    The following packages currently only contain classes from
1152*333d2b36SAndroid Build Coastguard Worker                    this bootclasspath_fragment but some of their sub-packages
1153*333d2b36SAndroid Build Coastguard Worker                    contain classes from other bootclasspath modules. Packages
1154*333d2b36SAndroid Build Coastguard Worker                    should only be listed here when necessary for legacy
1155*333d2b36SAndroid Build Coastguard Worker                    purposes, new packages should match a package prefix.
1156*333d2b36SAndroid Build Coastguard Worker                    """),
1157*333d2b36SAndroid Build Coastguard Worker                    action=PropertyChangeAction.REPLACE,
1158*333d2b36SAndroid Build Coastguard Worker                ))
1159*333d2b36SAndroid Build Coastguard Worker
1160*333d2b36SAndroid Build Coastguard Worker            self.report_dedent(f"""
1161*333d2b36SAndroid Build Coastguard Worker                bootclasspath_fragment {self.bcpf} contains classes from
1162*333d2b36SAndroid Build Coastguard Worker                packages that has sub-packages which contain classes provided by
1163*333d2b36SAndroid Build Coastguard Worker                other bootclasspath modules. Those packages are called single
1164*333d2b36SAndroid Build Coastguard Worker                packages. Single packages should be avoided where possible but
1165*333d2b36SAndroid Build Coastguard Worker                are often unavoidable when modularizing existing code.
1166*333d2b36SAndroid Build Coastguard Worker
1167*333d2b36SAndroid Build Coastguard Worker                Because some sub-packages contains classes from other
1168*333d2b36SAndroid Build Coastguard Worker                bootclasspath modules it is not possible to use the package as a
1169*333d2b36SAndroid Build Coastguard Worker                package prefix as that treats the package and all its
1170*333d2b36SAndroid Build Coastguard Worker                sub-packages as being provided by this module.
1171*333d2b36SAndroid Build Coastguard Worker                """)
1172*333d2b36SAndroid Build Coastguard Worker            for package in single_packages:
1173*333d2b36SAndroid Build Coastguard Worker                reason = result.single_packages[package]
1174*333d2b36SAndroid Build Coastguard Worker                self.report(f"""
1175*333d2b36SAndroid Build Coastguard Worker    Package {package} is not a package prefix because while this
1176*333d2b36SAndroid Build Coastguard Worker    bootclasspath_fragment provides the following sub-packages:
1177*333d2b36SAndroid Build Coastguard Worker{indent_lines(reason.bcpf)}
1178*333d2b36SAndroid Build Coastguard Worker
1179*333d2b36SAndroid Build Coastguard Worker    Other module(s) on the bootclasspath provide the following sub-packages:
1180*333d2b36SAndroid Build Coastguard Worker{indent_lines(reason.other)}
1181*333d2b36SAndroid Build Coastguard Worker""")
1182*333d2b36SAndroid Build Coastguard Worker
1183*333d2b36SAndroid Build Coastguard Worker        package_prefixes = result.package_prefixes
1184*333d2b36SAndroid Build Coastguard Worker        if package_prefixes:
1185*333d2b36SAndroid Build Coastguard Worker            result.property_changes.append(
1186*333d2b36SAndroid Build Coastguard Worker                HiddenApiPropertyChange(
1187*333d2b36SAndroid Build Coastguard Worker                    property_name="package_prefixes",
1188*333d2b36SAndroid Build Coastguard Worker                    values=package_prefixes,
1189*333d2b36SAndroid Build Coastguard Worker                    property_comment=self.package_prefixes_comment(),
1190*333d2b36SAndroid Build Coastguard Worker                    action=PropertyChangeAction.REPLACE,
1191*333d2b36SAndroid Build Coastguard Worker                ))
1192*333d2b36SAndroid Build Coastguard Worker
1193*333d2b36SAndroid Build Coastguard Worker    def explain_how_to_check_signature_patterns(self):
1194*333d2b36SAndroid Build Coastguard Worker        signature_patterns_files = self.find_bootclasspath_fragment_output_file(
1195*333d2b36SAndroid Build Coastguard Worker            "signature-patterns.csv", required=False)
1196*333d2b36SAndroid Build Coastguard Worker        if signature_patterns_files:
1197*333d2b36SAndroid Build Coastguard Worker            signature_patterns_files = signature_patterns_files.removeprefix(
1198*333d2b36SAndroid Build Coastguard Worker                self.top_dir)
1199*333d2b36SAndroid Build Coastguard Worker
1200*333d2b36SAndroid Build Coastguard Worker            self.report_dedent(f"""
1201*333d2b36SAndroid Build Coastguard Worker                The purpose of the hiddenapi split_packages and package_prefixes
1202*333d2b36SAndroid Build Coastguard Worker                properties is to allow the removal of implementation details
1203*333d2b36SAndroid Build Coastguard Worker                from the hidden API flags to reduce the coupling between sdk
1204*333d2b36SAndroid Build Coastguard Worker                snapshots and the APEX runtime. It cannot eliminate that
1205*333d2b36SAndroid Build Coastguard Worker                coupling completely though. Doing so may require changes to the
1206*333d2b36SAndroid Build Coastguard Worker                code.
1207*333d2b36SAndroid Build Coastguard Worker
1208*333d2b36SAndroid Build Coastguard Worker                This tool provides support for managing those properties but it
1209*333d2b36SAndroid Build Coastguard Worker                cannot decide whether the set of package prefixes suggested is
1210*333d2b36SAndroid Build Coastguard Worker                appropriate that needs the input of the developer.
1211*333d2b36SAndroid Build Coastguard Worker
1212*333d2b36SAndroid Build Coastguard Worker                Please run the following command:
1213*333d2b36SAndroid Build Coastguard Worker                    m {signature_patterns_files}
1214*333d2b36SAndroid Build Coastguard Worker
1215*333d2b36SAndroid Build Coastguard Worker                And then check the '{signature_patterns_files}' for any mention
1216*333d2b36SAndroid Build Coastguard Worker                of implementation classes and packages (i.e. those
1217*333d2b36SAndroid Build Coastguard Worker                classes/packages that do not contain any part of an API surface,
1218*333d2b36SAndroid Build Coastguard Worker                including the hidden API). If they are found then the code
1219*333d2b36SAndroid Build Coastguard Worker                should ideally be moved to a package unique to this module that
1220*333d2b36SAndroid Build Coastguard Worker                is contained within a package that is part of an API surface.
1221*333d2b36SAndroid Build Coastguard Worker
1222*333d2b36SAndroid Build Coastguard Worker                The format of the file is a list of patterns:
1223*333d2b36SAndroid Build Coastguard Worker
1224*333d2b36SAndroid Build Coastguard Worker                * Patterns for split packages will list every class in that package.
1225*333d2b36SAndroid Build Coastguard Worker
1226*333d2b36SAndroid Build Coastguard Worker                * Patterns for package prefixes will end with .../**.
1227*333d2b36SAndroid Build Coastguard Worker
1228*333d2b36SAndroid Build Coastguard Worker                * Patterns for packages which are not split but cannot use a
1229*333d2b36SAndroid Build Coastguard Worker                package prefix because there are sub-packages which are provided
1230*333d2b36SAndroid Build Coastguard Worker                by another module will end with .../*.
1231*333d2b36SAndroid Build Coastguard Worker                """)
1232*333d2b36SAndroid Build Coastguard Worker
1233*333d2b36SAndroid Build Coastguard Worker    def compute_hiddenapi_package_properties(self, result):
1234*333d2b36SAndroid Build Coastguard Worker        trie = signature_trie()
1235*333d2b36SAndroid Build Coastguard Worker        # Populate the trie with the classes that are provided by the
1236*333d2b36SAndroid Build Coastguard Worker        # bootclasspath_fragment tagging them to make it clear where they
1237*333d2b36SAndroid Build Coastguard Worker        # are from.
1238*333d2b36SAndroid Build Coastguard Worker        sorted_classes = sorted(self.classes)
1239*333d2b36SAndroid Build Coastguard Worker        for class_name in sorted_classes:
1240*333d2b36SAndroid Build Coastguard Worker            trie.add(class_name + _FAKE_MEMBER, ClassProvider.BCPF)
1241*333d2b36SAndroid Build Coastguard Worker
1242*333d2b36SAndroid Build Coastguard Worker        # Now the same for monolithic classes.
1243*333d2b36SAndroid Build Coastguard Worker        monolithic_classes = set()
1244*333d2b36SAndroid Build Coastguard Worker        abs_flags_file = os.path.join(self.top_dir, _FLAGS_FILE)
1245*333d2b36SAndroid Build Coastguard Worker        with open(abs_flags_file, "r", encoding="utf8") as f:
1246*333d2b36SAndroid Build Coastguard Worker            for line in iter(f.readline, ""):
1247*333d2b36SAndroid Build Coastguard Worker                signature = self.line_to_signature(line)
1248*333d2b36SAndroid Build Coastguard Worker                class_name = self.signature_to_class(signature)
1249*333d2b36SAndroid Build Coastguard Worker                if (class_name not in monolithic_classes and
1250*333d2b36SAndroid Build Coastguard Worker                        class_name not in self.classes):
1251*333d2b36SAndroid Build Coastguard Worker                    trie.add(
1252*333d2b36SAndroid Build Coastguard Worker                        class_name + _FAKE_MEMBER,
1253*333d2b36SAndroid Build Coastguard Worker                        ClassProvider.OTHER,
1254*333d2b36SAndroid Build Coastguard Worker                        only_if_matches=True)
1255*333d2b36SAndroid Build Coastguard Worker                    monolithic_classes.add(class_name)
1256*333d2b36SAndroid Build Coastguard Worker
1257*333d2b36SAndroid Build Coastguard Worker        self.recurse_hiddenapi_packages_trie(trie, result)
1258*333d2b36SAndroid Build Coastguard Worker
1259*333d2b36SAndroid Build Coastguard Worker    @staticmethod
1260*333d2b36SAndroid Build Coastguard Worker    def selector_to_java_reference(node):
1261*333d2b36SAndroid Build Coastguard Worker        return node.selector.replace("/", ".")
1262*333d2b36SAndroid Build Coastguard Worker
1263*333d2b36SAndroid Build Coastguard Worker    @staticmethod
1264*333d2b36SAndroid Build Coastguard Worker    def determine_reason_for_single_package(node):
1265*333d2b36SAndroid Build Coastguard Worker        bcpf_packages = []
1266*333d2b36SAndroid Build Coastguard Worker        other_packages = []
1267*333d2b36SAndroid Build Coastguard Worker
1268*333d2b36SAndroid Build Coastguard Worker        def recurse(n):
1269*333d2b36SAndroid Build Coastguard Worker            if n.type != "package":
1270*333d2b36SAndroid Build Coastguard Worker                return
1271*333d2b36SAndroid Build Coastguard Worker
1272*333d2b36SAndroid Build Coastguard Worker            providers = n.get_matching_rows("*")
1273*333d2b36SAndroid Build Coastguard Worker            package_ref = BcpfAnalyzer.selector_to_java_reference(n)
1274*333d2b36SAndroid Build Coastguard Worker            if ClassProvider.BCPF in providers:
1275*333d2b36SAndroid Build Coastguard Worker                bcpf_packages.append(package_ref)
1276*333d2b36SAndroid Build Coastguard Worker            else:
1277*333d2b36SAndroid Build Coastguard Worker                other_packages.append(package_ref)
1278*333d2b36SAndroid Build Coastguard Worker
1279*333d2b36SAndroid Build Coastguard Worker            children = n.child_nodes()
1280*333d2b36SAndroid Build Coastguard Worker            if children:
1281*333d2b36SAndroid Build Coastguard Worker                for child in children:
1282*333d2b36SAndroid Build Coastguard Worker                    recurse(child)
1283*333d2b36SAndroid Build Coastguard Worker
1284*333d2b36SAndroid Build Coastguard Worker        recurse(node)
1285*333d2b36SAndroid Build Coastguard Worker        return PackagePropertyReason(bcpf=bcpf_packages, other=other_packages)
1286*333d2b36SAndroid Build Coastguard Worker
1287*333d2b36SAndroid Build Coastguard Worker    @staticmethod
1288*333d2b36SAndroid Build Coastguard Worker    def determine_reason_for_split_package(node):
1289*333d2b36SAndroid Build Coastguard Worker        bcpf_classes = []
1290*333d2b36SAndroid Build Coastguard Worker        other_classes = []
1291*333d2b36SAndroid Build Coastguard Worker        for child in node.child_nodes():
1292*333d2b36SAndroid Build Coastguard Worker            if child.type != "class":
1293*333d2b36SAndroid Build Coastguard Worker                continue
1294*333d2b36SAndroid Build Coastguard Worker
1295*333d2b36SAndroid Build Coastguard Worker            providers = child.values(lambda _: True)
1296*333d2b36SAndroid Build Coastguard Worker            class_ref = BcpfAnalyzer.selector_to_java_reference(child)
1297*333d2b36SAndroid Build Coastguard Worker            if ClassProvider.BCPF in providers:
1298*333d2b36SAndroid Build Coastguard Worker                bcpf_classes.append(class_ref)
1299*333d2b36SAndroid Build Coastguard Worker            else:
1300*333d2b36SAndroid Build Coastguard Worker                other_classes.append(class_ref)
1301*333d2b36SAndroid Build Coastguard Worker
1302*333d2b36SAndroid Build Coastguard Worker        return PackagePropertyReason(bcpf=bcpf_classes, other=other_classes)
1303*333d2b36SAndroid Build Coastguard Worker
1304*333d2b36SAndroid Build Coastguard Worker    def recurse_hiddenapi_packages_trie(self, node, result):
1305*333d2b36SAndroid Build Coastguard Worker        nodes = node.child_nodes()
1306*333d2b36SAndroid Build Coastguard Worker        if nodes:
1307*333d2b36SAndroid Build Coastguard Worker            for child in nodes:
1308*333d2b36SAndroid Build Coastguard Worker                # Ignore any non-package nodes.
1309*333d2b36SAndroid Build Coastguard Worker                if child.type != "package":
1310*333d2b36SAndroid Build Coastguard Worker                    continue
1311*333d2b36SAndroid Build Coastguard Worker
1312*333d2b36SAndroid Build Coastguard Worker                package = self.selector_to_java_reference(child)
1313*333d2b36SAndroid Build Coastguard Worker
1314*333d2b36SAndroid Build Coastguard Worker                providers = set(child.get_matching_rows("**"))
1315*333d2b36SAndroid Build Coastguard Worker                if not providers:
1316*333d2b36SAndroid Build Coastguard Worker                    # The package and all its sub packages contain no
1317*333d2b36SAndroid Build Coastguard Worker                    # classes. This should never happen.
1318*333d2b36SAndroid Build Coastguard Worker                    pass
1319*333d2b36SAndroid Build Coastguard Worker                elif providers == {ClassProvider.BCPF}:
1320*333d2b36SAndroid Build Coastguard Worker                    # The package and all its sub packages only contain
1321*333d2b36SAndroid Build Coastguard Worker                    # classes provided by the bootclasspath_fragment.
1322*333d2b36SAndroid Build Coastguard Worker                    logging.debug("Package '%s.**' is not split", package)
1323*333d2b36SAndroid Build Coastguard Worker                    result.package_prefixes.append(package)
1324*333d2b36SAndroid Build Coastguard Worker                    # There is no point traversing into the sub packages.
1325*333d2b36SAndroid Build Coastguard Worker                    continue
1326*333d2b36SAndroid Build Coastguard Worker                elif providers == {ClassProvider.OTHER}:
1327*333d2b36SAndroid Build Coastguard Worker                    # The package and all its sub packages contain no
1328*333d2b36SAndroid Build Coastguard Worker                    # classes provided by the bootclasspath_fragment.
1329*333d2b36SAndroid Build Coastguard Worker                    # There is no point traversing into the sub packages.
1330*333d2b36SAndroid Build Coastguard Worker                    logging.debug("Package '%s.**' contains no classes from %s",
1331*333d2b36SAndroid Build Coastguard Worker                                  package, self.bcpf)
1332*333d2b36SAndroid Build Coastguard Worker                    continue
1333*333d2b36SAndroid Build Coastguard Worker                elif ClassProvider.BCPF in providers:
1334*333d2b36SAndroid Build Coastguard Worker                    # The package and all its sub packages contain classes
1335*333d2b36SAndroid Build Coastguard Worker                    # provided by the bootclasspath_fragment and other
1336*333d2b36SAndroid Build Coastguard Worker                    # sources.
1337*333d2b36SAndroid Build Coastguard Worker                    logging.debug(
1338*333d2b36SAndroid Build Coastguard Worker                        "Package '%s.**' contains classes from "
1339*333d2b36SAndroid Build Coastguard Worker                        "%s and other sources", package, self.bcpf)
1340*333d2b36SAndroid Build Coastguard Worker
1341*333d2b36SAndroid Build Coastguard Worker                providers = set(child.get_matching_rows("*"))
1342*333d2b36SAndroid Build Coastguard Worker                if not providers:
1343*333d2b36SAndroid Build Coastguard Worker                    # The package contains no classes.
1344*333d2b36SAndroid Build Coastguard Worker                    logging.debug("Package: %s contains no classes", package)
1345*333d2b36SAndroid Build Coastguard Worker                elif providers == {ClassProvider.BCPF}:
1346*333d2b36SAndroid Build Coastguard Worker                    # The package only contains classes provided by the
1347*333d2b36SAndroid Build Coastguard Worker                    # bootclasspath_fragment.
1348*333d2b36SAndroid Build Coastguard Worker                    logging.debug(
1349*333d2b36SAndroid Build Coastguard Worker                        "Package '%s.*' is not split but does have "
1350*333d2b36SAndroid Build Coastguard Worker                        "sub-packages from other modules", package)
1351*333d2b36SAndroid Build Coastguard Worker
1352*333d2b36SAndroid Build Coastguard Worker                    # Partition the sub-packages into those that are provided by
1353*333d2b36SAndroid Build Coastguard Worker                    # this bootclasspath_fragment and those provided by other
1354*333d2b36SAndroid Build Coastguard Worker                    # modules. They can be used to explain the reason for the
1355*333d2b36SAndroid Build Coastguard Worker                    # single package to developers.
1356*333d2b36SAndroid Build Coastguard Worker                    reason = self.determine_reason_for_single_package(child)
1357*333d2b36SAndroid Build Coastguard Worker                    result.single_packages[package] = reason
1358*333d2b36SAndroid Build Coastguard Worker
1359*333d2b36SAndroid Build Coastguard Worker                elif providers == {ClassProvider.OTHER}:
1360*333d2b36SAndroid Build Coastguard Worker                    # The package contains no classes provided by the
1361*333d2b36SAndroid Build Coastguard Worker                    # bootclasspath_fragment. Child nodes make contain such
1362*333d2b36SAndroid Build Coastguard Worker                    # classes.
1363*333d2b36SAndroid Build Coastguard Worker                    logging.debug("Package '%s.*' contains no classes from %s",
1364*333d2b36SAndroid Build Coastguard Worker                                  package, self.bcpf)
1365*333d2b36SAndroid Build Coastguard Worker                elif ClassProvider.BCPF in providers:
1366*333d2b36SAndroid Build Coastguard Worker                    # The package contains classes provided by both the
1367*333d2b36SAndroid Build Coastguard Worker                    # bootclasspath_fragment and some other source.
1368*333d2b36SAndroid Build Coastguard Worker                    logging.debug("Package '%s.*' is split", package)
1369*333d2b36SAndroid Build Coastguard Worker
1370*333d2b36SAndroid Build Coastguard Worker                    # Partition the classes in this split package into those
1371*333d2b36SAndroid Build Coastguard Worker                    # that come from this bootclasspath_fragment and those that
1372*333d2b36SAndroid Build Coastguard Worker                    # come from other modules. That can be used to explain the
1373*333d2b36SAndroid Build Coastguard Worker                    # reason for the split package to developers.
1374*333d2b36SAndroid Build Coastguard Worker                    reason = self.determine_reason_for_split_package(child)
1375*333d2b36SAndroid Build Coastguard Worker                    result.split_packages[package] = reason
1376*333d2b36SAndroid Build Coastguard Worker
1377*333d2b36SAndroid Build Coastguard Worker                self.recurse_hiddenapi_packages_trie(child, result)
1378*333d2b36SAndroid Build Coastguard Worker
1379*333d2b36SAndroid Build Coastguard Worker
1380*333d2b36SAndroid Build Coastguard Workerdef newline_stripping_iter(iterator):
1381*333d2b36SAndroid Build Coastguard Worker    """Return an iterator over the iterator that strips trailing white space."""
1382*333d2b36SAndroid Build Coastguard Worker    lines = iter(iterator, "")
1383*333d2b36SAndroid Build Coastguard Worker    lines = (line.rstrip() for line in lines)
1384*333d2b36SAndroid Build Coastguard Worker    return lines
1385*333d2b36SAndroid Build Coastguard Worker
1386*333d2b36SAndroid Build Coastguard Worker
1387*333d2b36SAndroid Build Coastguard Workerdef format_comment_as_text(text, indent):
1388*333d2b36SAndroid Build Coastguard Worker    return "".join(
1389*333d2b36SAndroid Build Coastguard Worker        [f"{line}\n" for line in format_comment_as_lines(text, indent)])
1390*333d2b36SAndroid Build Coastguard Worker
1391*333d2b36SAndroid Build Coastguard Worker
1392*333d2b36SAndroid Build Coastguard Workerdef format_comment_as_lines(text, indent):
1393*333d2b36SAndroid Build Coastguard Worker    lines = textwrap.wrap(text.strip("\n"), width=77 - len(indent))
1394*333d2b36SAndroid Build Coastguard Worker    lines = [f"{indent}// {line}" for line in lines]
1395*333d2b36SAndroid Build Coastguard Worker    return lines
1396*333d2b36SAndroid Build Coastguard Worker
1397*333d2b36SAndroid Build Coastguard Worker
1398*333d2b36SAndroid Build Coastguard Workerdef log_stream_for_subprocess():
1399*333d2b36SAndroid Build Coastguard Worker    stream = subprocess.DEVNULL
1400*333d2b36SAndroid Build Coastguard Worker    for handler in logging.root.handlers:
1401*333d2b36SAndroid Build Coastguard Worker        if handler.level == logging.DEBUG:
1402*333d2b36SAndroid Build Coastguard Worker            if isinstance(handler, logging.StreamHandler):
1403*333d2b36SAndroid Build Coastguard Worker                stream = handler.stream
1404*333d2b36SAndroid Build Coastguard Worker    return stream
1405*333d2b36SAndroid Build Coastguard Worker
1406*333d2b36SAndroid Build Coastguard Worker
1407*333d2b36SAndroid Build Coastguard Workerdef main(argv):
1408*333d2b36SAndroid Build Coastguard Worker    args_parser = argparse.ArgumentParser(
1409*333d2b36SAndroid Build Coastguard Worker        description="Analyze a bootclasspath_fragment module.")
1410*333d2b36SAndroid Build Coastguard Worker    args_parser.add_argument(
1411*333d2b36SAndroid Build Coastguard Worker        "--bcpf",
1412*333d2b36SAndroid Build Coastguard Worker        help="The bootclasspath_fragment module to analyze",
1413*333d2b36SAndroid Build Coastguard Worker        required=True,
1414*333d2b36SAndroid Build Coastguard Worker    )
1415*333d2b36SAndroid Build Coastguard Worker    args_parser.add_argument(
1416*333d2b36SAndroid Build Coastguard Worker        "--apex",
1417*333d2b36SAndroid Build Coastguard Worker        help="The apex module to which the bootclasspath_fragment belongs. It "
1418*333d2b36SAndroid Build Coastguard Worker        "is not strictly necessary at the moment but providing it will "
1419*333d2b36SAndroid Build Coastguard Worker        "allow this script to give more useful messages and it may be"
1420*333d2b36SAndroid Build Coastguard Worker        "required in future.",
1421*333d2b36SAndroid Build Coastguard Worker        default="SPECIFY-APEX-OPTION")
1422*333d2b36SAndroid Build Coastguard Worker    args_parser.add_argument(
1423*333d2b36SAndroid Build Coastguard Worker        "--sdk",
1424*333d2b36SAndroid Build Coastguard Worker        help="The sdk module to which the bootclasspath_fragment belongs. It "
1425*333d2b36SAndroid Build Coastguard Worker        "is not strictly necessary at the moment but providing it will "
1426*333d2b36SAndroid Build Coastguard Worker        "allow this script to give more useful messages and it may be"
1427*333d2b36SAndroid Build Coastguard Worker        "required in future.",
1428*333d2b36SAndroid Build Coastguard Worker        default="SPECIFY-SDK-OPTION")
1429*333d2b36SAndroid Build Coastguard Worker    args_parser.add_argument(
1430*333d2b36SAndroid Build Coastguard Worker        "--fix",
1431*333d2b36SAndroid Build Coastguard Worker        help="Attempt to fix any issues found automatically.",
1432*333d2b36SAndroid Build Coastguard Worker        action="store_true",
1433*333d2b36SAndroid Build Coastguard Worker        default=False)
1434*333d2b36SAndroid Build Coastguard Worker    args = args_parser.parse_args(argv[1:])
1435*333d2b36SAndroid Build Coastguard Worker    top_dir = os.environ["ANDROID_BUILD_TOP"] + "/"
1436*333d2b36SAndroid Build Coastguard Worker    out_dir = os.environ.get("OUT_DIR", os.path.join(top_dir, "out"))
1437*333d2b36SAndroid Build Coastguard Worker    product_out_dir = os.environ.get("ANDROID_PRODUCT_OUT", top_dir)
1438*333d2b36SAndroid Build Coastguard Worker    # Make product_out_dir relative to the top so it can be used as part of a
1439*333d2b36SAndroid Build Coastguard Worker    # build target.
1440*333d2b36SAndroid Build Coastguard Worker    product_out_dir = product_out_dir.removeprefix(top_dir)
1441*333d2b36SAndroid Build Coastguard Worker    log_fd, abs_log_file = tempfile.mkstemp(
1442*333d2b36SAndroid Build Coastguard Worker        suffix="_analyze_bcpf.log", text=True)
1443*333d2b36SAndroid Build Coastguard Worker
1444*333d2b36SAndroid Build Coastguard Worker    with os.fdopen(log_fd, "w") as log_file:
1445*333d2b36SAndroid Build Coastguard Worker        # Set up debug logging to the log file.
1446*333d2b36SAndroid Build Coastguard Worker        logging.basicConfig(
1447*333d2b36SAndroid Build Coastguard Worker            level=logging.DEBUG,
1448*333d2b36SAndroid Build Coastguard Worker            format="%(levelname)-8s %(message)s",
1449*333d2b36SAndroid Build Coastguard Worker            stream=log_file)
1450*333d2b36SAndroid Build Coastguard Worker
1451*333d2b36SAndroid Build Coastguard Worker        # define a Handler which writes INFO messages or higher to the
1452*333d2b36SAndroid Build Coastguard Worker        # sys.stdout with just the message.
1453*333d2b36SAndroid Build Coastguard Worker        console = logging.StreamHandler()
1454*333d2b36SAndroid Build Coastguard Worker        console.setLevel(logging.INFO)
1455*333d2b36SAndroid Build Coastguard Worker        console.setFormatter(logging.Formatter("%(message)s"))
1456*333d2b36SAndroid Build Coastguard Worker        # add the handler to the root logger
1457*333d2b36SAndroid Build Coastguard Worker        logging.getLogger("").addHandler(console)
1458*333d2b36SAndroid Build Coastguard Worker
1459*333d2b36SAndroid Build Coastguard Worker        print(f"Writing log to {abs_log_file}")
1460*333d2b36SAndroid Build Coastguard Worker        try:
1461*333d2b36SAndroid Build Coastguard Worker            analyzer = BcpfAnalyzer(
1462*333d2b36SAndroid Build Coastguard Worker                tool_path=argv[0],
1463*333d2b36SAndroid Build Coastguard Worker                top_dir=top_dir,
1464*333d2b36SAndroid Build Coastguard Worker                out_dir=out_dir,
1465*333d2b36SAndroid Build Coastguard Worker                product_out_dir=product_out_dir,
1466*333d2b36SAndroid Build Coastguard Worker                bcpf=args.bcpf,
1467*333d2b36SAndroid Build Coastguard Worker                apex=args.apex,
1468*333d2b36SAndroid Build Coastguard Worker                sdk=args.sdk,
1469*333d2b36SAndroid Build Coastguard Worker                fix=args.fix,
1470*333d2b36SAndroid Build Coastguard Worker            )
1471*333d2b36SAndroid Build Coastguard Worker            analyzer.analyze()
1472*333d2b36SAndroid Build Coastguard Worker        finally:
1473*333d2b36SAndroid Build Coastguard Worker            print(f"Log written to {abs_log_file}")
1474*333d2b36SAndroid Build Coastguard Worker
1475*333d2b36SAndroid Build Coastguard Worker
1476*333d2b36SAndroid Build Coastguard Workerif __name__ == "__main__":
1477*333d2b36SAndroid Build Coastguard Worker    main(sys.argv)
1478