| OLD | NEW |
| (Empty) |
| 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 | |
| 3 # found in the LICENSE file. | |
| 4 """Extract source file information from .ninja files.""" | |
| 5 | |
| 6 import logging | |
| 7 import os | |
| 8 import re | |
| 9 | |
| 10 | |
| 11 # E.g.: | |
| 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 | | |
| 14 _REGEX = re.compile(r'build ([^:]+?\.[ao]): \w+ (.*?)(?: \||\n|$)') | |
| 15 | |
| 16 | |
| 17 class SourceFileMapper(object): | |
| 18 def __init__(self, output_directory): | |
| 19 self._output_directory = output_directory | |
| 20 self._ninja_files_to_parse = ['build.ninja'] | |
| 21 self._seen_ninja_files = set(('build.ninja',)) | |
| 22 self._dep_map = {} | |
| 23 | |
| 24 def _ParseNinja(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(')'): | |
| 72 return self._Lookup(path) | |
| 73 | |
| 74 # foo/bar.a(baz.o) | |
| 75 start_idx = path.index('(') | |
| 76 lib_name = path[:start_idx] | |
| 77 obj_name = path[start_idx + 1:-1] | |
| 78 by_basename = self._Lookup(lib_name) | |
| 79 if not by_basename: | |
| 80 return None | |
| 81 obj_path = by_basename.get(obj_name) | |
| 82 if not obj_path: | |
| 83 # Found the library, but it doesn't list the .o file. | |
| 84 logging.warning('no obj basename for %s', path) | |
| 85 return None | |
| 86 return self._Lookup(obj_path) | |
| 87 | |
| 88 def GetParsedFileCount(self): | |
| 89 return len(self._seen_ninja_files) | |
| OLD | NEW |