Merge "Extend the update.py script to determine usage." am: 8aedba5144

Original change: https://android-review.googlesource.com/c/platform/development/+/1560028

MUST ONLY BE SUBMITTED BY AUTOMERGER

Change-Id: Ibc6587d2e7d9882a1ce7f6e084885ff4930556f2
This commit is contained in:
Jose "Pepe" Galmes
2021-01-29 22:24:15 +00:00
committed by Automerger Merge Worker

View File

@@ -285,6 +285,198 @@ def gen_bp_files(image, install_dir, snapshot_version):
f.write(androidbp) f.write(androidbp)
def find_all_installed_files(install_dir):
installed_files = dict()
for root, _, files in os.walk(install_dir, followlinks = True):
for file_name in sorted(files):
if file_name.endswith('.json'):
continue
if file_name.endswith('Android.bp'):
continue
full_path = os.path.join(root, file_name)
size = os.stat(full_path).st_size
installed_files[full_path] = size
logging.debug('')
for f in sorted(installed_files.keys()):
logging.debug(f)
logging.debug('')
logging.debug('found {} installed files'.format(len(installed_files)))
logging.debug('')
return installed_files
def find_files_in_props(target_arch, arch_install_dir, variation, name, props, file_to_info):
logging.debug('{} {} {} {} {}'.format(
target_arch, arch_install_dir, variation, name, props))
def add_info(file, name, variation, arch, is_cfi, is_header):
info = (name, variation, arch, is_cfi, is_header)
info_list = file_to_info.get(file)
if not info_list:
info_list = []
file_to_info[file] = info_list
info_list.append(info)
def find_file_in_list(dict, key, is_cfi):
list = dict.get(key)
logging.debug(' {} {}'.format(key, list))
if list:
for item in list:
item_path = os.path.join(arch_install_dir, item)
add_info(item_path, name, variation, arch, is_cfi, False)
def find_file_in_dirs(dict, key, is_cfi, is_header):
dirs = dict.get(key)
logging.debug(' {} {}'.format(key, dirs))
if dirs:
for dir in dirs:
dir_path = os.path.join(arch_install_dir, dir)
logging.debug(' scanning {}'.format(dir_path))
for root, _, files in os.walk(dir_path, followlinks = True):
for file_name in sorted(files):
item_path = os.path.join(root, file_name)
add_info(item_path, name, variation, arch, is_cfi, is_header)
def find_file_in_dict(dict, is_cfi):
logging.debug(' arch {}'.format(arch))
logging.debug(' name {}'.format( name))
logging.debug(' is_cfi {}'.format(is_cfi))
src = dict.get('src')
logging.debug(' src {}'.format(src))
if src:
src_path = os.path.join(arch_install_dir, src)
add_info(src_path, name, variation, arch, is_cfi, False)
notice = dict.get('notice')
logging.debug(' notice {}'.format(notice))
if notice:
notice_path = os.path.join(arch_install_dir, notice)
add_info(notice_path, name, variation, arch, is_cfi, False)
find_file_in_list(dict, 'init_rc', is_cfi)
find_file_in_list(dict, 'vintf_fragments', is_cfi)
find_file_in_dirs(dict, 'export_include_dirs', is_cfi, True)
find_file_in_dirs(dict, 'export_system_include_dirs', is_cfi, True)
for arch in sorted(props):
name = props[arch]['name']
find_file_in_dict(props[arch], False)
cfi = props[arch].get('cfi')
if cfi:
find_file_in_dict(cfi, True)
def find_all_props_files(install_dir):
# This function builds a database of filename to module. This means that we
# need to dive into the json to find the files that the vendor snapshot
# provides, and link these back to modules that provide them.
file_to_info = dict()
props = build_props(install_dir)
for target_arch in sorted(props):
arch_install_dir = os.path.join(install_dir, target_arch)
for variation in sorted(props[target_arch]):
for name in sorted(props[target_arch][variation]):
find_files_in_props(
target_arch,
arch_install_dir,
variation,
name,
props[target_arch][variation][name],
file_to_info)
logging.debug('')
for f in sorted(file_to_info.keys()):
logging.debug(f)
logging.debug('')
logging.debug('found {} props files'.format(len(file_to_info)))
logging.debug('')
return file_to_info
def get_ninja_inputs(ninja_binary, ninja_build_file, modules):
"""Returns the set of input file path strings for the given modules.
Uses the `ninja -t inputs` tool.
Args:
ninja_binary: The path to a ninja binary.
ninja_build_file: The path to a .ninja file from a build.
modules: The list of modules to scan for inputs.
"""
inputs = set()
cmd = [
ninja_binary,
"-f",
ninja_build_file,
"-t",
"inputs",
"-d",
] + list(modules)
logging.debug('invoke ninja {}'.format(cmd))
inputs = inputs.union(set(
subprocess.check_output(cmd).decode().strip("\n").split("\n")))
return inputs
def check_module_usage(install_dir, ninja_binary, ninja_file, goals, output):
all_installed_files = find_all_installed_files(install_dir)
all_props_files = find_all_props_files(install_dir)
ninja_inputs = get_ninja_inputs(ninja_binary, ninja_file, goals)
logging.debug('')
logging.debug('ninja inputs')
for ni in ninja_inputs:
logging.debug(ni)
logging.debug('found {} ninja_inputs for goals {}'.format(
len(ninja_inputs), goals))
# Intersect the file_to_info dict with the ninja_inputs to determine
# which items from the vendor snapshot are actually used by the goals.
total_size = 0
used_size = 0
used_file_to_info = dict()
for file, size in all_installed_files.items():
total_size += size
if file in ninja_inputs:
logging.debug('used: {}'.format(file))
used_size += size
info = all_props_files.get(file)
if info:
used_file_to_info[file] = info
else:
logging.warning('No info for file {}'.format(file))
used_file_to_info[file] = 'no info'
logging.debug('Total size {}'.format(total_size))
logging.debug('Used size {}'.format(used_size))
logging.debug('')
logging.debug('used items')
used_modules = set()
for f, i in sorted(used_file_to_info.items()):
logging.debug('{} {}'.format(f, i))
for m in i:
key = 'n=%s,v=%s,a=%s,c=%s,h=%s' % m
(name, variation, arch, is_cfi, is_header) = m
if not is_header:
used_modules.add(key)
with open(output, 'w') as f:
for m in sorted(used_modules):
f.write('%s\n' % m)
def check_call(cmd): def check_call(cmd):
logging.debug('Running `{}`'.format(' '.join(cmd))) logging.debug('Running `{}`'.format(' '.join(cmd)))
subprocess.check_call(cmd) subprocess.check_call(cmd)
@@ -396,6 +588,7 @@ def get_args():
help='Use symlinks instead of unzipping vendor snapshot zip') help='Use symlinks instead of unzipping vendor snapshot zip')
parser.add_argument( parser.add_argument(
'--install-dir', '--install-dir',
required=True,
help=( help=(
'Base directory to which vendor snapshot artifacts are installed. ' 'Base directory to which vendor snapshot artifacts are installed. '
'Example: --install-dir vendor/<company name>/vendor_snapshot/v30')) 'Example: --install-dir vendor/<company name>/vendor_snapshot/v30'))
@@ -404,6 +597,20 @@ def get_args():
action='store_true', action='store_true',
help=( help=(
'If provided, does not ask before overwriting the install-dir.')) 'If provided, does not ask before overwriting the install-dir.'))
parser.add_argument(
'--check-module-usage',
action='store_true',
help='Check which modules are used.')
parser.add_argument(
'--check-module-usage-goal',
action='append',
help='Goal(s) for which --check-module-usage is calculated.')
parser.add_argument(
'--check-module-usage-ninja-file',
help='Ninja file for which --check-module-usage is calculated.')
parser.add_argument(
'--check-module-usage-output',
help='File to which to write the check-module-usage results.')
parser.add_argument( parser.add_argument(
'-v', '-v',
@@ -418,6 +625,34 @@ def main():
"""Program entry point.""" """Program entry point."""
args = get_args() args = get_args()
verbose_map = (logging.WARNING, logging.INFO, logging.DEBUG)
verbosity = min(args.verbose, 2)
logging.basicConfig(
format='%(levelname)-8s [%(filename)s:%(lineno)d] %(message)s',
level=verbose_map[verbosity])
if not args.install_dir:
raise ValueError('Please provide --install-dir option.')
install_dir = os.path.expanduser(args.install_dir)
if args.check_module_usage:
ninja_binary = './prebuilts/build-tools/linux-x86/bin/ninja'
if not args.check_module_usage_goal:
raise ValueError('Please provide --check-module-usage-goal option.')
if not args.check_module_usage_ninja_file:
raise ValueError(
'Please provide --check-module-usage-ninja-file option.')
if not args.check_module_usage_output:
raise ValueError(
'Please provide --check-module-usage-output option.')
check_module_usage(install_dir, ninja_binary,
args.check_module_usage_ninja_file,
args.check_module_usage_goal,
args.check_module_usage_output)
return
local = None local = None
if args.local: if args.local:
local = os.path.expanduser(args.local) local = os.path.expanduser(args.local)
@@ -437,18 +672,8 @@ def main():
'Please provide --branch, --build and --target. Or set --local ' 'Please provide --branch, --build and --target. Or set --local '
'option.') 'option.')
if not args.install_dir:
raise ValueError('Please provide --install-dir option.')
snapshot_version = args.snapshot_version snapshot_version = args.snapshot_version
verbose_map = (logging.WARNING, logging.INFO, logging.DEBUG)
verbosity = min(args.verbose, 2)
logging.basicConfig(
format='%(levelname)-8s [%(filename)s:%(lineno)d] %(message)s',
level=verbose_map[verbosity])
install_dir = os.path.expanduser(args.install_dir)
if os.path.exists(install_dir): if os.path.exists(install_dir):
def remove_dir(): def remove_dir():
logging.info('Removing {}'.format(install_dir)) logging.info('Removing {}'.format(install_dir))