5"""Clang based C++ code indexer which produces xref.json."""
6from __future__
import annotations
17from contextlib
import contextmanager
18from dataclasses
import dataclass, field
19from typing
import Dict, Optional, Set
21from marshmallow_dataclass
import class_schema
22from progress.bar
import ShadyBar
25_clang_include_dir =
None
26if platform.system() ==
'Darwin':
27 _path_to_llvm = subprocess.check_output([
'brew',
'--prefix',
'llvm'],
28 encoding=
'utf-8').strip()
29 _clang_include_dir = glob.glob(f
'{_path_to_llvm}/lib/clang/**/include')[0]
30 _libclang_path = f
'{_path_to_llvm}/lib/libclang.dylib'
32 f
'{_path_to_llvm}/lib/python{sys.version_info.major}.{sys.version_info.minor}/site-packages'
36from clang.cindex
import Config, CompilationDatabase, Cursor, CursorKind, Index, SourceLocation, TranslationUnit
38if _libclang_path
is not None:
39 Config.set_library_file(_libclang_path)
41_DART_CONFIGURATION =
'Release' + (
42 'ARM64' if platform.uname().machine ==
'arm64' else 'X64')
43_DART_BUILD_DIR = (
'xcodebuild' if platform.system() ==
'Darwin' else
44 'out') +
'/' + _DART_CONFIGURATION
58 """Create compilation database for the default configuration.
60 Extacts compilation database (compile_commands.json) for the default
61 build configuration and filters it down to commands taht builds just
62 the core VM pieces in the host configuration.
64 logging.info(
'Extracting compilation commands from build files for %s',
67 commands = json.loads(
68 subprocess.check_output([
'ninja',
'-C',
'.',
'-t',
'compdb',
71 r'libdart(_vm|_compiler)?_precompiler_host_targeting_host\.')
72 with open(
'compile_commands.json',
'w', encoding=
'utf-8')
as outfile:
74 cmd
for cmd
in commands
75 if pattern.search(cmd[
'command'])
is not None
80 return subprocess.check_output([
'git',
'merge-base',
'main',
'HEAD'],
87 members: Dict[str, str] = field(default_factory=dict)
92 """Symbol location referring to a line in a specific file."""
99 """Index of C++ symbols extracted from source."""
101 files: list[str] = field(default_factory=list)
102 classes: Dict[str, _ClassInfo] = field(default_factory=dict)
103 functions: Dict[str, str] = field(default_factory=dict)
106 """Resolve the location of the given reference."""
116 return self.
classes[ref].location
118 (class_name, function_name) = ref.rsplit(
'::', 1)
120 return self.
classes[class_name].members.get(function_name)
124 (file_idx, line_idx) = loc.split(
':', 1)
129 symbols_index: SymbolsIndex
131 processed_files: Set[str]
132 classes_by_usr: Dict[str, _ClassInfo]
134 name_stack: list[str]
135 info_stack: list[_ClassInfo]
136 files_seen_in_unit: Set[str]
149 def index(self, unit: TranslationUnit):
150 """Index the given translation unit and append new symbols to index."""
161 if cursor.location.file
is not None:
162 name = cursor.location.file.name
167 if kind == CursorKind.CLASS_DECL:
168 if not cursor.is_definition():
170 usr = cursor.get_usr()
179 elif kind == CursorKind.NAMESPACE:
181 elif kind == CursorKind.FUNCTION_DECL
and cursor.is_definition():
182 namespace_prefix =
""
183 if cursor.semantic_parent.kind == CursorKind.NAMESPACE:
186 function_name = namespace_prefix + cursor.spelling
190 elif kind == CursorKind.CXX_METHOD
and cursor.is_definition():
191 parent = cursor.semantic_parent
192 if parent.kind == CursorKind.CLASS_DECL:
194 if class_info_or_none
is None:
196 class_info_or_none.members[
199 elif kind == CursorKind.VAR_DECL
and cursor.is_definition():
200 parent = cursor.semantic_parent
201 if parent.kind == CursorKind.CLASS_DECL:
203 if class_info_or_none
is None:
205 class_info_or_none.members[
208 for child
in cursor.get_children():
211 if kind == CursorKind.NAMESPACE:
213 elif kind == CursorKind.CLASS_DECL:
218 file_name = loc.file.name
220 return f
'{self._get_file_index(file_name)}:{lineno}'
228 os.path.relpath(os.path.abspath(file_name),
229 os.path.abspath(
'../..')))
238 index = Index.create()
239 compdb = CompilationDatabase.fromDirectory(
'.')
241 commands = list(compdb.getAllCompileCommands())
242 with ShadyBar(
'Indexing',
244 suffix=
'%(percent)d%% eta %(eta_td)s')
as progress_bar:
245 for command
in commands:
247 arg
for arg
in command.arguments
248 if arg.startswith(
'-I')
or arg.startswith(
'-W')
or
249 arg.startswith(
'-D')
or arg.startswith(
'-i')
or
250 arg.startswith(
'sdk/')
or arg.startswith(
'-std')
252 '-Wno-macro-redefined',
'-Wno-unused-const-variable',
253 '-Wno-unused-function',
'-Wno-unused-variable'
256 if _clang_include_dir
is not None:
257 args.append(f
'-I{_clang_include_dir}')
259 unit = index.parse(command.filename, args=args)
260 for diag
in unit.diagnostics:
266 return indexer.symbols_index
269_SymbolsIndexSchema = class_schema(SymbolsIndex)()
272def load_index(filename: str) -> SymbolsIndex:
273 """Load symbols index from the given file.
275 If index is out of date or missing it will be generated.
279 if os.path.exists(filename):
280 with open(filename,
'r', encoding=
'utf-8')
as json_file:
281 index = _SymbolsIndexSchema.loads(json_file.read())
283 logging.info(
'Loaded symbols index from %s', filename)
286 '%s is generated for commit %s while current commit is %s',
290 with open(filename,
'w', encoding=
'utf-8')
as json_file:
291 json_file.write(_SymbolsIndexSchema.dumps(index))
293 'Successfully indexed C++ source and written symbols index into %s',
static std::function< void(void)> pop(std::deque< std::function< void(void)> > *list)
Type::kYUV Type::kRGBA() int(0.7 *637)
Optional[Location] try_resolve(self, str ref)
Location _location_from_string(self, str loc)
Optional[str] _try_resolve_impl(self, str ref)
_recurse(self, Cursor cursor)
SymbolsIndex symbols_index
_format_location(self, SourceLocation loc)
_get_file_index(self, str file_name)
index(self, TranslationUnit unit)
static void append(char **dst, size_t *count, const char *src, size_t n)
_change_working_directory_to(path)
_get_current_commit_hash()
SymbolsIndex _index_source()
_create_compilation_database()