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"""Creates the next compatibility matrix.""" 18 19import argparse 20import os 21import pathlib 22import re 23import subprocess 24import textwrap 25 26 27def check_call(*args, **kwargs): 28 print(args) 29 subprocess.check_call(*args, **kwargs) 30 31 32def check_output(*args, **kwargs): 33 print(args) 34 return subprocess.check_output(*args, **kwargs) 35 36 37class Bump(object): 38 39 def __init__(self, cmdline_args): 40 self.top = pathlib.Path(os.environ["ANDROID_BUILD_TOP"]) 41 self.interfaces_dir = self.top / "hardware/interfaces" 42 43 self.current_level = cmdline_args.current_level 44 self.current_letter = cmdline_args.current_letter 45 self.current_version = cmdline_args.platform_version 46 self.next_version = cmdline_args.next_platform_version 47 self.current_module_name = ( 48 f"framework_compatibility_matrix.{self.current_level}.xml" 49 ) 50 self.current_xml = ( 51 self.interfaces_dir 52 / f"compatibility_matrices/compatibility_matrix.{self.current_level}.xml" 53 ) 54 self.device_module_name = "framework_compatibility_matrix.device.xml" 55 56 self.next_level = cmdline_args.next_level 57 self.next_letter = cmdline_args.next_letter 58 self.current_sdk = cmdline_args.current_sdk 59 self.next_sdk = cmdline_args.next_sdk 60 self.next_module_name = ( 61 f"framework_compatibility_matrix.{self.next_level}.xml" 62 ) 63 self.next_xml = ( 64 self.interfaces_dir 65 / f"compatibility_matrices/compatibility_matrix.{self.next_level}.xml" 66 ) 67 68 def run(self): 69 self.bump_kernel_configs() 70 self.copy_matrix() 71 self.edit_android_bp() 72 self.bump_libvintf() 73 self.bump_libvts_vintf() 74 self.bump_cuttlefish() 75 76 def bump_kernel_configs(self): 77 check_call([ 78 self.top / "kernel/configs/tools/bump.py", 79 self.current_letter.lower(), 80 self.next_letter.lower(), 81 ]) 82 83 def copy_matrix(self): 84 with open(self.current_xml) as f_current, open( 85 self.next_xml, "w" 86 ) as f_next: 87 f_next.write( 88 f_current.read().replace( 89 f'level="{self.current_level}"', f'level="{self.next_level}"' 90 ) 91 ) 92 93 def edit_android_bp(self): 94 android_bp = self.interfaces_dir / "compatibility_matrices/Android.bp" 95 96 with open(android_bp, "r+") as f: 97 if self.next_module_name not in f.read(): 98 f.seek(0, 2) # end of file 99 f.write("\n") 100 f.write(textwrap.dedent(f"""\ 101 vintf_compatibility_matrix {{ 102 name: "{self.next_module_name}", 103 }} 104 """)) 105 106 next_kernel_configs = check_output( 107 """grep -rh name: | sed -E 's/^.*"(.*)".*/\\1/g'""", 108 cwd=self.top / "kernel/configs" / self.next_letter.lower(), 109 text=True, 110 shell=True, 111 ).splitlines() 112 print(next_kernel_configs) 113 114 check_call([ 115 "bpmodify", 116 "-w", 117 "-m", 118 self.next_module_name, 119 "-property", 120 "stem", 121 "-str", 122 self.next_xml.name, 123 android_bp, 124 ]) 125 126 check_call([ 127 "bpmodify", 128 "-w", 129 "-m", 130 self.next_module_name, 131 "-property", 132 "srcs", 133 "-a", 134 self.next_xml.relative_to(android_bp.parent), 135 android_bp, 136 ]) 137 138 check_call([ 139 "bpmodify", 140 "-w", 141 "-m", 142 self.next_module_name, 143 "-property", 144 "kernel_configs", 145 "-a", 146 " ".join(next_kernel_configs), 147 android_bp, 148 ]) 149 150 # Replace the phony module's product_variables entry to add the new FCM 151 # to the development targets (trunk* configs). 152 lines = [] 153 with open(android_bp) as f: 154 for line in f: 155 if f' "{self.current_module_name}",\n' in line: 156 lines.append(f' "{self.next_module_name}",\n') 157 else: 158 lines.append(line) 159 160 with open(android_bp, "w") as f: 161 f.write("".join(lines)) 162 163 def bump_libvintf(self): 164 if not self.current_version: 165 print("Skip libvintf update...") 166 return 167 try: 168 check_call([ 169 "grep", 170 "-h", 171 f"{self.next_letter.upper()} = {self.next_level}", 172 f"{self.top}/system/libvintf/include/vintf/Level.h", 173 ]) 174 except subprocess.CalledProcessError: 175 print("Adding new API level to libvintf") 176 add_lines_above( 177 f"{self.top}/system/libvintf/analyze_matrix/analyze_matrix.cpp", 178 " case Level::UNSPECIFIED:", 179 textwrap.indent( 180 textwrap.dedent( 181 f"""\ 182 case Level::{self.next_letter.upper()}: 183 return "Android {self.next_version} ({self.next_letter.upper()})";""" 184 ), 185 " " * 2, 186 ), 187 ) 188 add_lines_above( 189 f"{self.top}/system/libvintf/include/vintf/Level.h", 190 " // To add new values:", 191 f" {self.next_letter.upper()} = {self.next_level},", 192 ) 193 add_lines_above( 194 f"{self.top}/system/libvintf/include/vintf/Level.h", 195 " Level::UNSPECIFIED,", 196 f" Level::{self.next_letter.upper()},", 197 ) 198 add_lines_above( 199 f"{self.top}/system/libvintf/RuntimeInfo.cpp", 200 " // Add more levels above this line.", 201 textwrap.indent( 202 textwrap.dedent(f"""\ 203 case {self.next_version}: {{ 204 ret = Level::{self.next_letter.upper()}; 205 }} break;"""), 206 " " * 3, 207 ), 208 ) 209 210 def bump_libvts_vintf(self): 211 if not self.current_version: 212 print("Skip libvts_vintf update...") 213 return 214 try: 215 check_call([ 216 "grep", 217 "-h", 218 f"{self.next_level}, Level::{self.next_letter.upper()}", 219 f"{self.top}/test/vts-testcase/hal/treble/vintf/libvts_vintf_test_common/common.cpp", 220 ]) 221 print("libvts_vintf is already up-to-date") 222 except subprocess.CalledProcessError: 223 print("Adding new API level to libvts_vintf") 224 add_lines_below( 225 f"{self.top}/test/vts-testcase/hal/treble/vintf/libvts_vintf_test_common/common.cpp", 226 f" {{{self.current_level}," 227 f" Level::{self.current_letter.upper()}}},", 228 f" {{{self.next_level}," 229 f" Level::{self.next_letter.upper()}}},\n", 230 ) 231 232 def bump_cuttlefish(self): 233 if not self.next_sdk: 234 print("Skip Cuttlefish update...") 235 return 236 cf_mk_file = f"{self.top}/device/google/cuttlefish/shared/device.mk" 237 try: 238 check_call([ 239 "grep", 240 "-h", 241 f"PRODUCT_SHIPPING_API_LEVEL := {self.next_sdk}", 242 cf_mk_file, 243 ]) 244 print("Cuttlefish is already up-to-date") 245 except subprocess.CalledProcessError: 246 print("Bumping Cuttlefish to the next SHIPPING_API_LEVEL") 247 final_lines = [] 248 with open(cf_mk_file, "r+") as f: 249 for line in f: 250 if f"PRODUCT_SHIPPING_API_LEVEL := {self.current_sdk}" in line: 251 final_lines.append( 252 f"PRODUCT_SHIPPING_API_LEVEL := {self.next_sdk}\n" 253 ) 254 elif line.startswith("PRODUCT_SHIPPING_API_LEVEL :="): 255 # this is the previous SDK level. 256 final_lines.append( 257 f"PRODUCT_SHIPPING_API_LEVEL := {self.current_sdk}\n" 258 ) 259 else: 260 final_lines.append(line) 261 f.seek(0) 262 f.write("".join(final_lines)) 263 f.truncate() 264 final_lines = [] 265 with open( 266 f"{self.top}/device/google/cuttlefish/shared/config/previous_manifest.xml", 267 "r+", 268 ) as f: 269 for line in f: 270 if "target-level=" in line: 271 final_lines.append( 272 '<manifest version="1.0" type="device"' 273 f' target-level="{self.current_level}">\n' 274 ) 275 else: 276 final_lines.append(line) 277 f.seek(0) 278 f.write("".join(final_lines)) 279 f.truncate() 280 281 final_lines = [] 282 with open( 283 f"{self.top}/device/google/cuttlefish/shared/config/manifest.xml", "r+" 284 ) as f: 285 for line in f: 286 if "target-level=" in line: 287 final_lines.append( 288 '<manifest version="1.0" type="device"' 289 f' target-level="{self.next_level}">\n' 290 ) 291 else: 292 final_lines.append(line) 293 f.seek(0) 294 f.write("".join(final_lines)) 295 f.truncate() 296 297 298def add_lines_above(file, pattern, lines): 299 with open(file, "r+") as f: 300 text = f.read() 301 split_text = re.split(rf"\n{pattern}\n", text) 302 if len(split_text) != 2: 303 # Only one pattern must be found, otherwise the source must be 304 # changed unexpectedly. 305 raise Exception( 306 f'Pattern "{pattern}" not found or multiple patterns found in {file}' 307 ) 308 f.seek(0) 309 f.write(f"\n{lines}\n{pattern}\n".join(split_text)) 310 f.truncate() 311 312 313def add_lines_below(file, pattern, lines): 314 final_lines = [] 315 with open(file, "r+") as f: 316 for line in f: 317 final_lines.append(line) 318 if pattern in line: 319 final_lines.append(lines) 320 f.seek(0) 321 f.write("".join(final_lines)) 322 f.truncate() 323 324 325def main(): 326 parser = argparse.ArgumentParser(description=__doc__) 327 parser.add_argument( 328 "current_level", 329 type=str, 330 help="VINTF level of the current version (e.g. 202404)", 331 ) 332 parser.add_argument( 333 "next_level", 334 type=str, 335 help="VINTF level of the next version (e.g. 202504)", 336 ) 337 parser.add_argument( 338 "current_letter", 339 type=str, 340 help="Letter of the API level of the current version (e.g. b)", 341 ) 342 parser.add_argument( 343 "next_letter", 344 type=str, 345 help="Letter of the API level of the next version (e.g. c)", 346 ) 347 parser.add_argument( 348 "platform_version", 349 type=str, 350 nargs="?", 351 help="Current Android release version number (e.g. 16)", 352 ) 353 parser.add_argument( 354 "next_platform_version", 355 type=str, 356 nargs="?", 357 help="Next Android release version number number (e.g. 17)", 358 ) 359 parser.add_argument( 360 "current_sdk", 361 type=str, 362 nargs="?", 363 help="Version of the current SDK API level (e.g. 36)", 364 ) 365 parser.add_argument( 366 "next_sdk", 367 type=str, 368 nargs="?", 369 help="Version of the next SDK API level(e.g. 37)", 370 ) 371 372 cmdline_args = parser.parse_args() 373 374 Bump(cmdline_args).run() 375 376 377if __name__ == "__main__": 378 main() 379