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
48def _change_working_directory_to(path):
57def _create_compilation_database():
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',
66 with _change_working_directory_to(_DART_BUILD_DIR):
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
79def _get_current_commit_hash():
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."""
112 def _try_resolve_impl(self, ref: str) -> Optional[str]:
113 if ref
in self.functions:
114 return self.functions[ref]
115 if ref
in self.classes:
116 return self.classes[ref].location
118 (class_name, function_name) = ref.rsplit(
'::', 1)
119 if class_name
in self.classes:
120 return self.classes[class_name].members.get(function_name)
123 def _location_from_string(self, loc: str) -> Location:
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."""
157 def _recurse(self, cursor: Cursor):
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:
217 def _format_location(self, loc: SourceLocation):
218 file_name = loc.file.name
220 return f
'{self._get_file_index(file_name)}:{lineno}'
222 def _get_file_index(self, file_name: str):
228 os.path.relpath(os.path.abspath(file_name),
229 os.path.abspath(
'../..')))
233def _index_source() -> SymbolsIndex:
236 _create_compilation_database()
237 with _change_working_directory_to(_DART_BUILD_DIR):
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)()
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())
282 if _get_current_commit_hash() == index.commit:
283 logging.info(
'Loaded symbols index from %s', filename)
286 '%s is generated for commit %s while current commit is %s',
287 filename, index.commit, _get_current_commit_hash())
289 index = _index_source()
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)
Optional[Location] try_resolve(self, str ref)
Location _location_from_string(self, str loc)
Optional[str] _try_resolve_impl(self, str ref)
def _format_location(self, SourceLocation loc)
def index(self, TranslationUnit unit)
def _recurse(self, Cursor cursor)
static void append(char **dst, size_t *count, const char *src, size_t n)
SymbolsIndex load_index(str filename)
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not set
const myers::Point & get(const myers::Segment &)
def print(*args, **kwargs)
static SkString join(const CommandLineFlags::StringArray &)