blob: 2e7e9e4f9848940481ceb34617ac1900313ac57d [file] [log] [blame]
#!/usr/bin/env vpython3
# Copyright 2019 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
'''A tool that helps ease libunwindstack updates by managing the
platform/system/core source files that are actually needed for libunwindstack to
compile.
To use this script, the //third_party/libunwindstack:libunwindstack must
be compilable. In practice this means that the tool must be run within a
Chromium checkout and that the libunwindstack target must be a transitive
dependency of some target normally compilable within the checkout
(e.g. chrome_public_apk).'''
import argparse
import os
import re
import subprocess
import sys
_LIBUNWINDSTACK_ROOT = os.path.abspath(
os.path.join(os.path.dirname(__file__), os.pardir))
_LIBUNWINDSTACK_SRC_ROOT = os.path.join(_LIBUNWINDSTACK_ROOT, 'src')
def GenerateDepFiles(ninja_build_dir):
'''Generates the dependency files that tell us which sources are used to
compile libunwindstack.'''
# We need to clean the build directory for two reasons:
#
# 1) This eliminates the risk that there are stale depfiles from previous
# times that the user built with -d keepdepfiles (including through this
# script).
# 2) This ensures that the compilation actually happens, which is required to
# create the depfiles.
#
# Ideally, we'd use check_call to ensure that ninja -t clean
# succeeds. However, ninja's clean tool exits with a return code under normal
# circumstances because it can't clean up non-empty directories. Because of
# this, we just ignore cleaning errors by using call() instead of
# check_call().
subprocess.call(['ninja', '-C', ninja_build_dir, '-t', 'clean'],
stdout=sys.stderr,
stderr=sys.stderr)
subprocess.check_call([
'ninja', '-C', ninja_build_dir,
'third_party/libunwindstack:libunwindstack', '-d', 'keepdepfile'
],
stdout=sys.stderr,
stderr=sys.stderr)
def ExtractRequiredSources(ninja_build_dir):
'''Assuming that the .o.d depfiles have already been generated by a build,
parses the depfiles and returns a list of the C++ files in libunwindstack/src/
that are needed to compile libunwindstack.'''
aggregate_deps_process = subprocess.Popen([
'find', '{}/obj/third_party/libunwindstack/'.format(ninja_build_dir),
'-name', '*.o.d'
],
stdout=subprocess.PIPE,
stderr=sys.stderr)
sources = subprocess.check_output(['xargs', 'cat'],
stdin=aggregate_deps_process.stdout,
stderr=sys.stderr).decode(
sys.stdout.encoding)
# Each of the .o.d files is actually in Makefile form, like:
#
# obj/third_party/libunwindstack/libunwindstack/ElfInterface.o: \
# ../../third_party/libunwindstack/src/libunwindstack/ElfInterface.cpp \
# ../../third_party/lzma_sdk/7zCrc.h \
# ../../third_party/lzma_sdk/7zTypes.h ../../third_party/lzma_sdk/Xz.h \
# ../../third_party/lzma_sdk/Sha256.h \
# ../../third_party/lzma_sdk/XzCrc64.h \
# ../../third_party/libunwindstack/src/libunwindstack/include/unwindstack/DwarfError.h \
# ../../third_party/libunwindstack/src/libunwindstack/include/unwindstack/DwarfSection.h \
# ...
#
# We're looking explicitly for the platform/system/core C++ source code
# dependencies, so we use a regexp to extract these and ignore other
# dependencies (e.g. src/third_party/lzma_sdk/) and .o target names.
rel_sources = re.findall(r'third_party/libunwindstack/(src/\S+)', sources)
return sorted(set(rel_sources))
def PrintGnFormattedSources(sources):
print('sources = [')
for source in sources:
print(' "{}",'.format(source))
print(']')
def PruneUnrequiredSources(required_sources):
required_abs_sources = [
os.path.join(_LIBUNWINDSTACK_ROOT, src) for src in required_sources
]
current_sources = []
for subdir, _, files in os.walk(_LIBUNWINDSTACK_SRC_ROOT):
for f in files:
current_sources.append(os.path.join(subdir, f))
sources_to_remove = sorted(set(current_sources) - set(required_abs_sources))
print('Removed the following sources:')
for source in sources_to_remove:
os.remove(source)
print('{}'.format(source))
def Main():
parser = argparse.ArgumentParser(description=__doc__)
subparsers = parser.add_subparsers(dest='subcommand')
print_parser = subparsers.add_parser(
'print', help='Print the list of required sources to STDOUT.')
prune_parser = subparsers.add_parser(
'prune', help='Prune unrequired sources from the src/ directory.')
parser.add_argument(
'ninja_build_dir',
help='Change to DIR before compiling (ninja build directory)')
args = parser.parse_args()
# Using the abspath here ensures there's no trailing slash.
ninja_build_dir = os.path.abspath(args.ninja_build_dir)
if not os.path.exists(ninja_build_dir):
raise IOError(
'Ninja build directory {} does not exist.'.format(ninja_build_dir))
GenerateDepFiles(ninja_build_dir)
required_sources = ExtractRequiredSources(ninja_build_dir)
if args.subcommand == 'print':
PrintGnFormattedSources(required_sources)
elif args.subcommand == 'prune':
PruneUnrequiredSources(required_sources)
return 0
if __name__ == '__main__':
sys.exit(Main())