| #!/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()) |