Skip to content

Commit 32f1dcd

Browse files
j-piaseckimeta-codesync[bot]
authored andcommitted
Generate snapshots in parallel (#56184)
Summary: Pull Request resolved: #56184 Changelog: [Internal] Updates the snapshot generator script to run generation steps for all snapshots in parallel instead of in sequence, significantly improving the generation time. Reviewed By: cortinico Differential Revision: D97714334 fbshipit-source-id: b643dd7bb2fa4f3f9db1495bc7a44c0069a25274
1 parent 6b764a2 commit 32f1dcd

File tree

4 files changed

+115
-54
lines changed

4 files changed

+115
-54
lines changed

packages/react-native/.doxygen.config.template

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ PROJECT_ICON =
7474
# entered, it will be relative to the location where Doxygen was started. If
7575
# left blank the current directory will be used.
7676

77-
OUTPUT_DIRECTORY = api
77+
OUTPUT_DIRECTORY = ${OUTPUT_DIR}
7878

7979
# If the CREATE_SUBDIRS tag is set to YES then Doxygen will create up to 4096
8080
# sub-directories (in 2 levels) under the output directory of each output format

scripts/cxx-api/manual_test/.doxygen.config.template

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ PROJECT_ICON =
7474
# entered, it will be relative to the location where Doxygen was started. If
7575
# left blank the current directory will be used.
7676

77-
OUTPUT_DIRECTORY = api
77+
OUTPUT_DIRECTORY = ${OUTPUT_DIR}
7878

7979
# If the CREATE_SUBDIRS tag is set to YES then Doxygen will create up to 4096
8080
# sub-directories (in 2 levels) under the output directory of each output format

scripts/cxx-api/parser/__main__.py

Lines changed: 94 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@
1212
"""
1313

1414
import argparse
15+
import concurrent.futures
1516
import os
16-
import shutil
1717
import subprocess
1818
import sys
1919
import tempfile
20+
import traceback
2021

2122
from .config import ApiViewSnapshotConfig, parse_config_file
2223
from .doxygen import get_doxygen_bin, run_doxygen
@@ -36,13 +37,20 @@ def run_command(
3637
if result.returncode != 0:
3738
if verbose:
3839
print(f"{label} finished with error: {result.stderr}")
39-
sys.exit(1)
40+
raise RuntimeError(
41+
f"{label} finished with error (exit code {result.returncode})"
42+
)
4043
elif verbose:
4144
print(f"{label} finished successfully")
4245
return result
4346

4447

45-
def build_codegen(platform: str, verbose: bool = False) -> str:
48+
def build_codegen(
49+
platform: str,
50+
verbose: bool = False,
51+
output_path: str = "./api/codegen",
52+
label: str = "",
53+
) -> str:
4654
react_native_dir = os.path.join(get_react_native_dir(), "packages", "react-native")
4755

4856
run_command(
@@ -52,17 +60,21 @@ def build_codegen(platform: str, verbose: bool = False) -> str:
5260
"--path",
5361
"./",
5462
"--outputPath",
55-
"./api/codegen",
63+
output_path,
5664
"--targetPlatform",
5765
platform,
5866
"--forceOutputPath",
5967
],
60-
label="Codegen",
68+
label=f"[{label}] Codegen" if label else "Codegen",
6169
verbose=verbose,
6270
cwd=react_native_dir,
71+
capture_output=True,
72+
text=True,
6373
)
6474

65-
return os.path.join(react_native_dir, "api", "codegen")
75+
if os.path.isabs(output_path):
76+
return output_path
77+
return os.path.join(react_native_dir, output_path)
6678

6779

6880
def build_snapshot_for_view(
@@ -75,21 +87,29 @@ def build_snapshot_for_view(
7587
codegen_platform: str | None = None,
7688
verbose: bool = True,
7789
input_filter: str = None,
90+
work_dir: str | None = None,
7891
) -> str:
7992
if verbose:
80-
print(f"Generating API view: {api_view}")
93+
print(f"[{api_view}] Generating API view")
8194

82-
api_dir = os.path.join(react_native_dir, "api")
83-
if os.path.exists(api_dir):
84-
if verbose:
85-
print(" Deleting existing output directory")
86-
shutil.rmtree(api_dir)
95+
include_directories = list(include_directories)
96+
97+
if work_dir is None:
98+
work_dir = os.path.join(react_native_dir, "api")
8799

88100
if codegen_platform is not None:
89-
codegen_dir = build_codegen(codegen_platform, verbose=verbose)
101+
codegen_output = os.path.join(work_dir, "codegen")
102+
codegen_dir = build_codegen(
103+
codegen_platform,
104+
verbose=verbose,
105+
output_path=codegen_output,
106+
label=api_view,
107+
)
90108
include_directories.append(codegen_dir)
91109
elif verbose:
92-
print(" Skipping codegen")
110+
print(f"[{api_view}] Skipping codegen")
111+
112+
config_file = f".doxygen.config.{api_view}.generated"
93113

94114
run_doxygen(
95115
working_dir=react_native_dir,
@@ -98,12 +118,15 @@ def build_snapshot_for_view(
98118
definitions=definitions,
99119
input_filter=input_filter,
100120
verbose=verbose,
121+
output_dir=work_dir,
122+
config_file=config_file,
123+
label=api_view,
101124
)
102125

103126
if verbose:
104-
print(" Building snapshot")
127+
print(f"[{api_view}] Building snapshot")
105128

106-
snapshot = build_snapshot(os.path.join(api_dir, "xml"))
129+
snapshot = build_snapshot(os.path.join(work_dir, "xml"))
107130
snapshot_string = snapshot.to_string()
108131

109132
output_file = os.path.join(output_dir, f"{api_view}Cxx.api")
@@ -126,36 +149,66 @@ def build_snapshots(
126149
is_test: bool = False,
127150
) -> None:
128151
if not is_test:
129-
for config in snapshot_configs:
130-
if view_filter and config.snapshot_name != view_filter:
131-
continue
132-
133-
build_snapshot_for_view(
134-
api_view=config.snapshot_name,
152+
configs_to_build = [
153+
config
154+
for config in snapshot_configs
155+
if not view_filter or config.snapshot_name == view_filter
156+
]
157+
158+
with tempfile.TemporaryDirectory(prefix="cxx-api-") as parent_tmp:
159+
with concurrent.futures.ThreadPoolExecutor() as executor:
160+
futures = {}
161+
for config in configs_to_build:
162+
work_dir = os.path.join(parent_tmp, config.snapshot_name)
163+
os.makedirs(work_dir, exist_ok=True)
164+
future = executor.submit(
165+
build_snapshot_for_view,
166+
api_view=config.snapshot_name,
167+
react_native_dir=react_native_dir,
168+
include_directories=config.inputs,
169+
exclude_patterns=config.exclude_patterns,
170+
definitions=config.definitions,
171+
output_dir=output_dir,
172+
codegen_platform=config.codegen_platform,
173+
verbose=verbose,
174+
input_filter=input_filter if config.input_filter else None,
175+
work_dir=work_dir,
176+
)
177+
futures[future] = config.snapshot_name
178+
179+
errors = []
180+
for future in concurrent.futures.as_completed(futures):
181+
view_name = futures[future]
182+
try:
183+
future.result()
184+
except Exception as e:
185+
errors.append((view_name, e))
186+
if verbose:
187+
print(
188+
f"[{view_name}] Error generating:\n"
189+
f"{traceback.format_exc()}"
190+
)
191+
192+
if errors:
193+
failed_views = ", ".join(name for name, _ in errors)
194+
raise RuntimeError(f"Failed to generate snapshots: {failed_views}")
195+
else:
196+
with tempfile.TemporaryDirectory(prefix="cxx-api-test-") as work_dir:
197+
snapshot = build_snapshot_for_view(
198+
api_view="Test",
135199
react_native_dir=react_native_dir,
136-
include_directories=config.inputs,
137-
exclude_patterns=config.exclude_patterns,
138-
definitions=config.definitions,
200+
include_directories=[],
201+
exclude_patterns=[],
202+
definitions={},
139203
output_dir=output_dir,
140-
codegen_platform=config.codegen_platform,
204+
codegen_platform=None,
141205
verbose=verbose,
142-
input_filter=input_filter if config.input_filter else None,
206+
input_filter=input_filter,
207+
work_dir=work_dir,
143208
)
144-
else:
145-
snapshot = build_snapshot_for_view(
146-
api_view="Test",
147-
react_native_dir=react_native_dir,
148-
include_directories=[],
149-
exclude_patterns=[],
150-
definitions={},
151-
output_dir=output_dir,
152-
codegen_platform=None,
153-
verbose=verbose,
154-
input_filter=input_filter,
155-
)
156209

157-
if verbose:
158-
print(snapshot)
210+
if verbose:
211+
print(snapshot)
159212

160213

161214
def get_default_snapshot_dir() -> str:

scripts/cxx-api/parser/doxygen.py

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99

1010
import os
1111
import subprocess
12-
import sys
1312

1413
_DOXYGEN_CONFIG_FILE = ".doxygen.config.generated"
1514

@@ -24,6 +23,8 @@ def build_doxygen_config(
2423
exclude_patterns: list[str] = None,
2524
definitions: dict[str, str | int] = None,
2625
input_filter: str = None,
26+
output_dir: str = "api",
27+
config_file: str = _DOXYGEN_CONFIG_FILE,
2728
) -> None:
2829
if include_directories is None:
2930
include_directories = []
@@ -54,9 +55,10 @@ def build_doxygen_config(
5455
.replace("${EXCLUDE_PATTERNS}", exclude_patterns_str)
5556
.replace("${PREDEFINED}", definitions_str)
5657
.replace("${DOXYGEN_INPUT_FILTER}", input_filter_str)
58+
.replace("${OUTPUT_DIR}", output_dir)
5759
)
5860

59-
with open(os.path.join(directory, _DOXYGEN_CONFIG_FILE), "w") as f:
61+
with open(os.path.join(directory, config_file), "w") as f:
6062
f.write(config)
6163

6264

@@ -67,40 +69,46 @@ def run_doxygen(
6769
definitions: dict[str, str | int],
6870
input_filter: str = None,
6971
verbose: bool = True,
72+
output_dir: str = "api",
73+
config_file: str = _DOXYGEN_CONFIG_FILE,
74+
label: str = "",
7075
) -> None:
7176
"""Generate Doxygen config, run Doxygen, and clean up the config file."""
77+
prefix = f"[{label}] " if label else ""
7278
if verbose:
73-
print(" Generating Doxygen config file")
79+
print(f"{prefix}Generating Doxygen config file")
7480

7581
build_doxygen_config(
7682
working_dir,
7783
include_directories=include_directories,
7884
exclude_patterns=exclude_patterns,
7985
definitions=definitions,
8086
input_filter=input_filter,
87+
output_dir=output_dir,
88+
config_file=config_file,
8189
)
8290

8391
if verbose:
84-
print(" Running Doxygen")
92+
print(f"{prefix}Running Doxygen")
8593
if input_filter:
86-
print(f" Using input filter: {input_filter}")
94+
print(f"{prefix}Using input filter: {input_filter}")
8795

8896
doxygen_bin = get_doxygen_bin()
8997

9098
result = subprocess.run(
91-
[doxygen_bin, _DOXYGEN_CONFIG_FILE],
99+
[doxygen_bin, config_file],
92100
cwd=working_dir,
93101
capture_output=True,
94102
text=True,
95103
)
96104

97105
if result.returncode != 0:
98106
if verbose:
99-
print(f" Doxygen finished with error: {result.stderr}")
100-
sys.exit(1)
107+
print(f"{prefix}Doxygen finished with error: {result.stderr}")
108+
raise RuntimeError(f"Doxygen finished with error: {result.stderr}")
101109
elif verbose:
102-
print(" Doxygen finished successfully")
110+
print(f"{prefix}Doxygen finished successfully")
103111

104112
if verbose:
105-
print(" Deleting Doxygen config file")
106-
os.remove(os.path.join(working_dir, _DOXYGEN_CONFIG_FILE))
113+
print(f"{prefix}Deleting Doxygen config file")
114+
os.remove(os.path.join(working_dir, config_file))

0 commit comments

Comments
 (0)