Graphviz 16.1.1~dev.20260906.1627
Loading...
Searching...
No Matches
gvconfig.c
Go to the documentation of this file.
1/*************************************************************************
2 * Copyright (c) 2011 AT&T Intellectual Property
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v2.0
5 * which accompanies this distribution, and is available at
6 * https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.html
7 *
8 * Contributors: Details at https://graphviz.org
9 *************************************************************************/
10
11#include "config.h"
12
13#ifndef _GNU_SOURCE
14#define _GNU_SOURCE 1
15#endif
16
17#include <assert.h>
18#include <gvc/gvconfig.h>
19#include <stdatomic.h>
20#include <stdbool.h>
21#include <stdio.h>
22#include <stdlib.h>
23#include <string.h>
24#include <unistd.h>
25#include <util/agxbuf.h>
26#include <util/alloc.h>
27#include <util/exit.h>
28#include <util/gv_ctype.h>
29#include <util/gv_fopen.h>
30#include <util/list.h>
31#include <util/path.h>
32#include <util/startswith.h>
33
34#ifdef ENABLE_LTDL
35#ifdef HAVE_DL_ITERATE_PHDR
36#include <link.h>
37#endif
38#include <sys/types.h>
39#ifdef _WIN32
40#include <windows.h>
41#define GLOB_NOMATCH 3 /* No matches found. */
42#define GLOB_NOSORT 4
43typedef struct {
44 size_t gl_pathc; /* count of total paths so far */
45 char **gl_pathv; /* list of paths matching pattern */
46} glob_t;
47static void globfree(glob_t *pglob);
48static int glob(GVC_t *gvc, char *, int, int (*errfunc)(const char *, int),
49 glob_t *);
50#else
51#include <glob.h>
52#endif
53#include <sys/stat.h>
54#endif
55
56#ifdef __APPLE__
57#include <mach-o/dyld.h>
58#endif
59
60#include <common/const.h>
61#include <common/types.h>
62
63#include <gvc/gvcint.h>
64#include <gvc/gvcjob.h>
65#include <gvc/gvcproc.h>
66#include <gvc/gvplugin.h>
67
68/* FIXME */
69extern void textfont_dict_open(GVC_t *gvc);
70
71/*
72 A config for gvrender is a text file containing a
73 list of plugin libraries and their capabilities using a tcl-like
74 syntax
75
76 Lines beginning with '#' are ignored as comments
77
78 Blank lines are allowed and ignored.
79
80 plugin_library_path packagename {
81 plugin_api {
82 plugin_type plugin_quality
83 ...
84 }
85 ...
86 ...
87
88 e.g.
89
90 /usr/lib/graphviz/libgvplugin_cairo.so cairo {renderer {x 0 png 10 ps
91 -10}} /usr/lib/graphviz/libgvplugin_gd.so gd {renderer {png 0 gif 0 jpg 0}}
92
93 Internally the config is maintained as lists of plugin_types for each
94 plugin_api. If multiple plugins of the same type are found then the highest
95 quality wins. If equal quality then the last-one-installed wins (thus giving
96 preference to external plugins over internal builtins).
97
98 */
99
101 const char *package_path,
102 const char *name) {
103 gvplugin_package_t *package = gv_alloc(sizeof(gvplugin_package_t));
104 package->path = package_path ? gv_strdup(package_path) : NULL;
105 package->name = gv_strdup(name);
106 package->next = gvc->packages;
107 gvc->packages = package;
108 return package;
109}
110
111#ifdef ENABLE_LTDL
112/*
113 separator - consume all non-token characters until next token. This includes:
114 comments: '#' ... '\n'
115 nesting: '{'
116 unnesting: '}'
117 whitespace: ' ','\t','\n'
118
119 *nest is changed according to nesting/unnesting processed
120 */
121static void separator(int *nest, char **tokens) {
122 char c, *s;
123
124 s = *tokens;
125 while ((c = *s)) {
126 /* #->eol = comment */
127 if (c == '#') {
128 s++;
129 while ((c = *s)) {
130 s++;
131 if (c == '\n')
132 break;
133 }
134 continue;
135 }
136 if (c == '{') {
137 (*nest)++;
138 s++;
139 continue;
140 }
141 if (c == '}') {
142 (*nest)--;
143 s++;
144 continue;
145 }
146 if (c == ' ' || c == '\n' || c == '\t') {
147 s++;
148 continue;
149 }
150 break;
151 }
152 *tokens = s;
153}
154
155/*
156 token - capture all characters until next separator, then consume separator,
157 return captured token, leave **tokens pointing to next token.
158 */
159static char *token(int *nest, char **tokens) {
160 char c, *s, *t;
161
162 s = t = *tokens;
163 while ((c = *s)) {
164 if (c == '#' || c == ' ' || c == '\t' || c == '\n' || c == '{' || c == '}')
165 break;
166 s++;
167 }
168 *tokens = s;
169 separator(nest, tokens);
170 *s = '\0';
171 return t;
172}
173
174static int gvconfig_plugin_install_from_config(GVC_t *gvc, char *s) {
175 int nest = 0;
176
177 separator(&nest, &s);
178 while (*s) {
179 char *const package_path = token(&nest, &s);
180 char *const name = nest == 0 ? token(&nest, &s) : "x";
181 gvplugin_package_t *const package =
182 gvplugin_package_record(gvc, package_path, name);
183 do {
184 const char *api = token(&nest, &s);
185 const api_t gv_api = gvplugin_api(api);
186 if (gv_api == (api_t)-1) {
187 agerrorf("config error: %s %s not found\n", package_path, api);
188 return 0;
189 }
190 do {
191 if (nest == 2) {
192 const char *const type = token(&nest, &s);
193 const int quality = nest == 2 ? atoi(token(&nest, &s)) : 0;
194 bool rc = gvplugin_install(gvc, gv_api, type, quality, package, NULL);
195 if (!rc) {
196 agerrorf("config error: %s %s %s\n", package_path, api, type);
197 return 0;
198 }
199 }
200 } while (nest == 2);
201 } while (nest == 1);
202 }
203 return 1;
204}
205#endif
206
208 gvplugin_library_t *library) {
211 gvplugin_package_t *package;
212 int i;
213
214 package = gvplugin_package_record(gvc, package_path, library->packagename);
215 for (apis = library->apis; (types = apis->types); apis++) {
216 for (i = 0; types[i].type; i++) {
217 gvplugin_install(gvc, apis->api, types[i].type, types[i].quality, package,
218 &types[i]);
219 }
220 }
221}
222
224 const lt_symlist_t *s;
225 const char *name;
226
227 if (gvc->common.builtins == NULL)
228 return;
229
230 for (s = gvc->common.builtins; (name = s->name); s++)
231 if (name[0] == 'g' && strstr(name, "_LTX_library"))
233}
234
235#ifdef ENABLE_LTDL
236static void gvconfig_write_library_config(GVC_t *gvc, char *lib_path,
237 gvplugin_library_t *library,
238 FILE *f) {
240
241 fprintf(f, "%s %s {\n", lib_path, library->packagename);
242 for (gvplugin_api_t *apis = library->apis; (types = apis->types); apis++) {
243 fprintf(f, "\t%s {\n", gvplugin_api_name(apis->api));
244 for (size_t i = 0; types[i].type; i++) {
245 /* verify that dependencies are available */
246 if (!gvplugin_load(gvc, apis->api, types[i].type,
247 gvc->common.verbose > 0 ? f : NULL))
248 fprintf(f, "#FAILS");
249 fprintf(f, "\t\t%s %d\n", types[i].type, types[i].quality);
250 }
251 fputs("\t}\n", f);
252 }
253 fputs("}\n", f);
254}
255
256#define BSZ 1024
257#define DOTLIBS "/.libs"
258
259#ifdef HAVE_DL_ITERATE_PHDR
260static int line_callback(struct dl_phdr_info *info, size_t size, void *xb) {
261 const char *p = info->dlpi_name;
262 const char *const tmp = strstr(p, "/libgvc.");
263 (void)size;
264 if (tmp) {
265 const char *slash;
266 for (slash = tmp - 1; *slash != '/'; --slash)
267 ;
268 /* Check for real /lib dir. Don't accept pre-install /.libs */
269 if (strncmp(slash, DOTLIBS, (size_t)(tmp - slash)) != 0) {
270 agxbclear(xb);
271 // plugins are in "graphviz" subdirectory
272 agxbprint(xb, "%.*s/graphviz", (int)(tmp - p), p);
273 return 1;
274 }
275 }
276 return 0;
277}
278#endif
279
280char *gvconfig_libdir(GVC_t *gvc) {
281 agxbuf libdir = {0};
282 static atomic_flag dirShown;
283
284 const char *const gvbindir = getenv("GVBINDIR");
285 if (gvbindir != NULL) {
286 agxbput(&libdir, gvbindir);
287 } else {
288#ifdef _WIN32
289 int r;
290 char *s;
291
292 MEMORY_BASIC_INFORMATION mbi;
293 if (VirtualQuery(&gvconfig_libdir, &mbi, sizeof(mbi)) == 0) {
294 agerrorf("failed to get handle for executable.\n");
295 return 0;
296 }
297 {
298 char line[BSZ] = {0};
299 r = GetModuleFileName((HMODULE)mbi.AllocationBase, line, BSZ);
300 if (!r || (r == BSZ)) {
301 agerrorf("failed to get path for executable.\n");
302 return 0;
303 }
304 s = strrchr(line, '\\');
305 if (!s) {
306 agerrorf("no slash in path %s.\n", line);
307 return 0;
308 }
309 agxbput_n(&libdir, line, (size_t)(s - line));
310 }
311#else
312 agxbput(&libdir, GVLIBDIR);
313#ifdef __APPLE__
314 uint32_t i, c = _dyld_image_count();
315 size_t ind;
316 for (i = 0; i < c; ++i) {
317 const char *p = _dyld_get_image_name(i);
318 const char *tmp = strstr(p, "/libgvc.");
319 if (tmp) {
320 if (tmp > p) {
321 /* Check for real /lib dir. Don't accept pre-install /.libs */
322 const char *s = tmp - 1;
323 /* back up to previous slash (or head of string) */
324 while (*s != '/' && s > p)
325 s--;
326 if (startswith(s, DOTLIBS))
327 continue;
328 }
329
330 ind = tmp - p; // byte offset
331 /* plugins are in "graphviz" subdirectory */
332 agxbclear(&libdir);
333 agxbprint(&libdir, "%.*s/graphviz", (int)ind, p);
334 break;
335 }
336 }
337#elif defined(HAVE_DL_ITERATE_PHDR)
338 dl_iterate_phdr(line_callback, &libdir);
339#else
340 FILE *f = gv_fopen("/proc/self/maps", "r");
341 if (f) {
342 while (!feof(f)) {
343 char line[BSZ] = {0};
344 if (!fgets(line, sizeof(line), f))
345 continue;
346 if (!strstr(line, " r-xp "))
347 continue;
348 char *p = strchr(line, '/');
349 if (!p)
350 continue;
351 char *tmp = strstr(p, "/libgvc.");
352 if (tmp) {
353 *tmp = 0;
354 /* Check for real /lib dir. Don't accept pre-install /.libs */
355 if (strcmp(strrchr(p, '/'), DOTLIBS) == 0)
356 continue;
357 /* plugins are in "graphviz" subdirectory */
358 agxbclear(&libdir);
359 agxbprint(&libdir, "%s/graphviz", p);
360 break;
361 }
362 }
363 fclose(f);
364 }
365#endif
366#endif
367 }
368 char *const dir = agxbdisown(&libdir);
369 if (gvc->common.verbose && !atomic_flag_test_and_set(&dirShown)) {
370 fprintf(stderr, "libdir = \"%s\"\n", dir);
371 }
372 return dir;
373}
374#endif
375
376#ifdef ENABLE_LTDL
377// does this path look like a Graphviz plugin of our version?
378static bool is_plugin(const char *filepath) {
379
380 if (filepath == NULL) {
381 return false;
382 }
383
384 // shared library suffix to strip before looking for version number
385#if defined(DARWIN_DYLIB)
386 static const char SUFFIX[] = ".dylib";
387#elif defined(__MINGW32__) || defined(__CYGWIN__) || defined(_WIN32)
388 static const char SUFFIX[] = ".dll";
389#else
390 static const char SUFFIX[] = "";
391#endif
392
393 // does this filename end with the expected suffix?
394 size_t len = strlen(filepath);
395 if (len < strlen(SUFFIX) ||
396 strcmp(filepath + len - strlen(SUFFIX), SUFFIX) != 0) {
397 return false;
398 }
399 len -= strlen(SUFFIX);
400
401#if defined(_WIN32) && !defined(__MINGW32__) && !defined(__CYGWIN__)
402 // Windows libraries do not have a version in the filename
403
404#elif defined(GVPLUGIN_VERSION)
405// turn GVPLUGIN_VERSION into a string
406#define STRINGIZE_(x) #x
407#define STRINGIZE(x) STRINGIZE_(x)
408 static const char VERSION[] = STRINGIZE(GVPLUGIN_VERSION);
409#undef STRINGIZE
410#undef STRINGIZE_
411
412 // does this filename contain the expected version?
413 if (len < strlen(VERSION) ||
414 !startswith(filepath + len - strlen(VERSION), VERSION)) {
415 return false;
416 }
417 len -= strlen(VERSION);
418
419#else
420 // does this filename have a version?
421 if (len == 0 || !gv_isdigit(filepath[len - 1])) {
422 return false;
423 }
424 while (len > 0 && gv_isdigit(filepath[len - 1])) {
425 --len;
426 }
427
428#endif
429
430 // ensure the remainder conforms to what we expect of a shared library
431#if defined(DARWIN_DYLIB)
432 if (len < 2 || gv_isdigit(filepath[len - 2]) || filepath[len - 1] != '.') {
433 return false;
434 }
435#elif defined(__MINGW32__) || defined(__CYGWIN__)
436 if (len < 2 || gv_isdigit(filepath[len - 2]) || filepath[len - 1] != '-') {
437 return false;
438 }
439#elif defined(_WIN32)
440 if (len < 1 || gv_isdigit(filepath[len - 1])) {
441 return false;
442 }
443#else
444 static const char SO[] = ".so.";
445 if (len < strlen(SO) || !startswith(filepath + len - strlen(SO), SO)) {
446 return false;
447 }
448#endif
449
450 return true;
451}
452
453static void config_rescan(GVC_t *gvc, char *config_path) {
454 FILE *f = NULL;
455 glob_t globbuf;
456 char *libdir;
457 int rc;
458 gvplugin_library_t *library;
459#if defined(DARWIN_DYLIB)
460 char *plugin_glob = "libgvplugin_*";
461#elif defined(__MINGW32__)
462 char *plugin_glob = "libgvplugin_*";
463#elif defined(__CYGWIN__)
464 char *plugin_glob = "cyggvplugin_*";
465#elif defined(_WIN32)
466 char *plugin_glob = "gvplugin_*";
467#else
468 char *plugin_glob = "libgvplugin_*";
469#endif
470
471 if (config_path) {
472 f = gv_fopen(config_path, "w");
473 if (!f) {
474 agerrorf("failed to open %s for write.\n", config_path);
475 graphviz_exit(1);
476 }
477
478 fprintf(f,
479 "# This file was generated by \"dot -c\" at time of install.\n\n");
480 fprintf(f, "# You may temporarily disable a plugin by removing or "
481 "commenting out\n");
482 fprintf(f, "# a line in this file, or you can modify its \"quality\" value "
483 "to affect\n");
484 fprintf(f, "# default plugin selection.\n\n");
485 fprintf(f, "# Manual edits to this file **will be lost** on upgrade.\n\n");
486 }
487
488 libdir = gvconfig_libdir(gvc);
489
490 agxbuf config_glob = {0};
491 agxbprint(&config_glob, "%s%c%s", libdir, PATH_SEPARATOR, plugin_glob);
492 free(libdir);
493
494 /* load all libraries even if can't save config */
495
496#if defined(_WIN32)
497 rc = glob(gvc, agxbuse(&config_glob), GLOB_NOSORT, NULL, &globbuf);
498#else
499 rc = glob(agxbuse(&config_glob), 0, NULL, &globbuf);
500#endif
501 if (rc == 0) {
502 for (size_t i = 0; i < globbuf.gl_pathc; i++) {
503 if (is_plugin(globbuf.gl_pathv[i])) {
504 library = gvplugin_library_load(gvc, globbuf.gl_pathv[i]);
505 if (library) {
506 gvconfig_plugin_install_from_library(gvc, globbuf.gl_pathv[i],
507 library);
508 }
509 }
510 }
511 /* rescan with all libs loaded to check cross dependencies */
512 for (size_t i = 0; i < globbuf.gl_pathc; i++) {
513 if (is_plugin(globbuf.gl_pathv[i])) {
514 library = gvplugin_library_load(gvc, globbuf.gl_pathv[i]);
515 if (library) {
516 char *p = strrchr(globbuf.gl_pathv[i], PATH_SEPARATOR);
517 if (p)
518 p++;
519 if (f && p)
520 gvconfig_write_library_config(gvc, p, library, f);
521 }
522 }
523 }
524 }
525 globfree(&globbuf);
526 agxbfree(&config_glob);
527 if (f)
528 fclose(f);
529}
530#endif
531
532/*
533 gvconfig - parse a config file and install the identified plugins
534 */
535void gvconfig(GVC_t *gvc, bool rescan) {
536#ifdef ENABLE_LTDL
537 int rc;
538 struct stat config_st;
539 FILE *f = NULL;
540 char *config_text = NULL;
541 char *libdir;
542 char *config_file_name = GVPLUGIN_CONFIG_FILE;
543
544#endif
545
546 /* builtins don't require LTDL */
548
549 gvc->config_found = false;
550#ifdef ENABLE_LTDL
552 /* see if there are any new plugins */
553 libdir = gvconfig_libdir(gvc);
554 if (access(libdir, F_OK) < 0) {
555 /* if we fail to stat it then it probably doesn't exist so just fail
556 * silently */
557 free(libdir);
558 goto done;
559 }
560
561 if (!gvc->config_path) {
562 agxbuf xb = {0};
563 agxbprint(&xb, "%s%c%s", libdir, PATH_SEPARATOR, config_file_name);
564 gvc->config_path = agxbdisown(&xb);
565 }
566 free(libdir);
567
568 if (rescan) {
569 config_rescan(gvc, gvc->config_path);
570 gvc->config_found = true;
572 gvc); /* choose best available textlayout plugin immediately */
573 assert(gvc->textfont_dt != NULL &&
574 "config rescan performed without any prior first scan");
575 return;
576 }
577
578 /* load in the cached plugin library data */
579
580 rc = stat(gvc->config_path, &config_st);
581 if (rc == -1) {
582 /* silently return without setting gvc->config_found = TRUE */
583 goto done;
584 } else {
585 f = gv_fopen(gvc->config_path, "r");
586 if (!f) {
587 agerrorf("failed to open %s for read.\n", gvc->config_path);
588 return;
589 } else if (config_st.st_size == 0) {
590 agerrorf("%s is zero sized.\n", gvc->config_path);
591 } else {
592 config_text = gv_alloc((size_t)config_st.st_size + 1);
593 size_t sz = fread(config_text, 1, (size_t)config_st.st_size, f);
594 if (sz == 0) {
595 agerrorf("%s read error.\n", gvc->config_path);
596 } else {
597 gvc->config_found = true;
598 config_text[sz] = '\0'; /* make input into a null terminated string */
599 rc = gvconfig_plugin_install_from_config(gvc, config_text);
600 }
601 free(config_text);
602 }
603 if (f) {
604 fclose(f);
605 }
606 }
607 }
608done:
609#else
610 (void)rescan;
611#endif
613 gvc); /* choose best available textlayout plugin immediately */
614 textfont_dict_open(gvc); /* initialize font dict */
615}
616
617#ifdef ENABLE_LTDL
618#ifdef _WIN32
619
620/* Emulating windows glob */
621
622/* glob:
623 * Assumes only GLOB_NOSORT flag given. That is, there is no offset,
624 * and no previous call to glob.
625 */
626
627static int glob(GVC_t *gvc, char *pattern, int flags,
628 int (*errfunc)(const char *, int), glob_t *pglob) {
629 (void)flags;
630 (void)errfunc;
631
632 WIN32_FIND_DATA wfd;
633 LIST(char *) strs = {.dtor = LIST_DTOR_FREE};
634
635 pglob->gl_pathc = 0;
636 pglob->gl_pathv = NULL;
637
638 // the Windows APIs use '\'-separated directory components, but MinGW uses
639 // '/' as the canonical directory separator, so convert these
640#ifdef __MINGW32__
641 for (size_t i = 0; pattern[i] != '\0'; ++i) {
642 if (pattern[i] == '/') {
643 pattern[i] = '\\';
644 }
645 }
646#endif
647
648 HANDLE h = FindFirstFile(pattern, &wfd);
649 if (h == INVALID_HANDLE_VALUE)
650 return GLOB_NOMATCH;
651 char *const libdir = gvconfig_libdir(gvc);
652 do {
653 agxbuf entry = {0};
654 agxbprint(&entry, "%s%c%s", libdir, PATH_SEPARATOR, wfd.cFileName);
655 LIST_APPEND(&strs, agxbdisown(&entry));
656 } while (FindNextFile(h, &wfd));
657 LIST_APPEND(&strs, NULL);
658
659 LIST_DETACH(&strs, &pglob->gl_pathv, &pglob->gl_pathc);
660 free(libdir);
661
662 return 0;
663}
664
665static void globfree(glob_t *pglob) {
666 for (int i = 0; i < pglob->gl_pathc; i++)
667 free(pglob->gl_pathv[i]);
668 free(pglob->gl_pathv);
669}
670#endif
671#endif
Dynamically expanding string buffers.
static void agxbfree(agxbuf *xb)
free any malloced resources
Definition agxbuf.h:97
static size_t agxbput_n(agxbuf *xb, const char *s, size_t ssz)
append string s of length ssz into xb
Definition agxbuf.h:268
static int agxbprint(agxbuf *xb, const char *fmt,...)
Printf-style output to an agxbuf.
Definition agxbuf.h:252
static void agxbclear(agxbuf *xb)
resets pointer to data
Definition agxbuf.h:312
static WUR char * agxbuse(agxbuf *xb)
Definition agxbuf.h:325
static char * agxbdisown(agxbuf *xb)
Definition agxbuf.h:345
Memory allocation wrappers that exit on failure.
static char * gv_strdup(const char *original)
Definition alloc.h:101
static void * gv_alloc(size_t size)
Definition alloc.h:47
static NORETURN void graphviz_exit(int status)
Definition exit.h:23
expr procedure type
Definition exparse.y:208
static int flags
Definition gc.c:63
static double len(glCompPoint p)
Definition glutils.c:138
void free(void *)
node NULL
Definition grammar.y:181
void agerrorf(const char *fmt,...)
Definition agerror.c:167
static GVC_t * gvc
Definition gv.cpp:27
replacements for ctype.h functions
static bool gv_isdigit(int c)
Definition gv_ctype.h:41
FILE * gv_fopen(const char *filename, const char *mode)
Definition gv_fopen.c:34
wrapper around fopen for internal library usage
api_t
Definition gvcext.h:32
static gvplugin_package_t * gvplugin_package_record(GVC_t *gvc, const char *package_path, const char *name)
Definition gvconfig.c:100
void gvconfig(GVC_t *gvc, bool rescan)
Definition gvconfig.c:535
void textfont_dict_open(GVC_t *gvc)
Definition textspan.c:154
void gvconfig_plugin_install_from_library(GVC_t *gvc, char *package_path, gvplugin_library_t *library)
Definition gvconfig.c:207
static void gvconfig_plugin_install_builtins(GVC_t *gvc)
Definition gvconfig.c:223
gvplugin_library_t * gvplugin_library_load(GVC_t *gvc, const char *pathname)
Definition gvplugin.c:151
api_t gvplugin_api(const char *str)
Definition gvplugin.c:51
gvplugin_available_t * gvplugin_load(GVC_t *gvc, api_t api, const char *type, FILE *debug)
Definition gvplugin.c:252
int gvtextlayout_select(GVC_t *gvc)
char * gvplugin_api_name(api_t api)
Definition gvplugin.c:61
bool gvplugin_install(GVC_t *gvc, api_t api, const char *typestr, int quality, gvplugin_package_t *package, gvplugin_installed_t *typeptr)
Definition gvplugin.c:73
WUR char * gvconfig_libdir(GVC_t *gvc)
The return value is heap-allocated and should be freed by the caller.
agxbput(xb, staging)
static gvplugin_api_t apis[]
type-generic dynamically expanding list
#define LIST_DETACH(list, datap, sizep)
Definition list.h:427
#define LIST_DTOR_FREE
Definition list.h:70
#define LIST_APPEND(list,...)
Definition list.h:124
#define LIST(type)
Definition list.h:55
File system path helpers.
#define PATH_SEPARATOR
character for separating directory components in a file system path
Definition path.h:10
static bool startswith(const char *s, const char *prefix)
does the string s begin with the string prefix?
Definition startswith.h:11
const lt_symlist_t * builtins
Definition gvcommon.h:31
int demand_loading
Definition gvcommon.h:32
int verbose
Definition gvcommon.h:22
Definition gvcint.h:81
char * config_path
Definition gvcint.h:84
bool config_found
Definition gvcint.h:85
GVCOMMON_t common
Definition gvcint.h:82
gvplugin_package_t * packages
Definition gvcint.h:101
Dt_t * textfont_dt
Definition gvcint.h:108
gvplugin_installed_t * types
Definition gvplugin.h:53
ingroup plugin_api
Definition gvplugin.h:35
const char * type
Definition gvplugin.h:41
gvplugin_api_t * apis
Definition gvplugin.h:59
graphs, nodes and edges info: Agraphinfo_t, Agnodeinfo_t and Agedgeinfo_t
Definition grammar.c:90
char * name
Definition grammar.c:95