]> granicus.if.org Git - graphviz/commitdiff
gvedit: locate attrs.txt based on the location of our own executable
authorMatthew Fernandez <matthew.fernandez@gmail.com>
Mon, 14 Nov 2022 03:18:11 +0000 (19:18 -0800)
committerMatthew Fernandez <matthew.fernandez@gmail.com>
Thu, 17 Nov 2022 05:08:57 +0000 (21:08 -0800)
This commit adapts 24bd92c1e5d49b354141cd06d88ca658991b9825 that taught Smyrna
how to find the share directory without a build time define to Gvedit. The
pattern is essentially the same, but we can take advantage of some nicer C++
mechanisms instead of C.

The motivation for this is to make integration of Gvedit into the CMake build
system easier. By doing location of the share directory in the code instead of
the build system, we can more easily ensure the same behavior between build
systems. A side effect of this is the `gvedit` binary becoming more relocatable.

Note that the Windows discovery mechanism differs from what it was before, but
it is now more in line with how discovery works on other platforms.

Gitlab: #1836

CHANGELOG.md
cmd/gvedit/Makefile.am
cmd/gvedit/csettings.cpp

index 558b026880ccfda2ac0760f36ebd9a5c9acc10d1..6a1bb61e90a70b91952e3856805f4be9782b90aa 100644 (file)
@@ -11,6 +11,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - The `cluster`, `dot_builtins`, `dot2gxl`, `gv2gxl`, and `prune` utilities are
   now included in the CMake build system. #1836
 
+### Changed
+
+- `gvedit` now uses a relative path from its own executable to discover its
+  attributes file, `../share/graphviz/gvedit/attrs.txt`. This should make it
+  more relocatable and make its behavior more consistent across operating
+  systems.
+
 ### Fixed
 
 - `gxl2gv`, when dealing with `name` attributes, may be less likely to crash. We
index b5c1f24f33fb66f4a7ea9d0c9d6ec672eb26b272..8b711623c9609c62857e442e11ab7e533c5b8e6e 100644 (file)
@@ -27,8 +27,7 @@ gvedit_CPPFLAGS = \
        $(AM_CPPFLAGS) \
        $(QTGUI_CFLAGS) \
        $(QTCORE_CFLAGS) \
-       -DDEMAND_LOADING=1 \
-       -DGVEDIT_DATADIR='"$(pkgdatadir)/gvedit"'
+       -DDEMAND_LOADING=1
 
 gvedit_LDADD = \
        $(top_builddir)/lib/gvc/libgvc.la \
index b0fe35473a9eab0abccd9156d018eaa28f59c611..38cad561878df087735a190180617ce97e91872c 100644 (file)
@@ -10,7 +10,9 @@
 #ifdef _WIN32
 #include "windows.h"
 #endif
+#include <cassert>
 #include "csettings.h"
+#include <cstdint>
 #include "qmessagebox.h"
 #include "qfiledialog.h"
 #include <QtWidgets>
 #include "string.h"
 #include "mainwindow.h"
 #include <QTemporaryFile>
+#include <string>
+#include <vector>
+
+#ifdef __APPLE__
+#include <mach-o/dyld.h>
+#endif
+
+#ifdef __FreeBSD__
+#include <sys/sysctl.h>
+#include <sys/types.h>
+#endif
+
+#if !defined(_WIN32)
+#include <unistd.h>
+#endif
 
 extern int errorPipe(char *errMsg);
 
@@ -29,37 +46,143 @@ typedef struct {
     int cur;
 } rdr_t;
 
-#ifdef _WIN32
-#define BSZ 1024
+#if !defined(__APPLE__) && !defined(_WIN32)
+/// read the given symlink in the /proc file system
+static std::string read_proc(const std::string &path) {
 
-QString findAttrFile ()
-{
-    char line[BSZ];
-    int r;
-    char* s;
-    QString path;
-    MEMORY_BASIC_INFORMATION mbi;
+  std::vector<char> buf(PATH_MAX + 1, '\0');
+  if (readlink(path.c_str(), buf.data(), buf.size()) < 0)
+    return "";
 
-    if (VirtualQuery (&findAttrFile, &mbi, sizeof(mbi)) == 0) {
-       errout << "failed to get handle for executable.\n";
-       return path;
-    }
-    r = GetModuleFileNameA((HMODULE)mbi.AllocationBase, line, BSZ);
-    if (!r || r == BSZ) {
-       errout << "failed to get path for executable.\n";
-       return path;
+  // was the path too long?
+  if (buf.back() != '\0')
+    return "";
+
+  return buf.data();
+}
+#endif
+
+/// find an absolute path to the current executable
+static std::string find_me(void) {
+
+  // macOS
+#ifdef __APPLE__
+  {
+    // determine how many bytes we will need to allocate
+    uint32_t buf_size = 0;
+    int rc = _NSGetExecutablePath(NULL, &buf_size);
+    assert(rc != 0);
+    assert(buf_size > 0);
+
+    std::vector<char> path(buf_size);
+
+    // retrieve the actual path
+    if (_NSGetExecutablePath(path.data(), &buf_size) < 0) {
+      errout << "failed to get path for executable.\n";
+      return "";
     }
-    s = strstr(line,"\\bin\\");
-    if (!s) {
-       errout << "no \"\\bin\" in path " << line << "\n";
-       return path;
+
+    // try to resolve any levels of symlinks if possible
+    while (true) {
+      std::vector<char> buf(PATH_MAX + 1, '\0');
+      if (readlink(path.data(), buf.data(), buf.size()) < 0)
+        return path.data();
+
+      path = buf;
     }
-    *s = '\0';
-    path.append(line);
-    path.append("\\share\\graphviz\\gvedit\\attributes.txt");
+  }
+#elif defined(_WIN32)
+  {
+    std::vector<char> path;
+    int rc = 0;
+
+    do {
+      {
+        size_t size = path.empty() ? 1024 : (path.size() * 2);
+        path.resize(size);
+      }
+
+      rc = GetModuleFileName(NULL, path.data(), path.size());
+      if (rc == 0) {
+        errout << "failed to get path for executable.\n";
+        return "";
+      }
+
+    } while (rc == path.size());
+
+    return path.data();
+  }
+#else
+
+  // Linux
+  std::string path = read_proc("/proc/self/exe");
+  if (path != "")
     return path;
+
+  // DragonFly BSD, FreeBSD
+  path = read_proc("/proc/curproc/file");
+  if (path != "")
+    return path;
+
+  // NetBSD
+  path = read_proc("/proc/curproc/exe");
+  if (path != "")
+    return path;
+
+// /proc-less FreeBSD
+#ifdef __FreeBSD__
+  {
+    int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1};
+    static const size_t MIB_LENGTH = sizeof(mib) / sizeof(mib[0]);
+    std::vector<char> buf(PATH_MAX + 1, '\0');
+    size_t buf_size = buf.size();
+    if (sysctl(mib, MIB_LENGTH, buf.data(), &buf_size, NULL, 0) == 0)
+      return buf.data();
+  }
+#endif
+#endif
+
+  errout << "failed to get path for executable.\n";
+  return "";
 }
+
+/// find an absolute path to where Smyrna auxiliary files are stored
+static std::string find_share(void) {
+
+#ifdef _WIN32
+  const char PATH_SEPARATOR = '\\';
+#else
+  const char PATH_SEPARATOR = '/';
 #endif
+
+  // find the path to the `gvedit` binary
+  std::string gvedit_exe = find_me();
+  if (gvedit_exe == "")
+    return "";
+
+  // assume it is of the form …/bin/gvedit[.exe] and construct
+  // …/share/graphviz/gvedit
+
+  size_t slash = gvedit_exe.rfind(PATH_SEPARATOR);
+  if (slash == std::string::npos) {
+    errout << "no path separator in path to self, " << gvedit_exe.c_str()
+           << '\n';
+    return "";
+  }
+
+  std::string bin = gvedit_exe.substr(0, slash);
+  slash = bin.rfind(PATH_SEPARATOR);
+  if (slash == std::string::npos) {
+    errout << "no path separator in directory containing self, "
+           << bin.c_str() << '\n';
+    return "";
+  }
+
+  std::string install_prefix = bin.substr(0, slash);
+
+  return install_prefix + PATH_SEPARATOR + "share" + PATH_SEPARATOR + "graphviz"
+    + PATH_SEPARATOR + "gvedit";
+}
  
 bool loadAttrs(const QString fileName, QComboBox * cbNameG,
               QComboBox * cbNameN, QComboBox * cbNameE)
@@ -117,13 +240,14 @@ CFrmSettings::CFrmSettings()
     graph = nullptr;
     activeWindow = nullptr;
     QString path;
+    char *s = NULL;
 #ifndef _WIN32
-    char *s = getenv("GVEDIT_PATH");
+    s = getenv("GVEDIT_PATH");
+#endif
     if (s)
        path = s;
     else
-       path = GVEDIT_DATADIR;
-#endif
+       path = QString::fromStdString(find_share());
 
     connect(WIDGET(QPushButton, pbAdd), SIGNAL(clicked()), this,
            SLOT(addSlot()));
@@ -146,21 +270,10 @@ CFrmSettings::CFrmSettings()
            this, SLOT(scopeChangedSlot(int)));
     scopeChangedSlot(0);
 
-
-#ifndef _WIN32
-    loadAttrs(path + "/attrs.txt", WIDGET(QComboBox, cbNameG),
-             WIDGET(QComboBox, cbNameN), WIDGET(QComboBox, cbNameE));
-#else
-    if (loadAttrs("../share/graphviz/gvedit/attributes.txt",
-             WIDGET(QComboBox, cbNameG), WIDGET(QComboBox, cbNameN),
-             WIDGET(QComboBox, cbNameE))) {
-       path = findAttrFile();
-       if (!path.isEmpty())
-           loadAttrs(path,
-             WIDGET(QComboBox, cbNameG), WIDGET(QComboBox, cbNameN),
-             WIDGET(QComboBox, cbNameE));
+    if (path != "") {
+       loadAttrs(path + "/attrs.txt", WIDGET(QComboBox, cbNameG),
+                 WIDGET(QComboBox, cbNameN), WIDGET(QComboBox, cbNameE));
     }
-#endif
     setWindowIcon(QIcon(":/images/icon.png"));
 }