Source code for hlsfactory.harp._programl_compat
"""Compatibility shims for the pinned `programl` release used by HARP.
`programl` 0.3.2 is the latest published release and predates several
dependency changes. Importing and using it on a modern stack needs four
adjustments, all applied by :func:`load_programl`:
1. Its generated ``*_pb2.py`` modules were built with protoc < 3.19 and are
rejected by modern protobuf unless the pure-Python implementation is used.
2. ``programl.transform_ops`` imports ``dgl`` unconditionally to provide
``to_dgl``, which HARP never calls. A real ``dgl`` drags in ``torch`` plus a
``libgraphbolt`` built for one exact torch version, so a stub is injected
instead.
3. ``programl.to_networkx`` calls ``node_link_graph`` without naming the edges
key. networkx >= 3.4 changed that default from ``links`` to ``edges`` while
programl still emits ``links``, so :func:`program_graph_to_networkx` does the
conversion explicitly rather than pinning networkx.
4. The bundled ``llvm2graph-10`` binary needs ``libtinfo.so.5``. That is an
environment prerequisite and cannot be fixed from Python; see
:func:`check_programl_runtime`.
"""
from __future__ import annotations
import os
import sys
import types
from typing import Any
PROTOBUF_IMPL_ENV_VAR = "PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"
_INSTALL_HINT = (
"HARPGraphFlow requires `networkx` and `programl`. `programl` is not part "
"of the `harp` extra because it depends on dgl/torch/CUDA, none of which "
"HARP uses. Install both:\n"
" pip install 'hlsfactory[harp]'\n"
" pip install programl"
)
[docs]
def _install_dgl_stub() -> None:
if "dgl" in sys.modules:
return
dgl = types.ModuleType("dgl")
dgl.__path__ = [] # type: ignore[attr-defined]
heterograph = types.ModuleType("dgl.heterograph")
class DGLHeteroGraph:
"""Placeholder for the unused `programl.to_dgl` return type."""
heterograph.DGLHeteroGraph = DGLHeteroGraph # type: ignore[attr-defined]
dgl.heterograph = heterograph # type: ignore[attr-defined]
sys.modules["dgl"] = dgl
sys.modules["dgl.heterograph"] = heterograph
[docs]
def load_programl() -> Any:
"""Import and return the `programl` module with all shims applied.
Returns:
Any: The imported `programl` module.
Raises:
ImportError: If `programl` or `networkx` is not installed.
"""
os.environ.setdefault(PROTOBUF_IMPL_ENV_VAR, "python")
_install_dgl_stub()
try:
import networkx # noqa: F401
import programl
except ImportError as error:
raise ImportError(f"{_INSTALL_HINT} ({error})") from error
return programl
[docs]
def program_graph_to_networkx(program_graph: Any) -> Any:
"""Convert a ProGraML `ProgramGraph` to a networkx `MultiDiGraph`.
Replaces `programl.to_networkx`, which breaks on networkx >= 3.4.
Args:
program_graph (Any): A ProGraML `ProgramGraph`.
Returns:
Any: The graph as a networkx `MultiDiGraph`.
"""
from networkx.readwrite import json_graph as nx_json
programl = load_programl()
data = programl.to_json(program_graph)
edges_key = "links" if "links" in data else "edges"
return nx_json.node_link_graph(
data,
multigraph=True,
directed=True,
edges=edges_key,
)
[docs]
def check_programl_runtime() -> None:
"""Verify that the bundled ProGraML binary can actually run.
The `llvm2graph` binaries link against `libtinfo.so.5`, which modern
distributions no longer ship. Failing here gives a clearer message than the
`GraphCreationError` raised on first use.
Raises:
RuntimeError: If the bundled binary cannot be executed.
"""
programl = load_programl()
try:
programl.from_llvm_ir("define void @probe() {\nentry:\n ret void\n}\n")
except Exception as error: # noqa: BLE001
message = str(error)
if "libtinfo.so.5" in message:
raise RuntimeError(
"The ProGraML binary bundled with `programl` requires "
"libtinfo.so.5, which is not available on this system. Install "
"it (e.g. extract libtinfo5 from a Debian bullseye package) and "
"add its directory to LD_LIBRARY_PATH.",
) from error
raise RuntimeError(
f"The ProGraML binary bundled with `programl` failed to run: {message}",
) from error