Skip to content

Commit 215d95e

Browse files
coadometa-codesync[bot]
authored andcommitted
Add exclude_symbols to the cxx-api parser config (#56240)
Summary: Pull Request resolved: #56240 There are cases in which we want to strip particular symbols from the snapshot. This diff adds `exclude_symbols` to the parser config to enable definitions of symbol patterns that should be excluded from the snapshot. Changelog: [Internal] Reviewed By: cipolleschi Differential Revision: D98295409
1 parent 06b2bb3 commit 215d95e

5 files changed

Lines changed: 148 additions & 6 deletions

File tree

scripts/cxx-api/config.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ exclude_patterns:
44
- "*/test/*"
55
- "*/stubs/*"
66

7+
exclude_symbols:
8+
79
platforms:
810
ReactCommon:
911
codegen:

scripts/cxx-api/parser/__main__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ def build_snapshot_for_view(
8989
verbose: bool = True,
9090
input_filter: str = None,
9191
work_dir: str | None = None,
92+
exclude_symbols: list[str] | None = None,
9293
) -> str:
9394
if verbose:
9495
print(f"[{api_view}] Generating API view")
@@ -127,7 +128,9 @@ def build_snapshot_for_view(
127128
if verbose:
128129
print(f"[{api_view}] Building snapshot")
129130

130-
snapshot = build_snapshot(os.path.join(work_dir, "xml"))
131+
snapshot = build_snapshot(
132+
os.path.join(work_dir, "xml"), exclude_symbols=exclude_symbols
133+
)
131134
snapshot_string = snapshot.to_string()
132135

133136
output_file = os.path.join(output_dir, f"{api_view}Cxx.api")
@@ -175,6 +178,7 @@ def build_snapshots(
175178
verbose=verbose,
176179
input_filter=input_filter if config.input_filter else None,
177180
work_dir=work_dir,
181+
exclude_symbols=config.exclude_symbols,
178182
)
179183
futures[future] = config.snapshot_name
180184

scripts/cxx-api/parser/config.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ class ApiViewSnapshotConfig:
3838
definitions: dict[str, str | int]
3939
codegen_platform: str | None = None
4040
input_filter: bool = False
41+
exclude_symbols: list[str] = field(default_factory=list)
4142

4243

4344
def parse_config(
@@ -60,6 +61,7 @@ def parse_config(
6061
Flattened list of ApiViewSnapshotConfig objects for all views and variants
6162
"""
6263
global_exclude_patterns: list[str] = raw_config.get("exclude_patterns") or []
64+
global_exclude_symbols: list[str] = raw_config.get("exclude_symbols") or []
6365
platforms: dict = raw_config.get("platforms") or {}
6466

6567
snapshot_configs = []
@@ -103,6 +105,7 @@ def parse_config(
103105
definitions=base_definitions,
104106
codegen_platform=codegen_platform,
105107
input_filter=input_filter,
108+
exclude_symbols=global_exclude_symbols,
106109
)
107110
)
108111
else:
@@ -117,6 +120,7 @@ def parse_config(
117120
definitions=merged_definitions,
118121
codegen_platform=codegen_platform,
119122
input_filter=input_filter,
123+
exclude_symbols=global_exclude_symbols,
120124
)
121125
)
122126

scripts/cxx-api/parser/main.py

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,32 @@
2828
from .utils import has_scope_resolution_outside_angles, parse_qualified_path
2929

3030

31-
def _process_namespace_sections(snapshot, namespace_scope, compound_object):
31+
def _should_exclude_symbol(name: str, exclude_symbols: list[str]) -> bool:
32+
"""
33+
Check if a compound name should be excluded based on symbol patterns.
34+
35+
Each pattern in exclude_symbols is treated as a substring match against
36+
the compound's qualified name.
37+
"""
38+
return any(pattern in name for pattern in exclude_symbols)
39+
40+
41+
def _process_namespace_sections(
42+
snapshot, namespace_scope, compound_object, exclude_symbols: list[str]
43+
):
3244
"""
3345
Process all section definitions inside a namespace compound.
3446
"""
47+
compound_name = compound_object.compoundname
3548
for section_def in compound_object.sectiondef:
3649
if section_def.kind == "var":
3750
for variable_def in section_def.memberdef:
3851
# Skip out-of-class definitions (e.g. "Strct<T>::VALUE")
3952
if has_scope_resolution_outside_angles(variable_def.get_name()):
4053
continue
54+
qualified_name = f"{compound_name}::{variable_def.get_name()}"
55+
if _should_exclude_symbol(qualified_name, exclude_symbols):
56+
continue
4157
is_static = variable_def.static == "yes"
4258
namespace_scope.add_member(
4359
get_variable_member(variable_def, "public", is_static)
@@ -47,6 +63,9 @@ def _process_namespace_sections(snapshot, namespace_scope, compound_object):
4763
# Skip out-of-class definitions (e.g. "Strct<T>::convert")
4864
if has_scope_resolution_outside_angles(function_def.get_name()):
4965
continue
66+
qualified_name = f"{compound_name}::{function_def.get_name()}"
67+
if _should_exclude_symbol(qualified_name, exclude_symbols):
68+
continue
5069
function_static = function_def.static == "yes"
5170

5271
if not function_static:
@@ -55,20 +74,29 @@ def _process_namespace_sections(snapshot, namespace_scope, compound_object):
5574
)
5675
elif section_def.kind == "typedef":
5776
for typedef_def in section_def.memberdef:
77+
qualified_name = f"{compound_name}::{typedef_def.get_name()}"
78+
if _should_exclude_symbol(qualified_name, exclude_symbols):
79+
continue
5880
namespace_scope.add_member(get_typedef_member(typedef_def, "public"))
5981
elif section_def.kind == "enum":
6082
for enum_def in section_def.memberdef:
83+
qualified_name = f"{compound_name}::{enum_def.get_name()}"
84+
if _should_exclude_symbol(qualified_name, exclude_symbols):
85+
continue
6186
create_enum_scope(snapshot, enum_def)
6287
else:
6388
print(
6489
f"Unknown section kind: {section_def.kind} in {compound_object.location.file}"
6590
)
6691

6792

68-
def _handle_namespace_compound(snapshot, compound_object):
93+
def _handle_namespace_compound(snapshot, compound_object, exclude_symbols=None):
6994
"""
7095
Handle a namespace compound definition.
7196
"""
97+
if exclude_symbols is None:
98+
exclude_symbols = []
99+
72100
# Skip anonymous namespaces (internal linkage, not public API).
73101
# Doxygen encodes them with a '@' prefix in the compound name.
74102
if "@" in compound_object.compoundname:
@@ -78,7 +106,9 @@ def _handle_namespace_compound(snapshot, compound_object):
78106

79107
namespace_scope.location = compound_object.location.file
80108

81-
_process_namespace_sections(snapshot, namespace_scope, compound_object)
109+
_process_namespace_sections(
110+
snapshot, namespace_scope, compound_object, exclude_symbols
111+
)
82112

83113

84114
def _handle_concept_compound(snapshot, compound_object):
@@ -140,10 +170,18 @@ def _handle_class_compound(snapshot, compound_object):
140170
)
141171

142172

143-
def build_snapshot(xml_dir: str) -> Snapshot:
173+
def build_snapshot(xml_dir: str, exclude_symbols: list[str] | None = None) -> Snapshot:
144174
"""
145175
Reads the Doxygen XML output and builds a snapshot of the C++ API.
176+
177+
Args:
178+
xml_dir: Path to the Doxygen XML output directory.
179+
exclude_symbols: Optional list of substring patterns. Compounds whose
180+
qualified name contains any of these patterns will be excluded.
146181
"""
182+
if exclude_symbols is None:
183+
exclude_symbols = []
184+
147185
index_path = os.path.join(xml_dir, "index.xml")
148186
if not os.path.exists(index_path):
149187
raise RuntimeError(f"Doxygen entry point not found at {index_path}")
@@ -163,12 +201,19 @@ def build_snapshot(xml_dir: str) -> Snapshot:
163201
if compound_object.prot == "private":
164202
continue
165203

204+
if _should_exclude_symbol(compound_object.compoundname, exclude_symbols):
205+
continue
206+
166207
kind = compound_object.kind
167208

168209
if kind in _IGNORED_COMPOUNDS:
169210
pass
170211
elif kind in _COMPOUND_HANDLERS:
171-
_COMPOUND_HANDLERS[kind](snapshot, compound_object)
212+
handler = _COMPOUND_HANDLERS[kind]
213+
if handler == _handle_namespace_compound:
214+
handler(snapshot, compound_object, exclude_symbols)
215+
else:
216+
handler(snapshot, compound_object)
172217
else:
173218
print(f"Unknown compound kind: {kind}")
174219

scripts/cxx-api/tests/test_config.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,93 @@ def test_global_exclude_patterns_applied_to_all_platforms(self):
480480
view_b = next(r for r in result if r.snapshot_name == "ViewB")
481481
self.assertEqual(view_b.exclude_patterns, ["*/test/*", "*/b_only/*"])
482482

483+
# =========================================================================
484+
# Exclude symbols
485+
# =========================================================================
486+
487+
def test_exclude_symbols_parsed(self):
488+
"""Global exclude_symbols are parsed and propagated"""
489+
config = {
490+
"exclude_symbols": ["Fantom", "::detail::"],
491+
"platforms": {
492+
"TestView": {
493+
"inputs": [],
494+
"definitions": {},
495+
}
496+
},
497+
}
498+
result = parse_config(config, "/base/dir")
499+
500+
self.assertEqual(result[0].exclude_symbols, ["Fantom", "::detail::"])
501+
502+
def test_exclude_symbols_propagated_to_variants(self):
503+
"""Global exclude_symbols are propagated to all variant configs"""
504+
config = {
505+
"exclude_symbols": ["Fantom"],
506+
"platforms": {
507+
"TestView": {
508+
"inputs": [],
509+
"definitions": {},
510+
"variants": {
511+
"debug": {"definitions": {"DEBUG": 1}},
512+
"release": {"definitions": {"NDEBUG": 1}},
513+
},
514+
}
515+
},
516+
}
517+
result = parse_config(config, "/base/dir")
518+
519+
self.assertEqual(len(result), 2)
520+
for r in result:
521+
self.assertEqual(r.exclude_symbols, ["Fantom"])
522+
523+
def test_exclude_symbols_default_empty(self):
524+
"""Missing exclude_symbols defaults to empty list"""
525+
config = {
526+
"platforms": {
527+
"TestView": {
528+
"inputs": [],
529+
"definitions": {},
530+
}
531+
}
532+
}
533+
result = parse_config(config, "/base/dir")
534+
535+
self.assertEqual(result[0].exclude_symbols, [])
536+
537+
def test_exclude_symbols_none_treated_as_empty(self):
538+
"""None exclude_symbols treated as empty list"""
539+
config = {
540+
"exclude_symbols": None,
541+
"platforms": {
542+
"TestView": {
543+
"inputs": [],
544+
"definitions": {},
545+
}
546+
},
547+
}
548+
result = parse_config(config, "/base/dir")
549+
550+
self.assertEqual(result[0].exclude_symbols, [])
551+
552+
def test_exclude_symbols_applied_to_all_platforms(self):
553+
"""Global exclude_symbols apply to every platform"""
554+
config = {
555+
"exclude_symbols": ["Fantom"],
556+
"platforms": {
557+
"ViewA": {
558+
"inputs": [],
559+
},
560+
"ViewB": {
561+
"inputs": [],
562+
},
563+
},
564+
}
565+
result = parse_config(config, "/base/dir")
566+
567+
for r in result:
568+
self.assertEqual(r.exclude_symbols, ["Fantom"])
569+
483570
# =========================================================================
484571
# Variant naming
485572
# =========================================================================

0 commit comments

Comments
 (0)