OLD | NEW |
1 # Copyright 2017 The Chromium Authors. All rights reserved. | 1 # Copyright 2017 The Chromium Authors. All rights reserved. |
2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 """Extract source file information from .ninja files.""" | 4 """Extract source file information from .ninja files.""" |
5 | 5 |
6 import logging | 6 import logging |
7 import os | 7 import os |
8 import re | 8 import re |
9 | 9 |
10 | 10 |
11 # E.g.: | 11 # E.g.: |
12 # build obj/.../foo.o: cxx gen/.../foo.cc || obj/.../foo.inputdeps.stamp | 12 # build obj/.../foo.o: cxx gen/.../foo.cc || obj/.../foo.inputdeps.stamp |
13 # build obj/.../libfoo.a: alink obj/.../a.o obj/.../b.o | | 13 # build obj/.../libfoo.a: alink obj/.../a.o obj/.../b.o | |
14 _REGEX = re.compile(r'build ([^:]+?\.[ao]): \w+ (.*?)(?: \||\n|$)') | 14 # build ./libchrome.so ./lib.unstripped/libchrome.so: solink a.o b.o ... |
| 15 _REGEX = re.compile(r'build ([^:]+): \w+ (.*?)(?: \||\n|$)') |
15 | 16 |
16 | 17 |
17 class _SourceMapper(object): | 18 class _SourceMapper(object): |
18 def __init__(self, dep_map, parsed_file_count): | 19 def __init__(self, dep_map, parsed_file_count): |
19 self._dep_map = dep_map | 20 self._dep_map = dep_map |
20 self.parsed_file_count = parsed_file_count | 21 self.parsed_file_count = parsed_file_count |
21 self._unmatched_paths = set() | 22 self._unmatched_paths = set() |
22 | 23 |
23 def _FindSourceForPathInternal(self, path): | 24 def _FindSourceForPathInternal(self, path): |
24 if not path.endswith(')'): | 25 if not path.endswith(')'): |
(...skipping 23 matching lines...) Expand all Loading... |
48 if self.unmatched_paths_count < 10: | 49 if self.unmatched_paths_count < 10: |
49 logging.warning('Could not find source path for %s', path) | 50 logging.warning('Could not find source path for %s', path) |
50 self._unmatched_paths.add(path) | 51 self._unmatched_paths.add(path) |
51 return ret | 52 return ret |
52 | 53 |
53 @property | 54 @property |
54 def unmatched_paths_count(self): | 55 def unmatched_paths_count(self): |
55 return len(self._unmatched_paths) | 56 return len(self._unmatched_paths) |
56 | 57 |
57 | 58 |
58 def _ParseOneFile(lines, dep_map): | 59 def _ParseNinjaPathList(path_list): |
| 60 ret = path_list.replace('\\ ', '\b') |
| 61 return [s.replace('\b', ' ') for s in ret.split(' ')] |
| 62 |
| 63 |
| 64 def _ParseOneFile(lines, dep_map, elf_path): |
59 sub_ninjas = [] | 65 sub_ninjas = [] |
| 66 elf_inputs = None |
60 for line in lines: | 67 for line in lines: |
61 if line.startswith('subninja '): | 68 if line.startswith('subninja '): |
62 sub_ninjas.append(line[9:-1]) | 69 sub_ninjas.append(line[9:-1]) |
63 continue | 70 continue |
64 m = _REGEX.match(line) | 71 m = _REGEX.match(line) |
65 if m: | 72 if m: |
66 output, srcs = m.groups() | 73 outputs, srcs = m.groups() |
67 output = output.replace('\\ ', ' ') | 74 if len(outputs) > 2 and outputs[-2] == '.' and outputs[-1] in 'ao': |
68 assert output not in dep_map, 'Duplicate output: ' + output | 75 output = outputs.replace('\\ ', ' ') |
69 if output[-1] == 'o': | 76 assert output not in dep_map, 'Duplicate output: ' + output |
70 dep_map[output] = srcs.replace('\\ ', ' ') | 77 if output[-1] == 'o': |
71 else: | 78 dep_map[output] = srcs.replace('\\ ', ' ') |
72 srcs = srcs.replace('\\ ', '\b') | 79 else: |
73 obj_paths = (s.replace('\b', ' ') for s in srcs.split(' ')) | 80 obj_paths = _ParseNinjaPathList(srcs) |
74 dep_map[output] = {os.path.basename(p): p for p in obj_paths} | 81 dep_map[output] = {os.path.basename(p): p for p in obj_paths} |
75 return sub_ninjas | 82 elif elf_path and elf_path in outputs: |
| 83 properly_parsed = [ |
| 84 os.path.normpath(p) for p in _ParseNinjaPathList(outputs)] |
| 85 if elf_path in properly_parsed: |
| 86 elf_inputs = _ParseNinjaPathList(srcs) |
| 87 return sub_ninjas, elf_inputs |
76 | 88 |
77 | 89 |
78 def Parse(output_directory): | 90 def Parse(output_directory, elf_path): |
| 91 """Parses build.ninja and subninjas. |
| 92 |
| 93 Args: |
| 94 output_directory: Where to find the root build.ninja. |
| 95 elf_path: Path to elf file to find inputs for. |
| 96 |
| 97 Returns: A tuple of (source_mapper, elf_inputs). |
| 98 """ |
| 99 if elf_path: |
| 100 elf_path = os.path.relpath(elf_path, output_directory) |
79 to_parse = ['build.ninja'] | 101 to_parse = ['build.ninja'] |
80 seen_paths = set(to_parse) | 102 seen_paths = set(to_parse) |
81 dep_map = {} | 103 dep_map = {} |
| 104 elf_inputs = None |
82 while to_parse: | 105 while to_parse: |
83 path = os.path.join(output_directory, to_parse.pop()) | 106 path = os.path.join(output_directory, to_parse.pop()) |
84 with open(path) as obj: | 107 with open(path) as obj: |
85 sub_ninjas = _ParseOneFile(obj, dep_map) | 108 sub_ninjas, found_elf_inputs = _ParseOneFile(obj, dep_map, elf_path) |
| 109 if found_elf_inputs: |
| 110 assert not elf_inputs, 'Found multiple inputs for elf_path ' + elf_path |
| 111 elf_inputs = found_elf_inputs |
86 for subpath in sub_ninjas: | 112 for subpath in sub_ninjas: |
87 assert subpath not in seen_paths, 'Double include of ' + subpath | 113 assert subpath not in seen_paths, 'Double include of ' + subpath |
88 seen_paths.add(subpath) | 114 seen_paths.add(subpath) |
89 to_parse.extend(sub_ninjas) | 115 to_parse.extend(sub_ninjas) |
90 | 116 |
91 return _SourceMapper(dep_map, len(seen_paths)) | 117 return _SourceMapper(dep_map, len(seen_paths)), elf_inputs |
OLD | NEW |