591 lines
15 KiB
C
591 lines
15 KiB
C
/*-
|
|
* Copyright (c) 2010-2014 Juan Romero Pardines.
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdbool.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <stdarg.h>
|
|
#include <unistd.h>
|
|
#include <inttypes.h>
|
|
#include <assert.h>
|
|
|
|
#include <xbps.h>
|
|
#include "queue.h"
|
|
|
|
#ifdef __clang__
|
|
#pragma clang diagnostic ignored "-Wformat-nonliteral"
|
|
#endif
|
|
|
|
#ifndef __arraycount
|
|
# define __arraycount(a) (sizeof(a) / sizeof(*(a)))
|
|
#endif
|
|
|
|
#define _DGRAPH_CFFILE "xbps-dgraph.conf"
|
|
|
|
/*
|
|
* Properties written to default configuration file.
|
|
*/
|
|
struct defprops {
|
|
const char *sect;
|
|
const char *prop;
|
|
const char *val;
|
|
} dfprops[] = {
|
|
{ .sect = "graph", .prop = "rankdir", .val = "LR" },
|
|
{ .sect = "graph", .prop = "ranksep", .val = ".1" },
|
|
{ .sect = "graph", .prop = "nodesep", .val = ".1" },
|
|
{ .sect = "graph", .prop = "pack", .val = "true" },
|
|
{ .sect = "graph", .prop = "splines", .val = "polyline" },
|
|
{ .sect = "graph", .prop = "ratio", .val = "compress" },
|
|
|
|
{ .sect = "edge", .prop = "constraint", .val = "true" },
|
|
{ .sect = "edge", .prop = "arrowhead", .val = "vee" },
|
|
{ .sect = "edge", .prop = "arrowsize", .val = ".4" },
|
|
{ .sect = "edge", .prop = "fontname", .val = "Sans" },
|
|
{ .sect = "edge", .prop = "fontsize", .val = "8" },
|
|
|
|
{ .sect = "node", .prop = "height", .val = ".1" },
|
|
{ .sect = "node", .prop = "width", .val = ".1" },
|
|
{ .sect = "node", .prop = "shape", .val = "box" },
|
|
{ .sect = "node", .prop = "fontname", .val = "Sans" },
|
|
{ .sect = "node", .prop = "fontsize", .val = "8" },
|
|
|
|
{ .sect = "node-sub", .prop = "main-style", .val = "filled" },
|
|
{ .sect = "node-sub", .prop = "main-fillcolor", .val = "darksalmon" },
|
|
{ .sect = "node-sub", .prop = "style", .val = "filled" },
|
|
{ .sect = "node-sub", .prop = "fillcolor", .val = "yellowgreen" },
|
|
{ .sect = "node-sub", .prop = "opt-style", .val = "filled" },
|
|
{ .sect = "node-sub", .prop = "opt-fillcolor", .val = "grey" }
|
|
};
|
|
|
|
struct pkgdep {
|
|
SLIST_ENTRY(pkgdep) pkgdep_entries;
|
|
unsigned int idx;
|
|
const char *pkgver;
|
|
};
|
|
|
|
static xbps_dictionary_t confd;
|
|
static SLIST_HEAD(pkgdep_head, pkgdep) pkgdep_list =
|
|
SLIST_HEAD_INITIALIZER(pkgdep_list);
|
|
|
|
static void __attribute__((noreturn))
|
|
die(const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
int save_errno = errno;
|
|
|
|
va_start(ap, fmt);
|
|
fprintf(stderr, "xbps-dgraph: ERROR ");
|
|
vfprintf(stderr, fmt, ap);
|
|
if (save_errno)
|
|
fprintf(stderr, " (%s)\n", strerror(save_errno));
|
|
else
|
|
fprintf(stderr, "\n");
|
|
va_end(ap);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
static void __attribute__((noreturn))
|
|
usage(void)
|
|
{
|
|
fprintf(stdout,
|
|
"Usage: xbps-dgraph [options] <pkgname>\n\n"
|
|
" Options\n"
|
|
" -c\t\tPath to configuration file\n"
|
|
" -d\t\tDebug mode shown to stderr\n"
|
|
" -g\t\tGenerate a default config file\n"
|
|
" -R\t\tEnable repository mode\n"
|
|
" -f\t\tGenerate a full dependency graph\n"
|
|
" -r\t\t<rootdir>\n\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
static const char *
|
|
convert_proptype_to_string(xbps_object_t obj)
|
|
{
|
|
switch (xbps_object_type(obj)) {
|
|
case XBPS_TYPE_ARRAY:
|
|
return "array";
|
|
case XBPS_TYPE_BOOL:
|
|
return "bool";
|
|
case XBPS_TYPE_DICTIONARY:
|
|
return "dictionary";
|
|
case XBPS_TYPE_DICT_KEYSYM:
|
|
return "dictionary key";
|
|
case XBPS_TYPE_NUMBER:
|
|
return "integer";
|
|
case XBPS_TYPE_STRING:
|
|
return "string";
|
|
case XBPS_TYPE_DATA:
|
|
return "data";
|
|
default:
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
static xbps_dictionary_t
|
|
create_defconf(void)
|
|
{
|
|
xbps_dictionary_t d, d2;
|
|
struct defprops *dfp;
|
|
|
|
d = xbps_dictionary_create();
|
|
|
|
d2 = xbps_dictionary_create();
|
|
xbps_dictionary_set(d, "graph", d2);
|
|
xbps_object_release(d2);
|
|
|
|
d2 = xbps_dictionary_create();
|
|
xbps_dictionary_set(d, "edge", d2);
|
|
xbps_object_release(d2);
|
|
|
|
d2 = xbps_dictionary_create();
|
|
xbps_dictionary_set(d, "node", d2);
|
|
xbps_object_release(d2);
|
|
|
|
d2 = xbps_dictionary_create();
|
|
xbps_dictionary_set(d, "node-sub", d2);
|
|
xbps_object_release(d2);
|
|
|
|
for (unsigned int i = 0; i < __arraycount(dfprops); i++) {
|
|
dfp = &dfprops[i];
|
|
d2 = xbps_dictionary_get(d, dfp->sect);
|
|
xbps_dictionary_set_cstring_nocopy(d2, dfp->prop, dfp->val);
|
|
}
|
|
|
|
return d;
|
|
}
|
|
|
|
static void
|
|
generate_conf_file(void)
|
|
{
|
|
xbps_dictionary_t d;
|
|
|
|
d = create_defconf();
|
|
if (xbps_dictionary_externalize_to_file(d, _DGRAPH_CFFILE) == false) {
|
|
xbps_object_release(d);
|
|
die("couldn't write conf_file to %s", _DGRAPH_CFFILE);
|
|
}
|
|
xbps_object_release(d);
|
|
printf("Wrote configuration file: %s\n", _DGRAPH_CFFILE);
|
|
}
|
|
|
|
static void
|
|
write_conf_property_on_stream(FILE *f, const char *section)
|
|
{
|
|
xbps_array_t allkeys, allkeys2;
|
|
xbps_dictionary_keysym_t dksym, dksym2;
|
|
xbps_object_t keyobj, keyobj2;
|
|
const char *cf_val, *keyname, *keyname2;
|
|
|
|
/*
|
|
* Iterate over the main dictionary.
|
|
*/
|
|
allkeys = xbps_dictionary_all_keys(confd);
|
|
for (unsigned int i = 0; i < xbps_array_count(allkeys); i++) {
|
|
dksym = xbps_array_get(allkeys, i);
|
|
keyname = xbps_dictionary_keysym_cstring_nocopy(dksym);
|
|
keyobj = xbps_dictionary_get_keysym(confd, dksym);
|
|
if (strcmp(keyname, section))
|
|
continue;
|
|
|
|
/*
|
|
* Iterate over the dictionary sections [edge/graph/node].
|
|
*/
|
|
allkeys2 = xbps_dictionary_all_keys(keyobj);
|
|
for (unsigned int x = 0; x < xbps_array_count(allkeys2); x++) {
|
|
dksym2 = xbps_array_get(allkeys2, x);
|
|
keyname2 = xbps_dictionary_keysym_cstring_nocopy(dksym2);
|
|
keyobj2 = xbps_dictionary_get_keysym(keyobj, dksym2);
|
|
|
|
cf_val = xbps_string_cstring_nocopy(keyobj2);
|
|
fprintf(f, "%s=\"%s\"", keyname2, cf_val);
|
|
if (x + 1 >= xbps_array_count(allkeys2))
|
|
continue;
|
|
|
|
fprintf(f, ",");
|
|
}
|
|
}
|
|
}
|
|
|
|
static char *
|
|
strip_dashes_from_key(const char *str)
|
|
{
|
|
char *p;
|
|
size_t i;
|
|
|
|
p = strdup(str);
|
|
if (p == NULL)
|
|
die("%s alloc p", __func__);
|
|
|
|
for (i = 0; i < strlen(p); i++) {
|
|
if (p[i] == '-')
|
|
p[i] = '_';
|
|
}
|
|
return p;
|
|
}
|
|
|
|
static void
|
|
parse_array_in_pkg_dictionary(FILE *f, xbps_dictionary_t plistd,
|
|
xbps_dictionary_t sub_confd,
|
|
xbps_array_t allkeys)
|
|
{
|
|
xbps_dictionary_keysym_t dksym;
|
|
xbps_object_t keyobj, sub_keyobj;
|
|
const char *tmpkeyname, *cfprop;
|
|
char *keyname;
|
|
|
|
for (unsigned int i = 0; i < xbps_array_count(allkeys); i++) {
|
|
dksym = xbps_array_get(allkeys, i);
|
|
tmpkeyname = xbps_dictionary_keysym_cstring_nocopy(dksym);
|
|
keyobj = xbps_dictionary_get_keysym(plistd, dksym);
|
|
keyname = strip_dashes_from_key(tmpkeyname);
|
|
|
|
fprintf(f, " main -> %s [label=\"%s\"];\n",
|
|
keyname, convert_proptype_to_string(keyobj));
|
|
|
|
/*
|
|
* Process array objects.
|
|
*/
|
|
xbps_dictionary_get_cstring_nocopy(sub_confd, "style", &cfprop);
|
|
if (xbps_object_type(keyobj) == XBPS_TYPE_ARRAY) {
|
|
for (unsigned int x = 0; x < xbps_array_count(keyobj); x++) {
|
|
sub_keyobj = xbps_array_get(keyobj, x);
|
|
if (xbps_object_type(sub_keyobj) == XBPS_TYPE_STRING) {
|
|
/*
|
|
* Process arrays of strings.
|
|
*/
|
|
fprintf(f, " %s -> %s_%u_string "
|
|
"[label=\"string\"];\n",
|
|
keyname, keyname, x);
|
|
xbps_dictionary_get_cstring_nocopy(sub_confd,
|
|
"style", &cfprop);
|
|
fprintf(f, " %s_%u_string [style=\"%s\",",
|
|
keyname, x, cfprop);
|
|
xbps_dictionary_get_cstring_nocopy(sub_confd,
|
|
"fillcolor", &cfprop);
|
|
fprintf(f, "fillcolor=\"%s\","
|
|
"label=\"%s\"];\n", cfprop,
|
|
xbps_string_cstring_nocopy(sub_keyobj));
|
|
}
|
|
}
|
|
free(keyname);
|
|
continue;
|
|
}
|
|
fprintf(f, " %s -> %s_value;\n", keyname, keyname);
|
|
xbps_dictionary_get_cstring_nocopy(sub_confd, "style", &cfprop);
|
|
fprintf(f, " %s_value [style=\"%s\",", keyname, cfprop);
|
|
xbps_dictionary_get_cstring_nocopy(sub_confd,
|
|
"fillcolor", &cfprop);
|
|
fprintf(f, "fillcolor=\"%s\"", cfprop);
|
|
|
|
/*
|
|
* Process all other object types...
|
|
*/
|
|
switch (xbps_object_type(keyobj)) {
|
|
case XBPS_TYPE_BOOL:
|
|
fprintf(f, ",label=\"%s\"",
|
|
xbps_bool_true(keyobj) ? "true" : "false");
|
|
break;
|
|
case XBPS_TYPE_DATA:
|
|
fprintf(f, ",label=\"%zu bytes\"", xbps_data_size(keyobj));
|
|
break;
|
|
case XBPS_TYPE_NUMBER:
|
|
fprintf(f, ",label=\"%"PRIu64" bytes\"",
|
|
xbps_number_unsigned_integer_value(keyobj));
|
|
break;
|
|
case XBPS_TYPE_STRING:
|
|
if (strcmp(keyname, "long_desc") == 0) {
|
|
/*
|
|
* Do not print this obj, too large!
|
|
*/
|
|
fprintf(f, ",label=\"...\"");
|
|
} else {
|
|
fprintf(f, ",label=\"%s\"",
|
|
xbps_string_cstring_nocopy(keyobj));
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
fprintf(f, "];\n");
|
|
free(keyname);
|
|
}
|
|
}
|
|
|
|
static void
|
|
process_fulldeptree(struct xbps_handle *xhp, FILE *f,
|
|
xbps_dictionary_t pkgd, xbps_array_t rdeps,
|
|
bool repomode)
|
|
{
|
|
xbps_array_t rpkgrdeps;
|
|
struct pkgdep *pd;
|
|
const char *pkgver;
|
|
unsigned int i, x;
|
|
|
|
xbps_dictionary_get_cstring_nocopy(pkgd, "pkgver", &pkgver);
|
|
|
|
for (i = 0; i < xbps_array_count(rdeps); i++) {
|
|
xbps_dictionary_t rpkgd;
|
|
const char *pkgdep;
|
|
unsigned int pkgidx = 0;
|
|
bool found = false;
|
|
|
|
xbps_array_get_cstring_nocopy(rdeps, i, &pkgdep);
|
|
SLIST_FOREACH(pd, &pkgdep_list, pkgdep_entries) {
|
|
if (strcmp(pd->pkgver, pkgdep) == 0) {
|
|
found = true;
|
|
break;
|
|
}
|
|
pkgidx++;
|
|
}
|
|
if (!found) {
|
|
pd = malloc(sizeof(*pd));
|
|
assert(pd);
|
|
pd->idx = pkgidx;
|
|
pd->pkgver = pkgdep;
|
|
SLIST_INSERT_HEAD(&pkgdep_list, pd, pkgdep_entries);
|
|
}
|
|
if (repomode) {
|
|
rpkgd = xbps_rpool_get_pkg(xhp, pkgdep);
|
|
} else {
|
|
rpkgd = xbps_pkgdb_get_pkg(xhp, pkgdep);
|
|
}
|
|
assert(rpkgd);
|
|
rpkgrdeps = xbps_dictionary_get(rpkgd, "run_depends");
|
|
for (x = 0; x < xbps_array_count(rpkgrdeps); x++) {
|
|
struct pkgdep *ppd;
|
|
const char *rpkgdep;
|
|
|
|
xbps_array_get_cstring_nocopy(rpkgrdeps, x, &rpkgdep);
|
|
SLIST_FOREACH(ppd, &pkgdep_list, pkgdep_entries) {
|
|
if (xbps_pkgpattern_match(ppd->pkgver, rpkgdep))
|
|
fprintf(f, "\t%u -> %u;\n", pkgidx, ppd->idx);
|
|
}
|
|
}
|
|
fprintf(f, "\t%u [label=\"%s\"", pkgidx, pkgdep);
|
|
if (repomode && xbps_pkgdb_get_pkg(xhp, pkgdep))
|
|
fprintf(f, ",style=\"filled\",fillcolor=\"yellowgreen\"");
|
|
|
|
fprintf(f, "]\n");
|
|
}
|
|
i = 0;
|
|
SLIST_FOREACH(pd, &pkgdep_list, pkgdep_entries)
|
|
++i;
|
|
|
|
fprintf(f, "\t%u [label=\"%s\",style=\"filled\",fillcolor=\"darksalmon\"];\n", i, pkgver);
|
|
rpkgrdeps = xbps_dictionary_get(pkgd, "run_depends");
|
|
for (x = 0; x < xbps_array_count(rpkgrdeps); x++) {
|
|
const char *rpkgdep;
|
|
|
|
xbps_array_get_cstring_nocopy(rpkgrdeps, x, &rpkgdep);
|
|
SLIST_FOREACH(pd, &pkgdep_list, pkgdep_entries) {
|
|
if (xbps_pkgpattern_match(pd->pkgver, rpkgdep))
|
|
fprintf(f, "\t%u -> %u;\n", i, pd->idx);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
create_dot_graph(struct xbps_handle *xhp,
|
|
FILE *f,
|
|
xbps_dictionary_t plistd,
|
|
bool repomode,
|
|
bool fulldepgraph)
|
|
{
|
|
xbps_dictionary_t sub_confd;
|
|
xbps_array_t allkeys, rdeps;
|
|
const char *pkgver, *cfprop;
|
|
|
|
xbps_dictionary_get_cstring_nocopy(plistd, "pkgver", &pkgver);
|
|
|
|
/*
|
|
* Start filling the output file...
|
|
*/
|
|
fprintf(f, "/* Graph created for %s by xbps-graph %s */\n\n",
|
|
pkgver, XBPS_RELVER);
|
|
fprintf(f, "digraph pkg_dictionary {\n");
|
|
|
|
/*
|
|
* Process the graph section in config file.
|
|
*/
|
|
fprintf(f, " graph [");
|
|
write_conf_property_on_stream(f, "graph");
|
|
if (fulldepgraph)
|
|
fprintf(f, ",label=\"[XBPS] %s full dependency graph "
|
|
"[%s]\"];\n", pkgver, repomode ? "repo" : "pkgdb");
|
|
else
|
|
fprintf(f, ",label=\"[XBPS] %s metadata properties\"];\n", pkgver);
|
|
|
|
/*
|
|
* Process the edge section in config file.
|
|
*/
|
|
fprintf(f, " edge [");
|
|
write_conf_property_on_stream(f, "edge");
|
|
fprintf(f, "];\n");
|
|
|
|
/*
|
|
* Process the node section in config file.
|
|
*/
|
|
fprintf(f, " node [");
|
|
write_conf_property_on_stream(f, "node");
|
|
fprintf(f, "];\n");
|
|
|
|
if (fulldepgraph) {
|
|
if (repomode) {
|
|
rdeps = xbps_rpool_get_pkg_fulldeptree(xhp, pkgver);
|
|
} else {
|
|
rdeps = xbps_pkgdb_get_pkg_fulldeptree(xhp, pkgver);
|
|
}
|
|
process_fulldeptree(xhp, f, plistd, rdeps, repomode);
|
|
} else {
|
|
/*
|
|
* Process the node-sub section in config file.
|
|
*/
|
|
fprintf(f, " main [");
|
|
sub_confd = xbps_dictionary_get(confd, "node-sub");
|
|
if (xbps_dictionary_get_cstring_nocopy(sub_confd, "main-style", &cfprop))
|
|
fprintf(f, "style=%s,", cfprop);
|
|
if (xbps_dictionary_get_cstring_nocopy(sub_confd, "main-fillcolor", &cfprop))
|
|
fprintf(f, "fillcolor=\"%s\",", cfprop);
|
|
|
|
fprintf(f, "label=\"Dictionary\"];\n");
|
|
if (repomode) {
|
|
rdeps = xbps_rpool_get_pkg_revdeps(xhp, pkgver);
|
|
} else {
|
|
rdeps = xbps_pkgdb_get_pkg_revdeps(xhp, pkgver);
|
|
}
|
|
if (xbps_array_count(rdeps))
|
|
xbps_dictionary_set(plistd, "requiredby", rdeps);
|
|
|
|
allkeys = xbps_dictionary_all_keys(plistd);
|
|
parse_array_in_pkg_dictionary(f, plistd, sub_confd, allkeys);
|
|
}
|
|
/*
|
|
* Terminate the stream...
|
|
*/
|
|
fprintf(f, "}\n");
|
|
fflush(f);
|
|
fclose(f);
|
|
}
|
|
|
|
int
|
|
main(int argc, char **argv)
|
|
{
|
|
xbps_dictionary_t plistd = NULL;
|
|
struct xbps_handle xh;
|
|
FILE *f = NULL;
|
|
const char *conf_file = NULL, *rootdir = NULL;
|
|
int c, rv, flags = 0;
|
|
bool repomode = false, fulldepgraph = false;
|
|
|
|
while ((c = getopt(argc, argv, "c:dgfRr:")) != -1) {
|
|
switch (c) {
|
|
case 'c':
|
|
/* Configuration file. */
|
|
conf_file = optarg;
|
|
break;
|
|
case 'd':
|
|
flags |= XBPS_FLAG_DEBUG;
|
|
break;
|
|
case 'g':
|
|
/* Generate auto conf file. */
|
|
generate_conf_file();
|
|
exit(EXIT_SUCCESS);
|
|
case 'f':
|
|
/* generate a full dependency graph */
|
|
fulldepgraph = true;
|
|
break;
|
|
case 'R':
|
|
/* enable repository mode */
|
|
repomode = true;
|
|
break;
|
|
case 'r':
|
|
/* Set different rootdir. */
|
|
rootdir = optarg;
|
|
break;
|
|
case '?':
|
|
default:
|
|
usage();
|
|
}
|
|
}
|
|
|
|
argc -= optind;
|
|
argv += optind;
|
|
|
|
if (argc != 1)
|
|
usage();
|
|
|
|
/* Initialize libxbps */
|
|
memset(&xh, 0, sizeof(xh));
|
|
if (rootdir != NULL)
|
|
xbps_strlcpy(xh.rootdir, rootdir, sizeof(xh.rootdir));
|
|
|
|
xh.flags = flags;
|
|
if ((rv = xbps_init(&xh)) != 0)
|
|
die("failed to initialize libxbps: %s", strerror(rv));
|
|
|
|
/*
|
|
* If -c not set and config file does not exist, use defaults.
|
|
*/
|
|
if (conf_file == NULL)
|
|
conf_file = _DGRAPH_CFFILE;
|
|
|
|
confd = xbps_dictionary_internalize_from_zfile(conf_file);
|
|
if (confd == NULL) {
|
|
if (errno != ENOENT)
|
|
die("cannot read conf file `%s'", conf_file);
|
|
|
|
confd = create_defconf();
|
|
}
|
|
/*
|
|
* Internalize the plist file of the target installed package.
|
|
*/
|
|
if (repomode) {
|
|
plistd = xbps_rpool_get_pkg(&xh, argv[0]);
|
|
} else {
|
|
plistd = xbps_pkgdb_get_pkg(&xh, argv[0]);
|
|
}
|
|
if (plistd == NULL)
|
|
die("cannot find `%s' package", argv[0]);
|
|
|
|
/*
|
|
* Create the output FILE.
|
|
*/
|
|
if ((f = fdopen(STDOUT_FILENO, "w")) == NULL)
|
|
die("cannot open stdout");
|
|
|
|
/*
|
|
* Create the dot(1) graph!
|
|
*/
|
|
if (fulldepgraph) {
|
|
create_dot_graph(&xh, f, plistd, repomode, true);
|
|
} else {
|
|
create_dot_graph(&xh, f, plistd, repomode, false);
|
|
}
|
|
exit(EXIT_SUCCESS);
|
|
}
|