| 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 _REGEX = re.compile(r'build ([^:]+?\.[ao]): \w+ (.*?)(?: \||\n|$)') |
| 15 | 15 |
| 16 | 16 |
| 17 class SourceFileMapper(object): | 17 class _SourceMapper(object): |
| 18 def __init__(self, output_directory): | 18 def __init__(self, dep_map, parsed_file_count): |
| 19 self._output_directory = output_directory | 19 self._dep_map = dep_map |
| 20 self._ninja_files_to_parse = ['build.ninja'] | 20 self.parsed_file_count = parsed_file_count |
| 21 self._seen_ninja_files = set(('build.ninja',)) | 21 self._unmatched_paths = set() |
| 22 self._dep_map = {} | |
| 23 | 22 |
| 24 def _ParseNinja(self, path): | 23 def _FindSourceForPathInternal(self, path): |
| 25 with open(os.path.join(self._output_directory, path)) as obj: | |
| 26 self._ParseNinjaLines(obj) | |
| 27 | |
| 28 def _ParseNinjaLines(self, lines): | |
| 29 dep_map = self._dep_map | |
| 30 sub_ninjas = [] | |
| 31 for line in lines: | |
| 32 if line.startswith('subninja '): | |
| 33 subpath = line[9:-1] | |
| 34 assert subpath not in self._seen_ninja_files, ( | |
| 35 'Double include of ' + subpath) | |
| 36 self._seen_ninja_files.add(subpath) | |
| 37 sub_ninjas.append(subpath) | |
| 38 continue | |
| 39 m = _REGEX.match(line) | |
| 40 if m: | |
| 41 output, srcs = m.groups() | |
| 42 output = output.replace('\\ ', ' ') | |
| 43 assert output not in dep_map, 'Duplicate output: ' + output | |
| 44 if output[-1] == 'o': | |
| 45 dep_map[output] = srcs.replace('\\ ', ' ') | |
| 46 else: | |
| 47 srcs = srcs.replace('\\ ', '\b') | |
| 48 obj_paths = (s.replace('\b', ' ') for s in srcs.split(' ')) | |
| 49 dep_map[output] = {os.path.basename(p): p for p in obj_paths} | |
| 50 | |
| 51 # Add reversed so that the first on encoundered is at the top of the stack. | |
| 52 self._ninja_files_to_parse.extend(reversed(sub_ninjas)) | |
| 53 | |
| 54 def _Lookup(self, path): | |
| 55 """Looks for |path| within self._dep_map. | |
| 56 | |
| 57 If not found, continues to parse subninjas until it is found or there are no | |
| 58 more subninjas. | |
| 59 """ | |
| 60 ret = self._dep_map.get(path) | |
| 61 while not ret and self._ninja_files_to_parse: | |
| 62 self._ParseNinja(self._ninja_files_to_parse.pop()) | |
| 63 ret = self._dep_map.get(path) | |
| 64 return ret | |
| 65 | |
| 66 def FindSourceForPath(self, path): | |
| 67 """Returns the source path for the given object path (or None if not found). | |
| 68 | |
| 69 Paths for objects within archives should be in the format: foo/bar.a(baz.o) | |
| 70 """ | |
| 71 if not path.endswith(')'): | 24 if not path.endswith(')'): |
| 72 return self._Lookup(path) | 25 return self._dep_map.get(path) |
| 73 | 26 |
| 74 # foo/bar.a(baz.o) | 27 # foo/bar.a(baz.o) |
| 75 start_idx = path.index('(') | 28 start_idx = path.index('(') |
| 76 lib_name = path[:start_idx] | 29 lib_name = path[:start_idx] |
| 77 obj_name = path[start_idx + 1:-1] | 30 obj_name = path[start_idx + 1:-1] |
| 78 by_basename = self._Lookup(lib_name) | 31 by_basename = self._dep_map.get(lib_name) |
| 79 if not by_basename: | 32 if not by_basename: |
| 80 return None | 33 return None |
| 81 obj_path = by_basename.get(obj_name) | 34 obj_path = by_basename.get(obj_name) |
| 82 if not obj_path: | 35 if not obj_path: |
| 83 # Found the library, but it doesn't list the .o file. | 36 # Found the library, but it doesn't list the .o file. |
| 84 logging.warning('no obj basename for %s', path) | 37 logging.warning('no obj basename for %s', path) |
| 85 return None | 38 return None |
| 86 return self._Lookup(obj_path) | 39 return self._dep_map.get(obj_path) |
| 87 | 40 |
| 88 def GetParsedFileCount(self): | 41 def FindSourceForPath(self, path): |
| 89 return len(self._seen_ninja_files) | 42 """Returns the source path for the given object path (or None if not found). |
| 43 |
| 44 Paths for objects within archives should be in the format: foo/bar.a(baz.o) |
| 45 """ |
| 46 ret = self._FindSourceForPathInternal(path) |
| 47 if not ret and path not in self._unmatched_paths: |
| 48 if self.unmatched_paths_count < 10: |
| 49 logging.warning('Could not find source path for %s', path) |
| 50 self._unmatched_paths.add(path) |
| 51 return ret |
| 52 |
| 53 @property |
| 54 def unmatched_paths_count(self): |
| 55 return len(self._unmatched_paths) |
| 56 |
| 57 |
| 58 def _ParseOneFile(lines, dep_map): |
| 59 sub_ninjas = [] |
| 60 for line in lines: |
| 61 if line.startswith('subninja '): |
| 62 sub_ninjas.append(line[9:-1]) |
| 63 continue |
| 64 m = _REGEX.match(line) |
| 65 if m: |
| 66 output, srcs = m.groups() |
| 67 output = output.replace('\\ ', ' ') |
| 68 assert output not in dep_map, 'Duplicate output: ' + output |
| 69 if output[-1] == 'o': |
| 70 dep_map[output] = srcs.replace('\\ ', ' ') |
| 71 else: |
| 72 srcs = srcs.replace('\\ ', '\b') |
| 73 obj_paths = (s.replace('\b', ' ') for s in srcs.split(' ')) |
| 74 dep_map[output] = {os.path.basename(p): p for p in obj_paths} |
| 75 return sub_ninjas |
| 76 |
| 77 |
| 78 def Parse(output_directory): |
| 79 to_parse = ['build.ninja'] |
| 80 seen_paths = set(to_parse) |
| 81 dep_map = {} |
| 82 while to_parse: |
| 83 path = os.path.join(output_directory, to_parse.pop()) |
| 84 with open(path) as obj: |
| 85 sub_ninjas = _ParseOneFile(obj, dep_map) |
| 86 for subpath in sub_ninjas: |
| 87 assert subpath not in seen_paths, 'Double include of ' + subpath |
| 88 seen_paths.add(subpath) |
| 89 to_parse.extend(sub_ninjas) |
| 90 |
| 91 return _SourceMapper(dep_map, len(seen_paths)) |
| OLD | NEW |