Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/react-native/.doxygen.config.template
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ PROJECT_ICON =
# entered, it will be relative to the location where Doxygen was started. If
# left blank the current directory will be used.

OUTPUT_DIRECTORY = api
OUTPUT_DIRECTORY = ${OUTPUT_DIR}

# If the CREATE_SUBDIRS tag is set to YES then Doxygen will create up to 4096
# sub-directories (in 2 levels) under the output directory of each output format
Expand Down
2 changes: 1 addition & 1 deletion scripts/cxx-api/manual_test/.doxygen.config.template
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ PROJECT_ICON =
# entered, it will be relative to the location where Doxygen was started. If
# left blank the current directory will be used.

OUTPUT_DIRECTORY = api
OUTPUT_DIRECTORY = ${OUTPUT_DIR}

# If the CREATE_SUBDIRS tag is set to YES then Doxygen will create up to 4096
# sub-directories (in 2 levels) under the output directory of each output format
Expand Down
135 changes: 94 additions & 41 deletions scripts/cxx-api/parser/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@
"""

import argparse
import concurrent.futures
import os
import shutil
import subprocess
import sys
import tempfile
import traceback

from .config import ApiViewSnapshotConfig, parse_config_file
from .doxygen import get_doxygen_bin, run_doxygen
Expand All @@ -36,13 +37,20 @@ def run_command(
if result.returncode != 0:
if verbose:
print(f"{label} finished with error: {result.stderr}")
sys.exit(1)
raise RuntimeError(
f"{label} finished with error (exit code {result.returncode})"
)
elif verbose:
print(f"{label} finished successfully")
return result


def build_codegen(platform: str, verbose: bool = False) -> str:
def build_codegen(
platform: str,
verbose: bool = False,
output_path: str = "./api/codegen",
label: str = "",
) -> str:
react_native_dir = os.path.join(get_react_native_dir(), "packages", "react-native")

run_command(
Expand All @@ -52,17 +60,21 @@ def build_codegen(platform: str, verbose: bool = False) -> str:
"--path",
"./",
"--outputPath",
"./api/codegen",
output_path,
"--targetPlatform",
platform,
"--forceOutputPath",
],
label="Codegen",
label=f"[{label}] Codegen" if label else "Codegen",
verbose=verbose,
cwd=react_native_dir,
capture_output=True,
text=True,
)

return os.path.join(react_native_dir, "api", "codegen")
if os.path.isabs(output_path):
return output_path
return os.path.join(react_native_dir, output_path)


def build_snapshot_for_view(
Expand All @@ -75,21 +87,29 @@ def build_snapshot_for_view(
codegen_platform: str | None = None,
verbose: bool = True,
input_filter: str = None,
work_dir: str | None = None,
) -> str:
if verbose:
print(f"Generating API view: {api_view}")
print(f"[{api_view}] Generating API view")

api_dir = os.path.join(react_native_dir, "api")
if os.path.exists(api_dir):
if verbose:
print(" Deleting existing output directory")
shutil.rmtree(api_dir)
include_directories = list(include_directories)

if work_dir is None:
work_dir = os.path.join(react_native_dir, "api")

if codegen_platform is not None:
codegen_dir = build_codegen(codegen_platform, verbose=verbose)
codegen_output = os.path.join(work_dir, "codegen")
codegen_dir = build_codegen(
codegen_platform,
verbose=verbose,
output_path=codegen_output,
label=api_view,
)
include_directories.append(codegen_dir)
elif verbose:
print(" Skipping codegen")
print(f"[{api_view}] Skipping codegen")

config_file = f".doxygen.config.{api_view}.generated"

run_doxygen(
working_dir=react_native_dir,
Expand All @@ -98,12 +118,15 @@ def build_snapshot_for_view(
definitions=definitions,
input_filter=input_filter,
verbose=verbose,
output_dir=work_dir,
config_file=config_file,
label=api_view,
)

if verbose:
print(" Building snapshot")
print(f"[{api_view}] Building snapshot")

snapshot = build_snapshot(os.path.join(api_dir, "xml"))
snapshot = build_snapshot(os.path.join(work_dir, "xml"))
snapshot_string = snapshot.to_string()

output_file = os.path.join(output_dir, f"{api_view}Cxx.api")
Expand All @@ -126,36 +149,66 @@ def build_snapshots(
is_test: bool = False,
) -> None:
if not is_test:
for config in snapshot_configs:
if view_filter and config.snapshot_name != view_filter:
continue

build_snapshot_for_view(
api_view=config.snapshot_name,
configs_to_build = [
config
for config in snapshot_configs
if not view_filter or config.snapshot_name == view_filter
]

with tempfile.TemporaryDirectory(prefix="cxx-api-") as parent_tmp:
with concurrent.futures.ThreadPoolExecutor() as executor:
futures = {}
for config in configs_to_build:
work_dir = os.path.join(parent_tmp, config.snapshot_name)
os.makedirs(work_dir, exist_ok=True)
future = executor.submit(
build_snapshot_for_view,
api_view=config.snapshot_name,
react_native_dir=react_native_dir,
include_directories=config.inputs,
exclude_patterns=config.exclude_patterns,
definitions=config.definitions,
output_dir=output_dir,
codegen_platform=config.codegen_platform,
verbose=verbose,
input_filter=input_filter if config.input_filter else None,
work_dir=work_dir,
)
futures[future] = config.snapshot_name

errors = []
for future in concurrent.futures.as_completed(futures):
view_name = futures[future]
try:
future.result()
except Exception as e:
errors.append((view_name, e))
if verbose:
print(
f"[{view_name}] Error generating:\n"
f"{traceback.format_exc()}"
)

if errors:
failed_views = ", ".join(name for name, _ in errors)
raise RuntimeError(f"Failed to generate snapshots: {failed_views}")
else:
with tempfile.TemporaryDirectory(prefix="cxx-api-test-") as work_dir:
snapshot = build_snapshot_for_view(
api_view="Test",
react_native_dir=react_native_dir,
include_directories=config.inputs,
exclude_patterns=config.exclude_patterns,
definitions=config.definitions,
include_directories=[],
exclude_patterns=[],
definitions={},
output_dir=output_dir,
codegen_platform=config.codegen_platform,
codegen_platform=None,
verbose=verbose,
input_filter=input_filter if config.input_filter else None,
input_filter=input_filter,
work_dir=work_dir,
)
else:
snapshot = build_snapshot_for_view(
api_view="Test",
react_native_dir=react_native_dir,
include_directories=[],
exclude_patterns=[],
definitions={},
output_dir=output_dir,
codegen_platform=None,
verbose=verbose,
input_filter=input_filter,
)

if verbose:
print(snapshot)
if verbose:
print(snapshot)


def get_default_snapshot_dir() -> str:
Expand Down
30 changes: 19 additions & 11 deletions scripts/cxx-api/parser/doxygen.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@

import os
import subprocess
import sys

_DOXYGEN_CONFIG_FILE = ".doxygen.config.generated"

Expand All @@ -24,6 +23,8 @@ def build_doxygen_config(
exclude_patterns: list[str] = None,
definitions: dict[str, str | int] = None,
input_filter: str = None,
output_dir: str = "api",
config_file: str = _DOXYGEN_CONFIG_FILE,
) -> None:
if include_directories is None:
include_directories = []
Expand Down Expand Up @@ -54,9 +55,10 @@ def build_doxygen_config(
.replace("${EXCLUDE_PATTERNS}", exclude_patterns_str)
.replace("${PREDEFINED}", definitions_str)
.replace("${DOXYGEN_INPUT_FILTER}", input_filter_str)
.replace("${OUTPUT_DIR}", output_dir)
)

with open(os.path.join(directory, _DOXYGEN_CONFIG_FILE), "w") as f:
with open(os.path.join(directory, config_file), "w") as f:
f.write(config)


Expand All @@ -67,40 +69,46 @@ def run_doxygen(
definitions: dict[str, str | int],
input_filter: str = None,
verbose: bool = True,
output_dir: str = "api",
config_file: str = _DOXYGEN_CONFIG_FILE,
label: str = "",
) -> None:
"""Generate Doxygen config, run Doxygen, and clean up the config file."""
prefix = f"[{label}] " if label else ""
if verbose:
print(" Generating Doxygen config file")
print(f"{prefix}Generating Doxygen config file")

build_doxygen_config(
working_dir,
include_directories=include_directories,
exclude_patterns=exclude_patterns,
definitions=definitions,
input_filter=input_filter,
output_dir=output_dir,
config_file=config_file,
)

if verbose:
print(" Running Doxygen")
print(f"{prefix}Running Doxygen")
if input_filter:
print(f" Using input filter: {input_filter}")
print(f"{prefix}Using input filter: {input_filter}")

doxygen_bin = get_doxygen_bin()

result = subprocess.run(
[doxygen_bin, _DOXYGEN_CONFIG_FILE],
[doxygen_bin, config_file],
cwd=working_dir,
capture_output=True,
text=True,
)

if result.returncode != 0:
if verbose:
print(f" Doxygen finished with error: {result.stderr}")
sys.exit(1)
print(f"{prefix}Doxygen finished with error: {result.stderr}")
raise RuntimeError(f"Doxygen finished with error: {result.stderr}")
elif verbose:
print(" Doxygen finished successfully")
print(f"{prefix}Doxygen finished successfully")

if verbose:
print(" Deleting Doxygen config file")
os.remove(os.path.join(working_dir, _DOXYGEN_CONFIG_FILE))
print(f"{prefix}Deleting Doxygen config file")
os.remove(os.path.join(working_dir, config_file))
Loading