xref: /aosp_15_r20/hardware/interfaces/compatibility_matrices/bump.py (revision 4d7e907c777eeecc4c5bd7cf640a754fac206ff7)
1#!/usr/bin/env python3
2#
3# Copyright (C) 2023 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#      http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17"""
18Creates the next compatibility matrix.
19"""
20
21import argparse
22import os
23import pathlib
24import re
25import subprocess
26import textwrap
27
28
29def check_call(*args, **kwargs):
30    print(args)
31    subprocess.check_call(*args, **kwargs)
32
33
34def check_output(*args, **kwargs):
35    print(args)
36    return subprocess.check_output(*args, **kwargs)
37
38
39class Bump(object):
40
41    def __init__(self, cmdline_args):
42        self.top = pathlib.Path(os.environ["ANDROID_BUILD_TOP"])
43        self.interfaces_dir = self.top / "hardware/interfaces"
44
45        self.current_level = cmdline_args.current_level
46        self.current_letter = cmdline_args.current_letter
47        self.current_version = cmdline_args.platform_version
48        self.current_module_name = f"framework_compatibility_matrix.{self.current_level}.xml"
49        self.current_xml = self.interfaces_dir / f"compatibility_matrices/compatibility_matrix.{self.current_level}.xml"
50        self.device_module_name = "framework_compatibility_matrix.device.xml"
51
52        self.next_level = cmdline_args.next_level
53        self.next_letter = cmdline_args.next_letter
54        self.next_module_name = f"framework_compatibility_matrix.{self.next_level}.xml"
55        self.next_xml = self.interfaces_dir / f"compatibility_matrices/compatibility_matrix.{self.next_level}.xml"
56
57    def run(self):
58        self.bump_kernel_configs()
59        self.copy_matrix()
60        self.edit_android_bp()
61        self.bump_libvintf()
62
63    def bump_kernel_configs(self):
64        check_call([
65            self.top / "kernel/configs/tools/bump.py",
66            self.current_letter.lower(),
67            self.next_letter.lower(),
68        ])
69
70    def copy_matrix(self):
71        with open(self.current_xml) as f_current, open(self.next_xml, "w") as f_next:
72            f_next.write(f_current.read().replace(f"level=\"{self.current_level}\"", f"level=\"{self.next_level}\""))
73
74    def edit_android_bp(self):
75        android_bp = self.interfaces_dir / "compatibility_matrices/Android.bp"
76
77        with open(android_bp, "r+") as f:
78            if self.next_module_name not in f.read():
79                f.seek(0, 2)  # end of file
80                f.write("\n")
81                f.write(
82                    textwrap.dedent(f"""\
83                        vintf_compatibility_matrix {{
84                            name: "{self.next_module_name}",
85                        }}
86                    """))
87
88        next_kernel_configs = check_output(
89            """grep -rh name: | sed -E 's/^.*"(.*)".*/\\1/g'""",
90            cwd=self.top / "kernel/configs" /
91            self.next_letter.lower(),
92            text=True,
93            shell=True,
94        ).splitlines()
95        print(next_kernel_configs)
96
97        check_call([
98            "bpmodify", "-w", "-m", self.next_module_name, "-property", "stem",
99            "-str", self.next_xml.name, android_bp
100        ])
101
102        check_call([
103            "bpmodify", "-w", "-m", self.next_module_name, "-property", "srcs",
104            "-a",
105            self.next_xml.relative_to(android_bp.parent), android_bp
106        ])
107
108        check_call([
109            "bpmodify", "-w", "-m", self.next_module_name, "-property",
110            "kernel_configs", "-a", " ".join(next_kernel_configs), android_bp
111        ])
112
113        # update the SYSTEM_MATRIX_DEPS variable and the phony module's
114        # product_variables entry.
115        lines = []
116        with open(android_bp) as f:
117            for line in f:
118              if f"    \"{self.device_module_name}\",\n" in line:
119                  lines.append(f"    \"{self.current_module_name}\",\n")
120
121              if f"                \"{self.current_module_name}\",\n" in line:
122                  lines.append(f"                \"{self.next_module_name}\",\n")
123              else:
124                  lines.append(line)
125
126        with open(android_bp, "w") as f:
127            f.write("".join(lines))
128
129    def bump_libvintf(self):
130        if not self.current_version:
131            print("Skip libvintf update...")
132            return
133        try:
134            check_call(["grep", "-h",
135                        f"{self.current_letter.upper()} = {self.current_level}",
136                        "system/libvintf/include/vintf/Level.h"])
137        except subprocess.CalledProcessError:
138            print("Adding new API level to libvintf")
139            add_lines_above("system/libvintf/analyze_matrix/analyze_matrix.cpp",
140                            "        case Level::UNSPECIFIED:",
141                            textwrap.indent(textwrap.dedent(f"""\
142                                    case Level::{self.current_letter.upper()}:
143                                        return "Android {self.current_version} ({self.current_letter.upper()})";"""),
144                            "    "*2))
145            add_lines_above("system/libvintf/include/vintf/Level.h",
146                            "    // To add new values:",
147                            f"    {self.current_letter.upper()} = {self.current_level},")
148            add_lines_above("system/libvintf/include/vintf/Level.h",
149                            "        Level::UNSPECIFIED,",
150                            f"        Level::{self.current_letter.upper()},")
151            add_lines_above("system/libvintf/RuntimeInfo.cpp",
152                            "            // Add more levels above this line.",
153                            textwrap.indent(textwrap.dedent(f"""\
154                                        case {self.current_version}: {{
155                                            ret = Level::{self.current_letter.upper()};
156                                        }} break;"""),
157                            "    "*3))
158
159
160def add_lines_above(file, pattern, lines):
161    with open(file, 'r+') as f:
162        text = f.read()
163        split_text = re.split(rf"\n{pattern}\n", text)
164        if len(split_text) != 2:
165            # Only one pattern must be found, otherwise the source must be
166            # changed unexpectedly.
167            raise Exception(
168                f'Pattern "{pattern}" not found or multiple patterns found in {file}')
169        f.seek(0)
170        f.write(f"\n{lines}\n{pattern}\n".join(split_text))
171        f.truncate()
172
173
174def main():
175    parser = argparse.ArgumentParser(description=__doc__)
176    parser.add_argument("current_level",
177                        type=str,
178                        help="VINTF level of the current version (e.g. 202404)")
179    parser.add_argument("next_level",
180                        type=str,
181                        help="VINTF level of the next version (e.g. 202504)")
182    parser.add_argument("current_letter",
183                        type=str,
184                        help="Letter of the API level of the current version (e.g. v)")
185    parser.add_argument("next_letter",
186                        type=str,
187                        help="Letter of the API level of the next version (e.g. w)")
188    parser.add_argument("platform_version",
189                        type=str,
190                        nargs="?",
191                        help="Android release version number number (e.g. 15)")
192    cmdline_args = parser.parse_args()
193
194    Bump(cmdline_args).run()
195
196
197if __name__ == "__main__":
198    main()
199