# Copyright 2022 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Logic needed by multiple archive-related modules."""
import logging
import os
import re
import models
def ExtendSectionRange(section_range_by_name, section_name, delta_size):
"""Adds |delta_size| to |section_name|'s size in |section_range_by_name|."""
prev_address, prev_size = section_range_by_name.get(section_name, (0, 0))
section_range_by_name[section_name] = (prev_address, prev_size + delta_size)
def _NormalizeObjectPath(path, obj_prefixes):
"""Normalizes object paths.
Prefixes are removed: obj/, ../../
Archive names made more pathy: foo/bar.a(baz.o) -> foo/bar.a/baz.o
"""
if path.startswith('../../'):
# Convert ../../third_party/... -> third_party/...
path = path[6:]
elif path.startswith('/'):
# Convert absolute paths to $SYSTEM/basename.o.
path = os.path.join(models.SYSTEM_PREFIX_PATH, os.path.basename(path))
else:
# Convert obj/third_party/... -> third_party/...
for prefix in obj_prefixes:
if path.startswith(prefix):
path = path[len(prefix):]
if path.endswith(')'):
# Convert foo/bar.a(baz.o) -> foo/bar.a/baz.o so that hierarchical
# breakdowns consider the .o part to be a separate node.
start_idx = path.rindex('(')
path = os.path.join(path[:start_idx], path[start_idx + 1:-1])
return path
def _NormalizeSourcePath(path, gen_prefixes, gen_dir_pattern):
"""Returns (is_generated, normalized_path)"""
# Don't change $APK/, or $NATIVE/ paths.
if path.startswith('$'):
return False, path
if gen_dir_pattern:
# Non-chromium gen_dir logic.
m = gen_dir_pattern.match(path)
if m:
return True, path[m.end():]
return False, path
if path.startswith('../../'):
# Convert ../../third_party/... -> third_party/...
return False, path[6:]
if path.startswith('/'):
# Convert absolute paths to $SYSTEM/basename.cpp.
# E.g.: /buildbot/src/android/ndk-release-r23/toolchain/llvm-project/
# libcxx/src/vector.cpp
path = os.path.join(models.SYSTEM_PREFIX_PATH, os.path.basename(path))
# Convert gen/third_party/... -> third_party/...
for prefix in gen_prefixes:
if path.startswith(prefix):
return True, path[len(prefix):]
return True, path
def NormalizePaths(raw_symbols, gen_dir_regex=None, toolchain_subdirs=None):
"""Fills in the |source_path| attribute and normalizes |object_path|."""
logging.info('Normalizing source and object paths')
gen_dir_pattern = re.compile(gen_dir_regex) if gen_dir_regex else None
obj_prefixes = ['obj/']
gen_prefixes = ['gen/']
if toolchain_subdirs != None:
obj_prefixes.extend(f'{t}/obj/' for t in toolchain_subdirs)
gen_prefixes.extend(f'{t}/gen/' for t in toolchain_subdirs)
for symbol in raw_symbols:
if symbol.object_path:
symbol.object_path = _NormalizeObjectPath(symbol.object_path,
obj_prefixes)
if symbol.source_path:
symbol.generated_source, symbol.source_path = _NormalizeSourcePath(
symbol.source_path, gen_prefixes, gen_dir_pattern)
def _ComputeAncestorPath(path_list, symbol_count):
"""Returns the common ancestor of the given paths."""
if not path_list:
return ''
prefix = os.path.commonprefix(path_list)
# Check if all paths were the same.
if prefix == path_list[0]:
return prefix
# Put in buckets to cut down on the number of unique paths.
if symbol_count >= 100:
symbol_count_str = '100+'
elif symbol_count >= 50:
symbol_count_str = '50-99'
elif symbol_count >= 20:
symbol_count_str = '20-49'
elif symbol_count >= 10:
symbol_count_str = '10-19'
else:
symbol_count_str = str(symbol_count)
# Put the path count as a subdirectory so that grouping by path will show
# "{shared}" as a bucket, and the symbol counts as leafs.
if not prefix:
return os.path.join('{shared}', symbol_count_str)
return os.path.join(os.path.dirname(prefix), '{shared}', symbol_count_str)
def CompactLargeAliasesIntoSharedSymbols(raw_symbols, max_count):
"""Converts symbols with large number of aliases into single symbols.
The merged symbol's path fields are changed to common-ancestor paths in
the form: common/dir/{shared}/$SYMBOL_COUNT
Assumes aliases differ only by path (not by name).
"""
num_raw_symbols = len(raw_symbols)
num_shared_symbols = 0
src_cursor = 0
dst_cursor = 0
while src_cursor < num_raw_symbols:
symbol = raw_symbols[src_cursor]
raw_symbols[dst_cursor] = symbol
dst_cursor += 1
aliases = symbol.aliases
if aliases and len(aliases) > max_count:
symbol.source_path = _ComputeAncestorPath(
[s.source_path for s in aliases if s.source_path], len(aliases))
symbol.object_path = _ComputeAncestorPath(
[s.object_path for s in aliases if s.object_path], len(aliases))
symbol.generated_source = all(s.generated_source for s in aliases)
symbol.aliases = None
num_shared_symbols += 1
src_cursor += len(aliases)
else:
src_cursor += 1
raw_symbols[dst_cursor:] = []
num_removed = src_cursor - dst_cursor
logging.debug('Converted %d aliases into %d shared-path symbols', num_removed,
num_shared_symbols)
def RemoveAssetSuffix(path):
"""Undo asset path suffixing. https://crbug.com/357131361"""
# E.g.: "assets/foo.pak+org.foo.bar+"
if path.endswith('+'):
suffix_idx = path.rfind('+', 0, len(path) - 1)
if suffix_idx != -1:
path = path[:suffix_idx]
return path