Files
IfcOpenShell/src/wrappergen/generate.py
T
2026-03-31 20:51:46 +02:00

135 lines
4.4 KiB
Python

from __future__ import annotations
import argparse
import sys
from pathlib import Path
if __package__ in {None, ""}:
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from wrappergen.clang_frontend import build_module_model
from wrappergen.config import CompilationConfig, IgnoreConfig, WrapperConfig
from wrappergen.emit import write_module_outputs
else:
from .clang_frontend import build_module_model
from .config import CompilationConfig, IgnoreConfig, WrapperConfig
from .emit import write_module_outputs
def _existing_directories(paths: list[Path]) -> list[str]:
seen: set[str] = set()
results: list[str] = []
for path in paths:
resolved = str(path.resolve())
if path.is_dir() and resolved not in seen:
seen.add(resolved)
results.append(resolved)
return results
def _discover_headers(src_ifcparse: Path) -> list[str]:
return [
str(path.resolve())
for path in sorted(src_ifcparse.glob("*.h"))
if path.parent.name != "schemas"
]
def _discover_boost_include_dirs() -> list[Path]:
candidates: list[Path] = []
prefix = Path(sys.prefix)
candidates.extend(
[
prefix / "Library" / "include",
prefix / "include",
]
)
executable = Path(sys.executable).resolve()
conda_root = executable.parents[2] if len(executable.parents) >= 3 else None
if conda_root is None:
return candidates
pkgs = conda_root / "pkgs"
if not pkgs.is_dir():
return candidates
for pattern in ("libboost-headers-*", "boost-cpp-*"):
for package_dir in sorted(pkgs.glob(pattern), reverse=True):
include_dir = package_dir / "Library" / "include"
if (include_dir / "boost" / "lexical_cast.hpp").is_file():
candidates.append(include_dir)
return candidates
def build_default_wrapper_config(repo_root: Path) -> WrapperConfig:
src_ifcparse = repo_root / "src" / "ifcparse"
include_dirs = _existing_directories([src_ifcparse, *_discover_boost_include_dirs()])
return WrapperConfig(
module_name="ifcopenshell_experimental",
c_prefix="ifcopenshell",
api_header_name="ifcopenshell_experimental_c_api.h",
api_implementation_name="ifcopenshell_experimental_c_api.cpp",
extension_source_name="_ifcopenshell_experimental.cpp",
python_source_name="ifcopenshell_experimental.py",
allowed_namespaces=["ifcopenshell", "express"],
enum_names={"ifcopenshell::filetype": "FileType"},
parameter_names={"type": "filetype", "read_only": "readonly"},
class_handle_kinds={"ifcopenshell::file": "shared_ptr"},
class_owner_types={
"express::Base": "ifcopenshell::file",
"express::Entity": "ifcopenshell::file",
"express::Select": "ifcopenshell::file",
"express::DeclaredType": "ifcopenshell::file",
},
type_adapters={
"std::string": "string",
"int": "integer",
"size_t": "integer",
"std::size_t": "integer",
"unsigned int": "integer",
"uint32_t": "integer",
"bool": "bool",
"void": "void",
},
ignore=IgnoreConfig(
namespaces=["ifcopenshell::impl"],
classes=[],
enums=[],
methods=[],
),
compilation=CompilationConfig(
headers=_discover_headers(src_ifcparse),
include_dirs=include_dirs,
),
)
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Generate an experimental C API, CPython module, and Python facade for ifcparse."
)
parser.add_argument(
"--repo-root",
default=str(Path(__file__).resolve().parents[2]),
help="Repository root that contains src/ifcparse",
)
parser.add_argument(
"--output-dir",
default=str(Path(__file__).resolve().parent / "generated"),
help="Directory that will receive the generated files",
)
return parser.parse_args()
def main() -> int:
arguments = parse_args()
repo_root = Path(arguments.repo_root).resolve()
output_dir = Path(arguments.output_dir).resolve()
model = build_module_model(build_default_wrapper_config(repo_root))
write_module_outputs(model, output_dir)
return 0
if __name__ == "__main__":
raise SystemExit(main())