Commit 6ed3b245 authored by Samuel Huang's avatar Samuel Huang Committed by Commit Bot

[SuperSize] Fix default src root to find OWNERS files.

Two notions of "src root" in SuperSize are:

(1) SuperSize script's src root |path_util.SRC_ROOT|. This is useful
    for finding tools used by SuperSize.
(2) The src root of the input binary (i.e., Chrome) given for
    SuperSize-archive, and can be specified by the --source-directory
    switch. This is used to find OWNER files to extract symbol
    component values.

Previously SuperSize uses (1) as the default value of (2). Normally this
is okay since (1) and (2) coincide. However, for development we may need
to compare the results of two versions of SuperSize (with different (1)
src root) on common binaries (common (2) src root). In this case,
defaulting (2) to (1) would lead to inconsistent default results
(fixable by --source-directory, but this is error-prone manually).

This CL fixes the problem by changing the default value for (2), by
having SuperSize search for src root using the output directory as the
starting point, i.e., map
  /.../src/out/Debug/ -> /.../src ,
and falls back to (1) only if results are inconclusive. Details:

* path_util.py:
  * For (1): Rename |SRC_ROOT| to |TOOLS_SRC_ROOT|.
  * Add GetSrcRootFromOutputDirectory().
  * Rename {From, To}SrcRootRelative() to
    {From, To}ToolsSrcRootRelative().
* archive.py:
  * ContainerArchiveOptions: Take optional |output_directory| in
    __init__() to compute default (2) |self.src_root|.
  * _ParseComponentFromOwners(): Take ContainerArchiveOptions param,
    and clean up to avoid recursion.
* Update tests:
  * Specify --source-directory.
  * Mock OWNER files no longer refer to actual Chrome directories.

Bug: 1040645
Change-Id: I8f72608491624eddf5790c1a1fc240bfd23ca9cb
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2171855
Commit-Queue: Samuel Huang <huangs@chromium.org>
Reviewed-by: default avatarAndrew Grieve <agrieve@chromium.org>
Cr-Commit-Position: refs/heads/master@{#763985}
parent 0da941d8
...@@ -38,7 +38,7 @@ import string_extract ...@@ -38,7 +38,7 @@ import string_extract
import zip_util import zip_util
sys.path.insert(1, os.path.join(path_util.SRC_ROOT, 'tools', 'grit')) sys.path.insert(1, os.path.join(path_util.TOOLS_SRC_ROOT, 'tools', 'grit'))
from grit.format import data_pack from grit.format import data_pack
_OWNERS_FILENAME = 'OWNERS' _OWNERS_FILENAME = 'OWNERS'
...@@ -116,8 +116,9 @@ class SectionSizeKnobs(object): ...@@ -116,8 +116,9 @@ class SectionSizeKnobs(object):
# Parameters and states for archiving a container. # Parameters and states for archiving a container.
class ContainerArchiveOptions: class ContainerArchiveOptions:
def __init__(self, sub_args): def __init__(self, sub_args, output_directory=''):
self.src_root = sub_args.source_directory or path_util.SRC_ROOT self.src_root = (sub_args.source_directory or
path_util.GetSrcRootFromOutputDirectory(output_directory))
# An estimate of pak translation compression ratio to make comparisons # An estimate of pak translation compression ratio to make comparisons
# between .size files reasonable. Otherwise this can differ every pak # between .size files reasonable. Otherwise this can differ every pak
...@@ -532,36 +533,41 @@ def _CreateMergeStringsReplacements(merge_string_syms, ...@@ -532,36 +533,41 @@ def _CreateMergeStringsReplacements(merge_string_syms,
return ret return ret
def _ParseComponentFromOwners(filename): def _ParseComponentFromOwners(filename, opts):
"""Searches an OWNERS file for lines that start with `# COMPONENT:`. """Searches an OWNERS file for lines that start with `# COMPONENT:`.
If an OWNERS file has no COMPONENT but references another OWNERS file, follow If an OWNERS file has no COMPONENT but references exactly one other OWNERS
the reference and check that file instead. file, follows the reference and checks that file instead.
Args: Args:
filename: Path to the file to parse. filename: Path to the file to parse.
opts: Instance of ContainerArchiveOptions.
Returns: Returns:
The text that follows the `# COMPONENT:` prefix, such as 'component>name'. The text that follows the `# COMPONENT:` prefix, such as 'component>name'.
Empty string if no component found or the file didn't exist. Empty string if no component found or the file doesn't exist.
""" """
reference_paths = [] seen = set()
try: while True:
with open(filename) as f: seen.add(filename)
for line in f: reference_paths = []
component_matches = _COMPONENT_REGEX.match(line) try:
path_matches = _FILE_PATH_REGEX.match(line) with open(filename) as f:
if component_matches: for line in f:
return component_matches.group(1) component_matches = _COMPONENT_REGEX.match(line)
elif path_matches: path_matches = _FILE_PATH_REGEX.match(line)
reference_paths.append(path_matches.group(1)) if component_matches:
except IOError: return component_matches.group(1)
return '' elif path_matches:
reference_paths.append(path_matches.group(1))
if len(reference_paths) == 1: except IOError:
newpath = os.path.join(path_util.SRC_ROOT, reference_paths[0]) break
return _ParseComponentFromOwners(newpath) if len(reference_paths) != 1:
else: break
return '' filename = os.path.join(opts.src_root, reference_paths[0])
if filename in seen:
logging.warning('OWNER dependence loop found for %s' % filename)
break
return ''
def _FindComponentRoot(start_path, cache, opts): def _FindComponentRoot(start_path, cache, opts):
...@@ -569,7 +575,7 @@ def _FindComponentRoot(start_path, cache, opts): ...@@ -569,7 +575,7 @@ def _FindComponentRoot(start_path, cache, opts):
Args: Args:
start_path: Path of directory to start searching from. Must be relative to start_path: Path of directory to start searching from. Must be relative to
SRC_ROOT. |opts.src_root|.
cache: Dict of OWNERS paths. Used instead of filesystem if paths are present cache: Dict of OWNERS paths. Used instead of filesystem if paths are present
in the dict. in the dict.
opts: Instance of ContainerArchiveOptions. opts: Instance of ContainerArchiveOptions.
...@@ -580,14 +586,15 @@ def _FindComponentRoot(start_path, cache, opts): ...@@ -580,14 +586,15 @@ def _FindComponentRoot(start_path, cache, opts):
prev_dir = None prev_dir = None
test_dir = start_path test_dir = start_path
# This loop will traverse the directory structure upwards until reaching # This loop will traverse the directory structure upwards until reaching
# SRC_ROOT, where test_dir and prev_dir will both equal an empty string. # |opts.src_root|, where |test_dir| and |prev_dir| will both equal an empty
# string.
while test_dir != prev_dir: while test_dir != prev_dir:
cached_component = cache.get(test_dir) cached_component = cache.get(test_dir)
if cached_component: if cached_component:
return cached_component return cached_component
elif cached_component is None: if cached_component is None: # Excludes ''.
owners_path = os.path.join(opts.src_root, test_dir, _OWNERS_FILENAME) owners_path = os.path.join(opts.src_root, test_dir, _OWNERS_FILENAME)
component = _ParseComponentFromOwners(owners_path) component = _ParseComponentFromOwners(owners_path, opts)
cache[test_dir] = component cache[test_dir] = component
if component: if component:
return component return component
...@@ -751,7 +758,7 @@ def CreateMetadata(map_path, elf_path, apk_path, minimal_apks_path, ...@@ -751,7 +758,7 @@ def CreateMetadata(map_path, elf_path, apk_path, minimal_apks_path,
shorten_path = os.path.basename shorten_path = os.path.basename
if tool_prefix: if tool_prefix:
relative_tool_prefix = path_util.ToSrcRootRelative(tool_prefix) relative_tool_prefix = path_util.ToToolsSrcRootRelative(tool_prefix)
metadata[models.METADATA_TOOL_PREFIX] = relative_tool_prefix metadata[models.METADATA_TOOL_PREFIX] = relative_tool_prefix
if linker_name: if linker_name:
...@@ -1871,7 +1878,8 @@ def _DeduceMainPaths(args, on_config_error): ...@@ -1871,7 +1878,8 @@ def _DeduceMainPaths(args, on_config_error):
apk_path: Path to .apk file that can be opened for processing, but whose apk_path: Path to .apk file that can be opened for processing, but whose
filename is unimportant (e.g., can be a temp file). filename is unimportant (e.g., can be a temp file).
""" """
opts = ContainerArchiveOptions(sub_args) output_directory = output_directory_finder.Tentative()
opts = ContainerArchiveOptions(sub_args, output_directory=output_directory)
if apk_prefix: if apk_prefix:
# Allow either .minimal.apks or just .apks. # Allow either .minimal.apks or just .apks.
apk_prefix = apk_prefix.replace('.minimal.apks', '.aab') apk_prefix = apk_prefix.replace('.minimal.apks', '.aab')
...@@ -1882,8 +1890,8 @@ def _DeduceMainPaths(args, on_config_error): ...@@ -1882,8 +1890,8 @@ def _DeduceMainPaths(args, on_config_error):
tool_prefix = None tool_prefix = None
if opts.analyze_native: if opts.analyze_native:
elf_path, map_path, apk_so_path = _DeduceNativeInfo( elf_path, map_path, apk_so_path = _DeduceNativeInfo(
output_directory_finder.Tentative(), apk_path, sub_args.elf_file, output_directory, apk_path, sub_args.elf_file, sub_args.map_file,
sub_args.map_file, on_config_error) on_config_error)
if map_path: if map_path:
linker_name = _DetectLinkerName(map_path) linker_name = _DetectLinkerName(map_path)
logging.info('Linker name: %s' % linker_name) logging.info('Linker name: %s' % linker_name)
......
...@@ -245,7 +245,7 @@ class _Session(object): ...@@ -245,7 +245,7 @@ class _Session(object):
tool_prefix = self._tool_prefix_finder.Tentative() tool_prefix = self._tool_prefix_finder.Tentative()
orig_tool_prefix = size_info.metadata.get(models.METADATA_TOOL_PREFIX) orig_tool_prefix = size_info.metadata.get(models.METADATA_TOOL_PREFIX)
if orig_tool_prefix: if orig_tool_prefix:
orig_tool_prefix = path_util.FromSrcRootRelative(orig_tool_prefix) orig_tool_prefix = path_util.FromToolsSrcRootRelative(orig_tool_prefix)
if os.path.exists(path_util.GetObjDumpPath(orig_tool_prefix)): if os.path.exists(path_util.GetObjDumpPath(orig_tool_prefix)):
tool_prefix = orig_tool_prefix tool_prefix = orig_tool_prefix
......
...@@ -293,8 +293,9 @@ def Run(args, on_config_error): ...@@ -293,8 +293,9 @@ def Run(args, on_config_error):
' https://chrome-supersize.firebaseapp.com/viewer.html' ' https://chrome-supersize.firebaseapp.com/viewer.html'
'?load_url=oneoffs/{2}.ndjson', '?load_url=oneoffs/{2}.ndjson',
] ]
supersize_path = os.path.relpath(os.path.join( supersize_path = os.path.relpath(
path_util.SRC_ROOT, 'tools', 'binary_size', 'supersize')) os.path.join(path_util.TOOLS_SRC_ROOT, 'tools', 'binary_size',
'supersize'))
# Use a random UUID as the filename so user can copy-and-paste command # Use a random UUID as the filename so user can copy-and-paste command
# directly without a name collision. # directly without a name collision.
upload_id = uuid.uuid4() upload_id = uuid.uuid4()
......
...@@ -12,9 +12,13 @@ import os ...@@ -12,9 +12,13 @@ import os
_STATUS_DETECTED = 1 _STATUS_DETECTED = 1
_STATUS_VERIFIED = 2 _STATUS_VERIFIED = 2
SRC_ROOT = os.environ.get('CHECKOUT_SOURCE_ROOT', # Src root of SuperSize being run. Not to be confused with src root of the input
os.path.abspath(os.path.join(os.path.dirname(__file__), # binary being archived.
os.pardir, os.pardir, os.pardir))) TOOLS_SRC_ROOT = os.environ.get(
'CHECKOUT_SOURCE_ROOT',
os.path.abspath(
os.path.join(
os.path.dirname(__file__), os.pardir, os.pardir, os.pardir)))
_SAMPLE_TOOL_SUFFIX = 'readelf' _SAMPLE_TOOL_SUFFIX = 'readelf'
...@@ -93,7 +97,7 @@ class ToolPrefixFinder(_PathFinder): ...@@ -93,7 +97,7 @@ class ToolPrefixFinder(_PathFinder):
if output_directory: if output_directory:
ret = None ret = None
if self.IsLld(): if self.IsLld():
ret = os.path.join(SRC_ROOT, 'third_party', 'llvm-build', ret = os.path.join(TOOLS_SRC_ROOT, 'third_party', 'llvm-build',
'Release+Asserts', 'bin', 'llvm-') 'Release+Asserts', 'bin', 'llvm-')
else: else:
# Auto-detect from build_vars.txt # Auto-detect from build_vars.txt
...@@ -137,16 +141,42 @@ def _LoadBuildVars(output_directory): ...@@ -137,16 +141,42 @@ def _LoadBuildVars(output_directory):
return dict() return dict()
def FromSrcRootRelative(path): def GetSrcRootFromOutputDirectory(output_directory):
ret = os.path.relpath(os.path.join(SRC_ROOT, path)) """Returns the source root directory from output directory.
Typical case: '/.../chromium/src/out/Release' -> '/.../chromium/src/'.
Heuristic: Look for .gn in the current and successive parent directories.
Args:
output_directory: Starting point of search. This may be relative to CWD.
Returns:
Source root directory.
"""
if output_directory:
cur_dir = os.path.abspath(output_directory)
while True:
gn_path = os.path.join(cur_dir, '.gn')
if os.path.isfile(gn_path):
return cur_dir
cur_dir, prev_dir = os.path.dirname(cur_dir), cur_dir
if cur_dir == prev_dir: # Reached root.
break
logging.warning('Cannot deduce src root from output directory. Falling back '
'to tools src root.')
return TOOLS_SRC_ROOT
def FromToolsSrcRootRelative(path):
ret = os.path.relpath(os.path.join(TOOLS_SRC_ROOT, path))
# Need to maintain a trailing /. # Need to maintain a trailing /.
if path.endswith(os.path.sep): if path.endswith(os.path.sep):
ret += os.path.sep ret += os.path.sep
return ret return ret
def ToSrcRootRelative(path): def ToToolsSrcRootRelative(path):
ret = os.path.relpath(path, SRC_ROOT) ret = os.path.relpath(path, TOOLS_SRC_ROOT)
# Need to maintain a trailing /. # Need to maintain a trailing /.
if path.endswith(os.path.sep): if path.endswith(os.path.sep):
ret += os.path.sep ret += os.path.sep
...@@ -164,14 +194,14 @@ def GetNmPath(tool_prefix): ...@@ -164,14 +194,14 @@ def GetNmPath(tool_prefix):
def GetApkAnalyzerPath(): def GetApkAnalyzerPath():
default_path = FromSrcRootRelative( default_path = FromToolsSrcRootRelative(
os.path.join('third_party', 'android_sdk', 'public', 'cmdline-tools', os.path.join('third_party', 'android_sdk', 'public', 'cmdline-tools',
'latest', 'bin', 'apkanalyzer')) 'latest', 'bin', 'apkanalyzer'))
return os.environ.get('APK_ANALYZER', default_path) return os.environ.get('APK_ANALYZER', default_path)
def GetJavaHome(): def GetJavaHome():
return FromSrcRootRelative(os.path.join('third_party', 'jdk', 'current')) return FromToolsSrcRootRelative(os.path.join('third_party', 'jdk', 'current'))
def GetObjDumpPath(tool_prefix): def GetObjDumpPath(tool_prefix):
......
# COMPONENT: UI>Browser
\ No newline at end of file
file://tools/binary_size/libsupersize/testdata/OWNERS file://OWNERS
# COMPONENT: Blink>Internal # COMPONENT: Blink>Internal
\ No newline at end of file
3p@email.com 3p@email.com
# COMPONENT: Internal>Android # COMPONENT: Internal>Android
\ No newline at end of file
file://tools/binary_size/libsupersize/testdata/TEST_OWNERS file://third_party/blink/OWNERS
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment