1#!/usr/bin/env python3 2# 3# Copyright (C) 2020 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"""Unzips and installs the vendor snapshot.""" 18 19import argparse 20import glob 21import logging 22import os 23import re 24import shutil 25import subprocess 26import sys 27import tempfile 28import textwrap 29import json 30 31INDENT = ' ' * 4 32 33 34def get_notice_path(module_name): 35 return os.path.join('NOTICE_FILES', module_name + '.txt') 36 37 38def get_target_arch(json_rel_path): 39 return json_rel_path.split('/')[0] 40 41 42def get_arch(json_rel_path): 43 return json_rel_path.split('/')[1].split('-')[1] 44 45 46def get_variation(json_rel_path): 47 return json_rel_path.split('/')[2] 48 49# convert .bp prop dictionary to .bp prop string 50def gen_bp_prop(prop, ind): 51 bp = '' 52 for key in sorted(prop): 53 val = prop[key] 54 55 # Skip empty list or dict, rather than printing empty prop like 56 # "key: []," or "key: {}," 57 if type(val) == list and len(val) == 0: 58 continue 59 if type(val) == dict and gen_bp_prop(val, '') == '': 60 continue 61 62 bp += ind + key + ': ' 63 if type(val) == bool: 64 bp += 'true,\n' if val else 'false,\n' 65 elif type(val) == str: 66 bp += '"%s",\n' % val 67 elif type(val) == list: 68 bp += '[\n' 69 for elem in val: 70 bp += ind + INDENT + '"%s",\n' % elem 71 bp += ind + '],\n' 72 elif type(val) == dict: 73 bp += '{\n' 74 bp += gen_bp_prop(val, ind + INDENT) 75 bp += ind + '},\n' 76 else: 77 raise TypeError('unsupported type %s for gen_bp_prop' % type(val)) 78 return bp 79 80 81# Remove non-existent dirs from given list. Emits warning for such dirs. 82def remove_invalid_dirs(paths, bp_dir, module_name): 83 ret = [] 84 for path in paths: 85 if os.path.isdir(os.path.join(bp_dir, path)): 86 ret.append(path) 87 else: 88 logging.warning('Dir "%s" of module "%s" does not exist', path, 89 module_name) 90 return ret 91 92 93JSON_TO_BP = { 94 'ModuleName': 'name', 95 'RelativeInstallPath': 'relative_install_path', 96 'ExportedDirs': 'export_include_dirs', 97 'ExportedSystemDirs': 'export_system_include_dirs', 98 'ExportedFlags': 'export_flags', 99 'Sanitize': 'sanitize', 100 'SanitizeMinimalDep': 'sanitize_minimal_dep', 101 'SanitizeUbsanDep': 'sanitize_ubsan_dep', 102 'Symlinks': 'symlinks', 103 'InitRc': 'init_rc', 104 'VintfFragments': 'vintf_fragments', 105 'SharedLibs': 'shared_libs', 106 'RuntimeLibs': 'runtime_libs', 107 'Required': 'required', 108} 109 110SANITIZER_VARIANT_PROPS = { 111 'export_include_dirs', 112 'export_system_include_dirs', 113 'export_flags', 114 'sanitize_minimal_dep', 115 'sanitize_ubsan_dep', 116 'src', 117} 118 119EXPORTED_FLAGS_PROPS = { 120 'export_include_dirs', 121 'export_system_include_dirs', 122 'export_flags', 123} 124 125 126# Converts parsed json dictionary (which is intermediate) to Android.bp prop 127# dictionary. This validates paths such as include directories and init_rc 128# files while converting. 129def convert_json_to_bp_prop(json_path, bp_dir): 130 prop = json.load(json_path) 131 ret = {} 132 133 module_name = prop['ModuleName'] 134 ret['name'] = module_name 135 136 # Soong will complain about non-existing paths on Android.bp. There might 137 # be missing files among generated header files, so check all exported 138 # directories and filter out invalid ones. Emits warning for such dirs. 139 # TODO: fix soong to track all generated headers correctly 140 for key in {'ExportedDirs', 'ExportedSystemDirs'}: 141 if key in prop: 142 prop[key] = remove_invalid_dirs(prop[key], bp_dir, module_name) 143 144 for key in prop: 145 if key in JSON_TO_BP: 146 ret[JSON_TO_BP[key]] = prop[key] 147 else: 148 logging.warning('Unknown prop "%s" of module "%s"', key, 149 module_name) 150 151 return ret 152 153def is_64bit_arch(arch): 154 return '64' in arch # arm64, x86_64 155 156def remove_keys_from_dict(keys, d): 157 # May contain subdictionaries (e.g. cfi), so recursively erase 158 for k in list(d.keys()): 159 if k in keys: 160 del d[k] 161 elif type(d[k]) == dict: 162 remove_keys_from_dict(keys, d[k]) 163 164def reexport_vndk_header(name, arch_props): 165 remove_keys_from_dict(EXPORTED_FLAGS_PROPS, arch_props) 166 for arch in arch_props: 167 arch_props[arch]['shared_libs'] = [name] 168 arch_props[arch]['export_shared_lib_headers'] = [name] 169 170def gen_bp_module(image, variation, name, version, target_arch, vndk_list, arch_props, bp_dir): 171 # Generate Android.bp module for given snapshot. 172 # If a vndk library with the same name exists, reuses exported flags of the vndk library, 173 # instead of the snapshot's own flags. 174 prop = { 175 # These three are common for all snapshot modules. 176 'version': str(version), 177 'target_arch': target_arch, 178 image: True, 179 'arch': {}, 180 } 181 182 reexport_vndk_name = name 183 if reexport_vndk_name == "libc++_static": 184 reexport_vndk_name = "libc++" 185 186 if reexport_vndk_name in vndk_list: 187 if variation == 'shared': 188 logging.error("Module %s is both vendor snapshot shared and vndk" % name) 189 reexport_vndk_header(reexport_vndk_name, arch_props) 190 191 # Factor out common prop among architectures to minimize Android.bp. 192 common_prop = None 193 for arch in arch_props: 194 if common_prop is None: 195 common_prop = dict() 196 for k in arch_props[arch]: 197 common_prop[k] = arch_props[arch][k] 198 continue 199 for k in list(common_prop.keys()): 200 if k not in arch_props[arch] or common_prop[k] != arch_props[arch][k]: 201 del common_prop[k] 202 203 # Some keys has to be arch_props to prevent 32-bit only modules from being 204 # used as 64-bit modules, and vice versa. 205 for arch_prop_key in ['src', 'cfi']: 206 if arch_prop_key in common_prop: 207 del common_prop[arch_prop_key] 208 prop.update(common_prop) 209 210 has32 = has64 = False 211 stem32 = stem64 = '' 212 213 for arch in arch_props: 214 for k in common_prop: 215 if k in arch_props[arch]: 216 del arch_props[arch][k] 217 prop['arch'][arch] = arch_props[arch] 218 219 has64 |= is_64bit_arch(arch) 220 has32 |= not is_64bit_arch(arch) 221 222 # Record stem for snapshots. 223 # We don't check existence of 'src'; src must exist for executables 224 if variation == 'binary': 225 if is_64bit_arch(arch): 226 stem64 = os.path.basename(arch_props[arch]['src']) 227 else: 228 stem32 = os.path.basename(arch_props[arch]['src']) 229 230 # header snapshots doesn't need compile_multilib. The other snapshots, 231 # shared/static/object/binary snapshots, do need them 232 if variation != 'header': 233 if has32 and has64: 234 prop['compile_multilib'] = 'both' 235 elif has32: 236 prop['compile_multilib'] = '32' 237 elif has64: 238 prop['compile_multilib'] = '64' 239 else: 240 raise RuntimeError("Module %s doesn't have prebuilts." % name) 241 242 # For binary snapshots, prefer 64bit if their stem collide and installing 243 # both is impossible 244 if variation == 'binary' and stem32 == stem64: 245 prop['compile_multilib'] = 'first' 246 247 bp = '%s_snapshot_%s {\n' % (image, variation) 248 bp += gen_bp_prop(prop, INDENT) 249 bp += '}\n\n' 250 return bp 251 252def get_vndk_list(vndk_dir, target_arch): 253 """Generates vndk_libs list, e.g. ['libbase', 'libc++', ...] 254 This list is retrieved from vndk_dir/target_arch/configs/module_names.txt. 255 If it doesn't exist, print an error message and return an empty list. 256 """ 257 258 module_names_path = os.path.join(vndk_dir, target_arch, 'configs/module_names.txt') 259 260 try: 261 with open(module_names_path, 'r') as f: 262 """The format of module_names.txt is a list of "{so_name} {module_name}", e.g. 263 264 lib1.so lib1 265 lib2.so lib2 266 ... 267 268 We extract the module name part. 269 """ 270 return [l.split()[1] for l in f.read().strip('\n').split('\n')] 271 except IOError as e: 272 logging.error('Failed to read %s: %s' % (module_names_path, e.strerror)) 273 except IndexError as e: 274 logging.error('Failed to parse %s: invalid format' % module_names_path) 275 276 return [] 277 278def gen_bp_list_module(image, snapshot_version, vndk_list, target_arch, arch_props): 279 """Generates a {image}_snapshot module which contains lists of snapshots. 280 For vendor snapshot, vndk list is also included, extracted from vndk_dir. 281 """ 282 283 bp = '%s_snapshot {\n' % image 284 285 bp_props = dict() 286 bp_props['name'] = '%s_snapshot' % image 287 bp_props['version'] = str(snapshot_version) 288 if image == 'vendor': 289 bp_props['vndk_libs'] = vndk_list 290 291 variant_to_property = { 292 'shared': 'shared_libs', 293 'static': 'static_libs', 294 'header': 'header_libs', 295 'binary': 'binaries', 296 'object': 'objects', 297 } 298 299 # arch_bp_prop[arch][variant_prop] = list 300 # e.g. arch_bp_prop['x86']['shared_libs'] == ['libfoo', 'libbar', ...] 301 arch_bp_prop = dict() 302 303 # Gather module lists per arch. 304 # arch_props structure: arch_props[variant][module_name][arch] 305 # e.g. arch_props['shared']['libc++']['x86'] 306 for variant in arch_props: 307 variant_name = variant_to_property[variant] 308 for name in arch_props[variant]: 309 for arch in arch_props[variant][name]: 310 if arch not in arch_bp_prop: 311 arch_bp_prop[arch] = dict() 312 if variant_name not in arch_bp_prop[arch]: 313 arch_bp_prop[arch][variant_name] = [] 314 arch_bp_prop[arch][variant_name].append(name) 315 316 bp_props['arch'] = arch_bp_prop 317 bp += gen_bp_prop(bp_props, INDENT) 318 319 bp += '}\n\n' 320 return bp 321 322def build_props(install_dir): 323 # props[target_arch]["static"|"shared"|"binary"|"header"][name][arch] : json 324 props = dict() 325 326 # {target_arch}/{arch}/{variation}/{module}.json 327 for root, _, files in os.walk(install_dir, followlinks = True): 328 for file_name in sorted(files): 329 if not file_name.endswith('.json'): 330 continue 331 full_path = os.path.join(root, file_name) 332 rel_path = os.path.relpath(full_path, install_dir) 333 334 target_arch = get_target_arch(rel_path) 335 arch = get_arch(rel_path) 336 variation = get_variation(rel_path) 337 bp_dir = os.path.join(install_dir, target_arch) 338 339 if not target_arch in props: 340 props[target_arch] = dict() 341 if not variation in props[target_arch]: 342 props[target_arch][variation] = dict() 343 344 with open(full_path, 'r') as f: 345 prop = convert_json_to_bp_prop(f, bp_dir) 346 # Remove .json after parsing? 347 # os.unlink(full_path) 348 349 if variation != 'header': 350 prop['src'] = os.path.relpath( 351 rel_path[:-5], # removing .json 352 target_arch) 353 354 module_name = prop['name'] 355 356 # Is this sanitized variant? 357 if 'sanitize' in prop: 358 sanitizer_type = prop['sanitize'] 359 # module_name is {name}.{sanitizer_type}; trim sanitizer_type 360 module_name = module_name[:-len(sanitizer_type) - 1] 361 # Only leave props for the sanitize variant 362 for k in list(prop.keys()): 363 if not k in SANITIZER_VARIANT_PROPS: 364 del prop[k] 365 prop = {'name': module_name, sanitizer_type: prop} 366 367 notice_path = 'NOTICE_FILES/' + module_name + '.txt' 368 if os.path.exists(os.path.join(bp_dir, notice_path)): 369 prop['notice'] = notice_path 370 371 variation_dict = props[target_arch][variation] 372 if not module_name in variation_dict: 373 variation_dict[module_name] = dict() 374 if not arch in variation_dict[module_name]: 375 variation_dict[module_name][arch] = prop 376 else: 377 variation_dict[module_name][arch].update(prop) 378 379 return props 380 381 382def gen_bp_files(image, vndk_dir, install_dir, snapshot_version): 383 """Generates Android.bp for each archtecture. 384 Android.bp will contain a {image}_snapshot module having lists of VNDK and 385 vendor snapshot libraries, and {image}_snapshot_{variant} modules which are 386 prebuilt libraries of the snapshot. 387 388 Args: 389 image: string, name of partition (e.g. 'vendor', 'recovery') 390 vndk_dir: string, directory to which vndk snapshot is installed 391 install_dir: string, directory to which the snapshot will be installed 392 snapshot_version: int, version of the snapshot 393 """ 394 props = build_props(install_dir) 395 396 for target_arch in sorted(props): 397 androidbp = '' 398 bp_dir = os.path.join(install_dir, target_arch) 399 vndk_list = [] 400 if image == 'vendor': 401 vndk_list = get_vndk_list(vndk_dir, target_arch) 402 403 # Generate snapshot modules. 404 for variation in sorted(props[target_arch]): 405 for name in sorted(props[target_arch][variation]): 406 androidbp += gen_bp_module(image, variation, name, 407 snapshot_version, target_arch, 408 vndk_list, 409 props[target_arch][variation][name], 410 bp_dir) 411 412 # Generate {image}_snapshot module which contains the list of modules. 413 androidbp += gen_bp_list_module(image, snapshot_version, vndk_list, 414 target_arch, props[target_arch]) 415 with open(os.path.join(bp_dir, 'Android.bp'), 'w') as f: 416 logging.info('Generating Android.bp to: {}'.format(f.name)) 417 f.write(androidbp) 418 419 420def find_all_installed_files(install_dir): 421 installed_files = dict() 422 for root, _, files in os.walk(install_dir, followlinks = True): 423 for file_name in sorted(files): 424 if file_name.endswith('.json'): 425 continue 426 if file_name.endswith('Android.bp'): 427 continue 428 full_path = os.path.join(root, file_name) 429 size = os.stat(full_path).st_size 430 installed_files[full_path] = size 431 432 logging.debug('') 433 for f in sorted(installed_files.keys()): 434 logging.debug(f) 435 logging.debug('') 436 logging.debug('found {} installed files'.format(len(installed_files))) 437 logging.debug('') 438 return installed_files 439 440 441def find_files_in_props(target_arch, arch_install_dir, variation, name, props, file_to_info): 442 logging.debug('{} {} {} {} {}'.format( 443 target_arch, arch_install_dir, variation, name, props)) 444 445 def add_info(file, name, variation, arch, is_cfi, is_header): 446 info = (name, variation, arch, is_cfi, is_header) 447 info_list = file_to_info.get(file) 448 if not info_list: 449 info_list = [] 450 file_to_info[file] = info_list 451 info_list.append(info) 452 453 def find_file_in_list(dict, key, is_cfi): 454 list = dict.get(key) 455 logging.debug(' {} {}'.format(key, list)) 456 if list: 457 for item in list: 458 item_path = os.path.join(arch_install_dir, item) 459 add_info(item_path, name, variation, arch, is_cfi, False) 460 461 def find_file_in_dirs(dict, key, is_cfi, is_header): 462 dirs = dict.get(key) 463 logging.debug(' {} {}'.format(key, dirs)) 464 if dirs: 465 for dir in dirs: 466 dir_path = os.path.join(arch_install_dir, dir) 467 logging.debug(' scanning {}'.format(dir_path)) 468 for root, _, files in os.walk(dir_path, followlinks = True): 469 for file_name in sorted(files): 470 item_path = os.path.join(root, file_name) 471 add_info(item_path, name, variation, arch, is_cfi, is_header) 472 473 def find_file_in_dict(dict, is_cfi): 474 logging.debug(' arch {}'.format(arch)) 475 logging.debug(' name {}'.format( name)) 476 logging.debug(' is_cfi {}'.format(is_cfi)) 477 478 src = dict.get('src') 479 logging.debug(' src {}'.format(src)) 480 if src: 481 src_path = os.path.join(arch_install_dir, src) 482 add_info(src_path, name, variation, arch, is_cfi, False) 483 484 notice = dict.get('notice') 485 logging.debug(' notice {}'.format(notice)) 486 if notice: 487 notice_path = os.path.join(arch_install_dir, notice) 488 add_info(notice_path, name, variation, arch, is_cfi, False) 489 490 find_file_in_list(dict, 'init_rc', is_cfi) 491 find_file_in_list(dict, 'vintf_fragments', is_cfi) 492 493 find_file_in_dirs(dict, 'export_include_dirs', is_cfi, True) 494 find_file_in_dirs(dict, 'export_system_include_dirs', is_cfi, True) 495 496 for arch in sorted(props): 497 name = props[arch]['name'] 498 find_file_in_dict(props[arch], False) 499 cfi = props[arch].get('cfi') 500 if cfi: 501 find_file_in_dict(cfi, True) 502 503 504def find_all_props_files(install_dir): 505 506 # This function builds a database of filename to module. This means that we 507 # need to dive into the json to find the files that the vendor snapshot 508 # provides, and link these back to modules that provide them. 509 510 file_to_info = dict() 511 512 props = build_props(install_dir) 513 for target_arch in sorted(props): 514 arch_install_dir = os.path.join(install_dir, target_arch) 515 for variation in sorted(props[target_arch]): 516 for name in sorted(props[target_arch][variation]): 517 find_files_in_props( 518 target_arch, 519 arch_install_dir, 520 variation, 521 name, 522 props[target_arch][variation][name], 523 file_to_info) 524 525 logging.debug('') 526 for f in sorted(file_to_info.keys()): 527 logging.debug(f) 528 logging.debug('') 529 logging.debug('found {} props files'.format(len(file_to_info))) 530 logging.debug('') 531 return file_to_info 532 533 534def get_ninja_inputs(ninja_binary, ninja_build_file, modules): 535 """Returns the set of input file path strings for the given modules. 536 537 Uses the `ninja -t inputs` tool. 538 539 Args: 540 ninja_binary: The path to a ninja binary. 541 ninja_build_file: The path to a .ninja file from a build. 542 modules: The list of modules to scan for inputs. 543 """ 544 inputs = set() 545 cmd = [ 546 ninja_binary, 547 "-f", 548 ninja_build_file, 549 "-t", 550 "inputs", 551 "-d", 552 ] + list(modules) 553 logging.debug('invoke ninja {}'.format(cmd)) 554 inputs = inputs.union(set( 555 subprocess.check_output(cmd).decode().strip("\n").split("\n"))) 556 557 return inputs 558 559 560def check_module_usage(install_dir, ninja_binary, image, ninja_file, goals, 561 output): 562 all_installed_files = find_all_installed_files(install_dir) 563 all_props_files = find_all_props_files(install_dir) 564 565 ninja_inputs = get_ninja_inputs(ninja_binary, ninja_file, goals) 566 logging.debug('') 567 logging.debug('ninja inputs') 568 for ni in ninja_inputs: 569 logging.debug(ni) 570 571 logging.debug('found {} ninja_inputs for goals {}'.format( 572 len(ninja_inputs), goals)) 573 574 # Intersect the file_to_info dict with the ninja_inputs to determine 575 # which items from the vendor snapshot are actually used by the goals. 576 577 total_size = 0 578 used_size = 0 579 used_file_to_info = dict() 580 581 for file, size in all_installed_files.items(): 582 total_size += size 583 if file in ninja_inputs: 584 logging.debug('used: {}'.format(file)) 585 used_size += size 586 info = all_props_files.get(file) 587 588 if info: 589 used_file_to_info[file] = info 590 else: 591 logging.warning('No info for file {}'.format(file)) 592 used_file_to_info[file] = 'no info' 593 594 logging.debug('Total size {}'.format(total_size)) 595 logging.debug('Used size {}'.format(used_size)) 596 logging.debug('') 597 logging.debug('used items') 598 599 used_modules = set() 600 601 for f, i in sorted(used_file_to_info.items()): 602 logging.debug('{} {}'.format(f, i)) 603 for m in i: 604 (name, variation, arch, is_cfi, is_header) = m 605 if not is_header: 606 used_modules.add(name) 607 608 with open(output, 'w') as f: 609 f.write('%s_SNAPSHOT_MODULES := \\\n' % image.upper()) 610 for m in sorted(used_modules): 611 f.write(' %s \\\n' % m) 612 613def check_call(cmd): 614 logging.debug('Running `{}`'.format(' '.join(cmd))) 615 subprocess.check_call(cmd) 616 617 618def fetch_artifact(branch, build, target, pattern, destination): 619 """Fetches build artifacts from Android Build server. 620 621 Args: 622 branch: string, branch to pull build artifacts from 623 build: string, build number to pull build artifacts from 624 target: string, target name to pull build artifacts from 625 pattern: string, pattern of build artifact file name 626 destination: string, destination to pull build artifact to 627 """ 628 fetch_artifact_path = '/google/data/ro/projects/android/fetch_artifact' 629 cmd = [ 630 fetch_artifact_path, '--branch', branch, '--target', target, '--bid', 631 build, pattern, destination 632 ] 633 check_call(cmd) 634 635def install_artifacts(image, branch, build, target, local_dir, symlink, 636 install_dir): 637 """Installs vendor snapshot build artifacts to {install_dir}/v{version}. 638 639 1) Fetch build artifacts from Android Build server or from local_dir 640 2) Unzip or create symlinks to build artifacts 641 642 Args: 643 image: string, img file for which the snapshot was created (vendor, 644 recovery, etc.) 645 branch: string or None, branch name of build artifacts 646 build: string or None, build number of build artifacts 647 target: string or None, target name of build artifacts 648 local_dir: string or None, local dir to pull artifacts from 649 symlink: boolean, whether to use symlinks instead of unzipping the 650 vendor snapshot zip 651 install_dir: string, directory to install vendor snapshot 652 temp_artifact_dir: string, temp directory to hold build artifacts fetched 653 from Android Build server. For 'local' option, is set to None. 654 """ 655 artifact_pattern = image + '-*.zip' 656 657 def unzip_artifacts(artifact_dir): 658 artifacts = glob.glob(os.path.join(artifact_dir, artifact_pattern)) 659 for artifact in artifacts: 660 logging.info('Unzipping Vendor snapshot: {}'.format(artifact)) 661 check_call(['unzip', '-qn', artifact, '-d', install_dir]) 662 663 if branch and build and target: 664 with tempfile.TemporaryDirectory() as tmpdir: 665 logging.info( 666 'Fetching {pattern} from {branch} (bid: {build}, target: {target})' 667 .format( 668 pattern=artifact_pattern, 669 branch=branch, 670 build=build, 671 target=target)) 672 fetch_artifact(branch, build, target, artifact_pattern, tmpdir) 673 unzip_artifacts(tmpdir) 674 elif local_dir: 675 if symlink: 676 # This assumes local_dir is the location of vendor-snapshot in the 677 # build (e.g., out/soong/vendor-snapshot). 678 # 679 # Create the first level as proper directories and the next level 680 # as symlinks. 681 for item1 in os.listdir(local_dir): 682 dest_dir = os.path.join(install_dir, item1) 683 src_dir = os.path.join(local_dir, item1) 684 if os.path.isdir(src_dir): 685 check_call(['mkdir', '-p', dest_dir]) 686 # Create symlinks. 687 for item2 in os.listdir(src_dir): 688 src_item = os.path.join(src_dir, item2) 689 logging.info('Creating symlink from {} in {}'.format( 690 src_item, dest_dir)) 691 os.symlink(src_item, os.path.join(dest_dir, item2)) 692 else: 693 logging.info('Fetching local VNDK snapshot from {}'.format( 694 local_dir)) 695 unzip_artifacts(local_dir) 696 else: 697 raise RuntimeError('Neither local nor remote fetch information given.') 698 699def get_args(): 700 parser = argparse.ArgumentParser() 701 parser.add_argument( 702 'snapshot_version', 703 type=int, 704 help='Vendor snapshot version to install, e.g. "30".') 705 parser.add_argument( 706 '--image', 707 help=('Image whose snapshot is being updated (e.g., vendor, ' 708 'recovery , ramdisk, etc.)'), 709 default='vendor') 710 parser.add_argument('--branch', help='Branch to pull build from.') 711 parser.add_argument('--build', help='Build number to pull.') 712 parser.add_argument('--target', help='Target to pull.') 713 parser.add_argument( 714 '--local', 715 help=('Fetch local vendor snapshot artifacts from specified local ' 716 'directory instead of Android Build server. ' 717 'Example: --local /path/to/local/dir')) 718 parser.add_argument( 719 '--symlink', 720 action='store_true', 721 help='Use symlinks instead of unzipping vendor snapshot zip') 722 parser.add_argument( 723 '--install-dir', 724 required=True, 725 help=( 726 'Base directory to which vendor snapshot artifacts are installed. ' 727 'Example: --install-dir vendor/<company name>/vendor_snapshot/v30')) 728 parser.add_argument( 729 '--overwrite', 730 action='store_true', 731 help=( 732 'If provided, does not ask before overwriting the install-dir.')) 733 parser.add_argument( 734 '--check-module-usage', 735 action='store_true', 736 help='Check which modules are used.') 737 parser.add_argument( 738 '--check-module-usage-goal', 739 action='append', 740 help='Goal(s) for which --check-module-usage is calculated.') 741 parser.add_argument( 742 '--check-module-usage-ninja-file', 743 help='Ninja file for which --check-module-usage is calculated.') 744 parser.add_argument( 745 '--check-module-usage-output', 746 help='File to which to write the check-module-usage results.') 747 parser.add_argument( 748 '--vndk-dir', 749 help='Path to installed vndk snapshot directory. Needed to retrieve ' 750 'the list of VNDK. prebuilts/vndk/v{ver} will be used by default.') 751 752 parser.add_argument( 753 '-v', 754 '--verbose', 755 action='count', 756 default=0, 757 help='Increase output verbosity, e.g. "-v", "-vv".') 758 return parser.parse_args() 759 760 761def main(): 762 """Program entry point.""" 763 args = get_args() 764 765 verbose_map = (logging.WARNING, logging.INFO, logging.DEBUG) 766 verbosity = min(args.verbose, 2) 767 logging.basicConfig( 768 format='%(levelname)-8s [%(filename)s:%(lineno)d] %(message)s', 769 level=verbose_map[verbosity]) 770 771 if not args.install_dir: 772 raise ValueError('Please provide --install-dir option.') 773 install_dir = os.path.expanduser(args.install_dir) 774 775 if args.check_module_usage: 776 ninja_binary = './prebuilts/build-tools/linux-x86/bin/ninja' 777 778 if not args.check_module_usage_goal: 779 raise ValueError('Please provide --check-module-usage-goal option.') 780 if not args.check_module_usage_ninja_file: 781 raise ValueError( 782 'Please provide --check-module-usage-ninja-file option.') 783 if not args.check_module_usage_output: 784 raise ValueError( 785 'Please provide --check-module-usage-output option.') 786 787 check_module_usage(install_dir, ninja_binary, args.image, 788 args.check_module_usage_ninja_file, 789 args.check_module_usage_goal, 790 args.check_module_usage_output) 791 return 792 793 local = None 794 if args.local: 795 local = os.path.expanduser(args.local) 796 797 if local: 798 if args.build or args.branch or args.target: 799 raise ValueError( 800 'When --local option is set, --branch, --build or --target cannot be ' 801 'specified.') 802 elif not os.path.isdir(local): 803 raise RuntimeError( 804 'The specified local directory, {}, does not exist.'.format( 805 local)) 806 else: 807 if not (args.build and args.branch and args.target): 808 raise ValueError( 809 'Please provide --branch, --build and --target. Or set --local ' 810 'option.') 811 812 snapshot_version = args.snapshot_version 813 814 if os.path.exists(install_dir): 815 def remove_dir(): 816 logging.info('Removing {}'.format(install_dir)) 817 check_call(['rm', '-rf', install_dir]) 818 if args.overwrite: 819 remove_dir() 820 else: 821 resp = input('Directory {} already exists. IT WILL BE REMOVED.\n' 822 'Are you sure? (yes/no): '.format(install_dir)) 823 if resp == 'yes': 824 remove_dir() 825 elif resp == 'no': 826 logging.info('Cancelled snapshot install.') 827 return 828 else: 829 raise ValueError('Did not understand: ' + resp) 830 check_call(['mkdir', '-p', install_dir]) 831 832 if args.vndk_dir: 833 vndk_dir = os.path.expanduser(args.vndk_dir) 834 else: 835 vndk_dir = 'prebuilts/vndk/v%d' % snapshot_version 836 logging.debug('Using %s for vndk directory' % vndk_dir) 837 838 install_artifacts( 839 image=args.image, 840 branch=args.branch, 841 build=args.build, 842 target=args.target, 843 local_dir=local, 844 symlink=args.symlink, 845 install_dir=install_dir) 846 gen_bp_files(args.image, vndk_dir, install_dir, snapshot_version) 847 848if __name__ == '__main__': 849 main() 850