xref: /aosp_15_r20/development/tools/ninja_dependency_analysis/collect_ninja_inputs.py (revision 90c8c64db3049935a07c6143d7fd006e26f8ecca)
1#!/usr/bin/env python3
2
3# Copyright (C) 2022 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the 'License');
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#      http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an 'AS IS' BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17import argparse
18import json
19import os
20import pathlib
21import subprocess
22import sys
23import xml.etree.ElementTree as ET
24from collections import OrderedDict
25from operator import itemgetter
26from ninja_metrics_proto import ninja_metrics
27
28def build_cmd(ninja_binary, ninja_file, target, exempted_file_list):
29    cmd = [ninja_binary, '-f', ninja_file, '-t', 'inputs']
30    if exempted_file_list and exempted_file_list.exists():
31        with open(exempted_file_list) as fin:
32            for l in map(str.strip, fin.readlines()):
33                if l and not l.startswith('#'):
34                    cmd.extend(['-e', l])
35    cmd.append(target)
36
37    return cmd
38
39
40def count_project(projects, input_files):
41    project_count = dict()
42    for p in projects:
43        file_count = sum(f.startswith(p + os.path.sep) for f in input_files)
44        if file_count > 0:
45            project_count[p] = file_count
46
47    return dict(sorted(project_count.items(), key=itemgetter(1), reverse=True))
48
49
50parser = argparse.ArgumentParser()
51
52parser.add_argument('-n', '--ninja_binary', type=pathlib.Path, required=True)
53parser.add_argument('-f', '--ninja_file', type=pathlib.Path, required=True)
54parser.add_argument('-t', '--target', type=str, required=True)
55parser.add_argument('-e', '--exempted_file_list', type=pathlib.Path)
56parser.add_argument('-o', '--out', type=pathlib.Path)
57group = parser.add_mutually_exclusive_group()
58group.add_argument('-r', '--repo_project_list', type=pathlib.Path)
59group.add_argument('-m', '--repo_manifest', type=pathlib.Path)
60args = parser.parse_args()
61
62input_files = sorted(
63    subprocess.check_output(
64        build_cmd(args.ninja_binary, args.ninja_file, args.target,
65                  args.exempted_file_list), text=True).strip().split('\n'))
66
67result = dict()
68result['input_files'] = input_files
69
70projects = None
71if args.repo_project_list and args.repo_project_list.exists():
72    with open(args.repo_project_list) as fin:
73        projects = list(map(str.strip, fin.readlines()))
74elif args.repo_manifest and args.repo_manifest.exists():
75    projects = [
76        p.attrib['path']
77        for p in ET.parse(args.repo_manifest).getroot().findall('project')
78    ]
79
80if projects:
81    project_to_count = count_project(projects, input_files)
82    result['project_count'] = project_to_count
83    result['total_project_count'] = len(project_to_count)
84
85result['total_input_count'] = len(input_files)
86
87if args.out:
88    with open(os.path.join(args.out.parent, args.out.name + '.json'), 'w') as json_file:
89        json.dump(result, json_file, indent=2)
90    with open(os.path.join(args.out.parent, args.out.name + '.pb'), 'wb') as pb_file:
91        pb_file.write(ninja_metrics.generate_proto(result).SerializeToString())
92else:
93    print(json.dumps(result, indent=2))
94