| 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 |