From ad6a9d0eaa0942be7ece6bb253eeea5f7dd2e9e7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mat=C4=9Bj=20Cepl?= <mcepl@cepl.eu>
Date: Wed, 22 Oct 2025 19:51:56 +0200
Subject: [PATCH] fix: correct signature of gvRenderData() function
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

For compatibility with graphviz 14.*

Fixes: https://github.com/pygraphviz/pygraphviz/issues/567
Replaces: https://github.com/pygraphviz/pygraphviz/pull/566
Signed-off-by: Matěj Cepl <mcepl@cepl.eu>
--- a/pygraphviz/graphviz.i
+++ b/pygraphviz/graphviz.i
@@ -338,7 +338,12 @@ int gvRenderFilename(GVC_t *gvc, Agraph_t* g, char *format, char *filename);
 /* three lines are straight from the SWIG manual.  */
 %include <cstring.i>
 %include <typemaps.i>
+#if GRAPHVIZ_VERSION_MAJOR >= 14
+%cstring_output_allocate_size(char **result, size_t* size, free(*$1));
+int gvRenderData(GVC_t *gvc, Agraph_t* g, char *format, char **result, size_t *size);
+#else
 %cstring_output_allocate_size(char **result, unsigned int* size, free(*$1));
 int gvRenderData(GVC_t *gvc, Agraph_t* g, char *format, char **result, unsigned int *size);
+#endif
 /* Free memory allocated and pointed to by *result in gvRenderData */
 extern void gvFreeRenderData (char* data);
--- a/setup.py
+++ b/setup.py
@@ -1,15 +1,72 @@
 import sys
+import os
+import re
 from setuptools import setup, Extension
 
+def get_graphviz_version():
+    """
+    Reads GRAPHVIZ_VERSION_MAJOR from the header file.
+    Assumes the header is available at a known path during setup.
+    """
+    # NOTE: You may need to adjust this path based on your environment
+    # or rely on the build system to have already installed it.
+    header_path = '/usr/include/graphviz/graphviz_version.h'
+
+    if not os.path.exists(header_path):
+        # Fallback/default if header file cannot be read during setup.
+        # This should match your expected target version.
+        raise RuntimeError(f"Graphviz header file not found at {header_path}.")
+
+    with open(header_path, 'r') as f:
+        content = f.read()
+        match = re.search(r'#define\s+GRAPHVIZ_VERSION_MAJOR\s+(\d+)', content)
+        if match:
+            return int(match.group(1))
+        else:
+            match = re.search(r'#define\s+PACKAGE_VERSION\s+"([0-9.]+)"', content)
+            if match:
+                maj_ver = match.group(1).split('.')[0]
+                return int(maj_ver)
+
+    raise RuntimeError(f"GRAPHVIZ_VERSION_MAJOR macro not found in the header file!")
+
 if __name__ == "__main__":
-    define_macros = [("SWIG_PYTHON_STRICT_BYTE_CHAR", None)]
-    if sys.platform == "win32":
+    WINDOWS = sys.platform == "win32"
+
+    # Get the target version number
+    gv_major_version = get_graphviz_version()
+
+    define_macros = []
+    swig_options = []
+
+    if WINDOWS:
         define_macros.append(("GVDLL", None))
 
+    swig_options.append("-DGRAPHVIZ_VERSION_MAJOR={}".format(str(gv_major_version)))
+    print(f"Defining GRAPHVIZ_VERSION_MAJOR as: {gv_major_version}")
+
+    # List of search paths for where graphviz libs may be installed.
+    # The graphviz library subdir contains the plugin libraries (e.g.
+    # gvplugin_*). The main graphviz libs (cgraph etc.) are in the
+    # parent dir
+    library_search_paths = [
+        "/usr/lib/x86_64-linux-gnu",  # Ubuntu x86_64
+        "/usr/lib/x86_64-linux-gnu/graphviz",
+        "/opt/homebrew/lib",  # Macos, homebrew aarch64
+        "/opt/homebrew/lib/graphviz",
+        "/usr/lib64",  # Fedora
+        "/usr/lib64/graphviz",
+        "/usr/local/lib",  # source install / macos homebrew x86_64
+        "/usr/local/lib/graphviz",
+    ]
+
+    # runtime_library_dirs must not be defined with windows else setup will fail
+    extra_kwargs = {} if WINDOWS else {"runtime_library_dirs": library_search_paths}
+
     extension = [
         Extension(
             name="pygraphviz._graphviz",
-            sources=["pygraphviz/graphviz_wrap.c"],
+            sources=["pygraphviz/graphviz.i"],
             include_dirs=[],
             library_dirs=[],
             # cdt does not link to cgraph, whereas cgraph links to cdt.
@@ -20,6 +77,8 @@ if __name__ == "__main__":
             # undefined symbol errors. seen under PyPy on Linux.)
             libraries=["cdt", "cgraph", "gvc"],
             define_macros=define_macros,
+            swig_opts=swig_options,
+            **extra_kwargs,
         )
     ]
 
