diff --git a/tools/ninja_dependency_analysis/Android.bp b/tools/ninja_dependency_analysis/Android.bp new file mode 100644 index 000000000..87eecc63d --- /dev/null +++ b/tools/ninja_dependency_analysis/Android.bp @@ -0,0 +1,15 @@ +python_binary_host { + name: "collect_ninja_inputs", + srcs: [ + "collect_ninja_inputs.py", + ], + version: { + py2: { + enabled: false, + }, + py3: { + enabled: true, + embedded_launcher: true, + }, + } +} \ No newline at end of file diff --git a/tools/ninja_dependency_analysis/OWNERS b/tools/ninja_dependency_analysis/OWNERS new file mode 100644 index 000000000..84993340a --- /dev/null +++ b/tools/ninja_dependency_analysis/OWNERS @@ -0,0 +1,4 @@ +jeongik@google.com +justinyun@google.com +kiyoungkim@google.com +inseob@google.com \ No newline at end of file diff --git a/tools/ninja_dependency_analysis/README.md b/tools/ninja_dependency_analysis/README.md new file mode 100644 index 000000000..8f17df048 --- /dev/null +++ b/tools/ninja_dependency_analysis/README.md @@ -0,0 +1,22 @@ + +`./development/tools/ninja_dependency_analysis/collect_inputs.py -n -f -t -e -r or -m ` + +For example +`./development/tools/ninja_dependency_analysis/collect_inputs.py -n prebuilts/build-tools/linux-x86/bin/ninja -f out/combined-aosp_cf_x86_64_phone.ninja -t vendorimage -e development/tools/ninja_dependency_analysis/exempted_files -r .repo/project.list` + +Output: +``` +{ + "inputs": [ + "foo/my.java", + "foo/my2.java", + "bar/my.cpp" + ], + "project_count": { + "foo": 2, + "bar": 1 + }, + "total_project_count": 2, + "total_input_count": 3 +} +``` \ No newline at end of file diff --git a/tools/ninja_dependency_analysis/collect_ninja_inputs.py b/tools/ninja_dependency_analysis/collect_ninja_inputs.py new file mode 100755 index 000000000..8f626df43 --- /dev/null +++ b/tools/ninja_dependency_analysis/collect_ninja_inputs.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2022 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the 'License'); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an 'AS IS' BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import argparse +import json +import os +import pathlib +import subprocess +import sys +import xml.etree.ElementTree as ET +from collections import OrderedDict +from operator import itemgetter + + +def build_cmd(ninja_binary, ninja_file, target, exempted_file_list): + cmd = [ninja_binary, '-f', ninja_file, '-t', 'inputs'] + if exempted_file_list and exempted_file_list.exists(): + with open(exempted_file_list) as fin: + for l in map(str.strip, fin.readlines()): + if l: + cmd.extend(['-e', l]) + cmd.append(target) + + return cmd + + +def count_project(projects, input_files): + project_count = dict() + for p in projects: + file_count = sum(f.startswith(p + os.path.sep) for f in input_files) + if file_count > 0: + project_count[p] = file_count + + return dict(sorted(project_count.items(), key=itemgetter(1), reverse=True)) + + +parser = argparse.ArgumentParser() + +parser.add_argument('-n', '--ninja_binary', type=pathlib.Path, required=True) +parser.add_argument('-f', '--ninja_file', type=pathlib.Path, required=True) +parser.add_argument('-t', '--target', type=str, required=True) +parser.add_argument('-e', '--exempted_file_list', type=pathlib.Path) +group = parser.add_mutually_exclusive_group() +group.add_argument('-r', '--repo_project_list', type=pathlib.Path) +group.add_argument('-m', '--repo_manifest', type=pathlib.Path) +args = parser.parse_args() + +input_files = sorted( + subprocess.check_output( + build_cmd(args.ninja_binary, args.ninja_file, args.target, + args.exempted_file_list), text=True).strip().split('\n')) + +result = dict() +result['input_files'] = input_files + +projects = None +if args.repo_project_list and args.repo_project_list.exists(): + with open(args.repo_project_list) as fin: + projects = list(map(str.strip, fin.readlines())) +elif args.repo_manifest and args.repo_manifest.exists(): + projects = [ + p.attrib['path'] + for p in ET.parse(args.repo_manifest).getroot().findall('project') + ] + +if projects: + project_to_count = count_project(projects, input_files) + result['project_count'] = project_to_count + result['total_project_count'] = len(project_to_count) + +result['total_input_count'] = len(input_files) +print(json.dumps(result, indent=2))