]> granicus.if.org Git - graphviz/commitdiff
fix installing example graphs during CPack; rewrite 'SMYRNA_PATH' discovery
authorMatthew Fernandez <matthew.fernandez@gmail.com>
Tue, 1 Nov 2022 03:26:33 +0000 (20:26 -0700)
committerMatthew Fernandez <matthew.fernandez@gmail.com>
Fri, 4 Nov 2022 00:01:04 +0000 (17:01 -0700)
This is effectively two commits in one. But there seems to be no way to separate
these changes without the parts individually failing
tests/test_tools.py::test_tools[smyrna]. Smyrna auxiliary files were being
installed incorrectly prior to this commit, so installing them correctly and
fixing how Smyrna finds them needs to be done in a single change.

---

CMake: fix installing example graphs during CPack

When running `cpack`, instead of staging the Smyrna auxiliary files (share/**)
and the example graphs (graphs/**) in build-local directories, they would be
incorrectly staged into their final install paths. This was quite unexpected as
it would result in errors if you did not have permission to write to the install
directories or deleting+replacing files from a previous Graphviz installation if
you did.

This bug was introduced in 67f88eb86c9966daa4d8c3a6f25aee8cd35a046d as an
accidental side effect of enabling Smyrna in the CMake build system. A close
re-reading of the CPack¹ and `GNUInstallDirs`² docs suggests that absolute paths
should never be used in `install` rules:

  `CMAKE_INSTALL_<dir>`
    Destination for files of a given type. This value may be passed to the
    `DESTINATION` options of `install()` commands for the corresponding file
    type. It should typically be a path relative to the installation prefix so
    that it can be converted to an absolute path in a relocatable way (see
    `CMAKE_INSTALL_FULL_<dir>`). However, an absolute path is also allowed.

  `CMAKE_INSTALL_FULL_<dir>`
    The absolute path generated from the corresponding `CMAKE_INSTALL_<dir>`
    value. If the value is not already an absolute path, an absolute path is
    constructed typically by prepending the value of the `CMAKE_INSTALL_PREFIX`
    variable. However, there are some special cases as documented below.

This change brings the behavior on Linux in line with how CPack operates on
other platforms – the default value of `CMAKE_INSTALL_DATAROOTDIR` is `share`
which is the hard coded value set on non-Linux platforms.

----

smyrna: rewrite 'SMYRNA_PATH' discovery

This aligns Smyrna on other platforms with how Smyrna on Windows locates its
templates, examples etc. This resolves a problem where a build time path was
being baked into the Smyrna binary, preventing it from being relocatable.

The code for locating our own executable in this commit is adapted from public
domain source.³

This commit also lifts the 1024 character limit on path discovery in the Windows
branch of this logic. It now dynamically expands the target buffer until the
current executable name will fit.

Gitlab: fixes #2232
Reported-by: Magnus Jacobsson <magnus.jacobsson@berotec.se>
¹ https://cmake.org/cmake/help/book/mastering-cmake/chapter/Packaging%20With%20CPack.html
² https://cmake.org/cmake/help/latest/module/GNUInstallDirs.html
³ https://github.com/Smattr/clink/blob/main/clink/src/find_me.c as of commit
  8cadfc49a74e429fa69afdc460cb2b0662d81260

CHANGELOG.md
CMakeLists.txt
cmd/smyrna/CMakeLists.txt
cmd/smyrna/Makefile.am
cmd/smyrna/main.c

index 1b8208fffb5f9e8e10033178e82ded5fce8e2609..dc55cfb99b02bb6b6e93f1df71a00c899eecaaf4 100644 (file)
@@ -22,6 +22,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 - Failure of arrowhead and arrowtail to respect penwidth #372 \
   Fixed also for the `diamond` [edge arrow shape](https://graphviz.org/doc/info/arrows.html#primitive-shapes).
+- The CMake build system no longer uses the final install location as the
+  staging area for example graphs and templates during `cpack`. This bug was
+  introduced in Graphviz 4.0.0. #2232
 
 ## [7.0.0] – 2022-10-22
 
index bc3f5e96c1965b03ff7fb0bd29c07fe1f25a266c..5d708e288a8c813a7d2aa74b5f64954eb0926e52 100644 (file)
@@ -64,7 +64,7 @@ if(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
   set(MAN_INSTALL_DIR     "${CMAKE_INSTALL_MANDIR}")
   set(libdir              "${CMAKE_INSTALL_FULL_LIBDIR}")
   set(includedir          "${CMAKE_INSTALL_FULL_INCLUDEDIR}")
-  set(DATA_INSTALL_DIR    "${CMAKE_INSTALL_FULL_DATAROOTDIR}")
+  set(DATA_INSTALL_DIR    "${CMAKE_INSTALL_DATAROOTDIR}")
 else()
   set(BINARY_INSTALL_DIR  bin)
   set(LIBRARY_INSTALL_DIR lib)
index 1b7597b25ed7533ad63267c0d56332a7769dd676..babe6eb1425b72e2165c9646efa1aca66c468aed 100644 (file)
@@ -12,8 +12,6 @@ if(with_smyrna)
   pkg_check_modules(GTS REQUIRED gts)
   pkg_check_modules(XRENDER REQUIRED xrender)
 
-  add_definitions(-DSMYRNA_PATH="${DATA_INSTALL_DIR}/graphviz/smyrna")
-
   add_executable(smyrna
     arcball.h
     draw.h
index 442e0adc2c53585d0d0528a211cf18a30ddb9c2d..01745993445bb1aded5a6860be9d756ac87fa508 100644 (file)
@@ -1,7 +1,6 @@
 ## Process this file with automake to produce Makefile.in
 
 AM_CPPFLAGS = \
-       -DSMYRNA_PATH='"$(pkgdatadir)/smyrna"' \
        -I$(top_srcdir)/lib \
        -I$(top_srcdir)/lib/cgraph \
        -I$(top_srcdir)/lib/cdt \
index d6c04581948cb66cf92a79a3472a974c35010560..b89a5ba6a289c533e95cfe4b4f864eff274009bf 100644 (file)
 #include "glutrender.h"
 
 #include <getopt.h>
-#include <stddef.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef __APPLE__
+#include <mach-o/dyld.h>
+#endif
+
+#ifdef __FreeBSD__
+#include <sys/sysctl.h>
+#include <sys/types.h>
+#endif
+
+#if !defined(__APPLE__) && !defined(_WIN32)
+#include <unistd.h>
+#endif
 
 static char *smyrnaDir;                /* path to directory containin smyrna data files */
 static char *smyrnaGlade;
@@ -168,49 +184,153 @@ static void windowedMode(int argc, char *argv[])
        gtk_main();
 }
 
+#if !defined(__APPLE__) && !defined(_WIN32)
+/// read the given symlink in the /proc file system
+static char *read_proc(const char *path) {
 
+  char buf[PATH_MAX + 1] = {0};
+  if (readlink(path, buf, sizeof(buf)) < 0)
+    return NULL;
 
+  // was the path too long?
+  if (buf[sizeof(buf) - 1] != '\0')
+    return NULL;
 
-int main(int argc, char *argv[])
-{
-    smyrnaDir = getenv("SMYRNA_PATH");
-    if (!smyrnaDir) {
-#ifdef _WIN32
-#define BSZ 1024
-#define SMYRNA "\\share\\graphviz\\smyrna"
+  return gv_strdup(buf);
+}
+#endif
 
-       int r;
-       char line[BSZ];
-       char* s;
+/// find an absolute path to the current executable
+static char *find_me(void) {
 
-       r = GetModuleFileName(NULL, line, BSZ);
-       if (!r || (r == BSZ)) {
-           fprintf (stderr,"failed to get path for executable.\n");
-           graphviz_exit(1);
-       }
+  // 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);
 
-       /* line contains path to smyrna: "$GVROOT\bin\smyrna.exe" 
-        * We want to obtain $GVROOT. We find the second rightmost '\'
-        * and store a '\0' there.
-        */
-       s = strrchr(line,'\\');
-       if (!s) {
-           fprintf (stderr,"no backslash in path %s.\n", line);
-           graphviz_exit(1);
-       }
-       while ((s != line) && (*(--s) != '\\')) ;
-       if (s == line) {
-           fprintf (stderr,"no backslash in path %s.\n", line);
-           graphviz_exit(1);
-       }
-       *s = '\0';
+    path = gv_alloc(buf_size);
+
+    // retrieve the actual path
+    if (_NSGetExecutablePath(path, &buf_size) < 0) {
+      fprintf(stderr, "failed to get path for executable.\n");
+      graphviz_exit(EXIT_FAILURE);
+    }
 
-       smyrnaDir = gv_calloc(strlen(line) + sizeof(SMYRNA), sizeof(char));
-       strcpy (smyrnaDir, line);
-       strcat(smyrnaDir, SMYRNA);
+    // try to resolve any levels of symlinks if possible
+    while (true) {
+      char buf[PATH_MAX + 1] = {0};
+      if (readlink(path, buf, sizeof(buf)) < 0)
+        return path;
+
+      free(path);
+      path = gv_strdup(buf);
+    }
+  }
+#elif defined(_WIN32)
+  {
+    char *path = NULL;
+    size_t path_size = 0;
+    int rc = 0;
+
+    do {
+      size_t size = path_size == 0 ? 1024 : (path_size * 2);
+      path = gv_realloc(path, path_size, size);
+      path_size = size;
+
+      rc = GetModuleFileName(NULL, path, path_size);
+      if (rc == 0) {
+        fprintf(stderr, "failed to get path for executable.\n");
+        graphviz_exit(EXIT_FAILURE);
+      }
+
+    } while (rc == path_size);
+
+    return path;
+  }
+#else
+
+  // Linux
+  char *path = read_proc("/proc/self/exe");
+  if (path != NULL)
+    return path;
+
+  // DragonFly BSD, FreeBSD
+  path = read_proc("/proc/curproc/file");
+  if (path != NULL)
+    return path;
+
+  // NetBSD
+  path = read_proc("/proc/curproc/exe");
+  if (path != NULL)
+    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]);
+    char buf[PATH_MAX + 1] = {0};
+    size_t buf_size = sizeof(buf);
+    if (sysctl(mib, MIB_LENGTH, buf, &buf_size, NULL, 0) == 0)
+      return gv_strdup(buf);
+  }
+#endif
+#endif
+
+  fprintf(stderr, "failed to get path for executable.\n");
+  graphviz_exit(EXIT_FAILURE);
+}
+
+/// find an absolute path to where Smyrna auxiliary files are stored
+static char *find_share(void) {
+
+#ifdef _WIN32
+  const char PATH_SEPARATOR = '\\';
 #else
-       smyrnaDir = SMYRNA_PATH;
+  const char PATH_SEPARATOR = '/';
 #endif
+
+  // find the path to the `smyrna` binary
+  char *smyrna_exe = find_me();
+
+  // assume it is of the form …/bin/smyrna[.exe] and construct
+  // …/share/graphviz/smyrna
+
+  char *slash = strrchr(smyrna_exe, PATH_SEPARATOR);
+  if (slash == NULL) {
+    fprintf(stderr, "no path separator in path to self, %s\n", smyrna_exe);
+    free(smyrna_exe);
+    graphviz_exit(EXIT_FAILURE);
+  }
+
+  *slash = '\0';
+  slash = strrchr(smyrna_exe, PATH_SEPARATOR);
+  if (slash == NULL) {
+    fprintf(stderr, "no path separator in directory containing self, %s\n",
+            smyrna_exe);
+    free(smyrna_exe);
+    graphviz_exit(EXIT_FAILURE);
+  }
+
+  *slash = '\0';
+  size_t share_len = strlen(smyrna_exe) + strlen("/share/graphviz/smyrna") + 1;
+  char *share = gv_alloc(share_len);
+  snprintf(share, share_len, "%s%cshare%cgraphviz%csmyrna", smyrna_exe,
+           PATH_SEPARATOR, PATH_SEPARATOR, PATH_SEPARATOR);
+  free(smyrna_exe);
+
+  return share;
+}
+
+int main(int argc, char *argv[])
+{
+    smyrnaDir = getenv("SMYRNA_PATH");
+    if (!smyrnaDir) {
+       smyrnaDir = find_share();
     }
     load_attributes();