1#!/usr/bin/env python3
2
3"""List all source file of a installed module."""
4
5import argparse
6import itertools
7import posixpath
8import re
9
10try:
11    import cPickle as pickle  # Python 2
12except ImportError:
13    import pickle  # Python 3
14
15import ninja
16
17
18def _parse_args():
19    """Parse the command line arguments."""
20
21    parser = argparse.ArgumentParser()
22
23    # Ninja input file options
24    parser.add_argument('input_file', help='input ninja file')
25    parser.add_argument('--ninja-deps', help='.ninja_deps file')
26    parser.add_argument('--cwd', help='working directory for ninja')
27    parser.add_argument('--encoding', default='utf-8',
28                        help='ninja file encoding')
29
30    # Options
31    parser.add_argument(
32            'installed_filter', nargs='+',
33            help='path filter for installed files (w.r.t. device root)')
34    parser.add_argument(
35            '--out-dir', default='out', help='path to output directory')
36
37    return parser.parse_args()
38
39
40def collect_source_files(graph, start, out_dir_pattern, out_host_dir_pattern):
41    """Collect the transitive dependencies of a target."""
42
43    source_files = []
44
45    # Extract the file name of the target file.  We need this file name to
46    # allow the strip/copy build rules while leaving other shared libraries
47    # alone.
48    start_basename = posixpath.basename(start)
49
50    # Collect all source files
51    visited = {start}
52    stack = [start]
53    while stack:
54        cur = stack.pop()
55
56        if not out_dir_pattern.match(cur):
57            source_files.append(cur)
58
59        build = graph.get(cur)
60        if build:
61            for dep in itertools.chain(build.explicit_ins, build.implicit_ins,
62                                       build.depfile_implicit_ins):
63                # Skip the binaries for build process
64                if dep.startswith('prebuilts/'):
65                    continue
66                if out_host_dir_pattern.match(dep):
67                    continue
68
69                # Skip the shared libraries
70                if dep.endswith('.toc'):
71                    continue
72                if dep.endswith('.so'):
73                    if posixpath.basename(dep) != start_basename:
74                        continue
75
76                if dep not in visited:
77                    visited.add(dep)
78                    stack.append(dep)
79
80    return sorted(source_files)
81
82
83def main():
84    args = _parse_args()
85
86    out_dir = posixpath.normpath(args.out_dir)
87    out_dir_pattern = re.compile(re.escape(out_dir) + '/')
88    out_host_dir_pattern = re.compile(re.escape(out_dir) + '/host/')
89    out_product_dir = out_dir + '/target/product/[^/]+'
90
91    def _normalize_path(path):
92        if path.startswith(out_dir + '/target'):
93            return path
94        return posixpath.join(out_product_dir, path)
95
96    installed_filter = [_normalize_path(path) for path in args.installed_filter]
97    installed_filter = re.compile(
98        '|'.join('(?:' + p + ')' for p in installed_filter))
99
100    manifest = ninja.load_manifest_from_args(args)
101
102    # Build lookup map
103    graph = {}
104    for build in manifest.builds:
105        for path in build.explicit_outs:
106            graph[path] = build
107        for path in build.implicit_outs:
108            graph[path] = build
109
110    # Collect all matching outputs
111    matched_files = [path for path in graph if installed_filter.match(path)]
112    matched_files.sort()
113
114    for path in matched_files:
115        source_files = collect_source_files(
116            graph, path, out_dir_pattern, out_host_dir_pattern)
117        print(path)
118        for dep in source_files:
119            print('\t' + dep)
120        print()
121
122
123if __name__ == '__main__':
124    main()
125