[pacman-contrib] [PATCH] Add pactree
Johannes Löthberg
johannes at kyriasis.com
Sat Oct 15 09:57:55 UTC 2016
Signed-off-by: Johannes Löthberg <johannes at kyriasis.com>
---
configure.ac | 17 ++
src/Makefile.am | 20 +++
src/pactree.c | 542 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 579 insertions(+)
create mode 100644 src/pactree.c
diff --git a/configure.ac b/configure.ac
index 789223d..043c62a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -6,6 +6,11 @@ if test "$pkgdatadir" = ""; then
pkgdatadir="$datadir/$PACKAGE_NAME"
fi
+# Help line for root directory
+AC_ARG_WITH(root-dir,
+ AS_HELP_STRING([--with-root-dir=PATH], [set the location of the root operating directory]),
+ [ROOTDIR=$withval], [ROOTDIR=/])
+
# Help line for vim runtime directory
AC_ARG_WITH(vim-dir,
[AS_HELP_STRING([--with-vim-dir=PATH], [set the location of the vim runtime file directory])],
@@ -18,6 +23,8 @@ AC_ARG_ENABLE(git-version,
[wantgitver=$enableval], [wantgitver=no])
# Checks for programs.
+AC_PROG_CC([clang gcc])
+AC_PROG_CC_C99
AC_PATH_PROGS([BASH_SHELL], [bash bash4], [false])
AS_IF([test "x$BASH_SHELL" = "xfalse"],
@@ -36,6 +43,13 @@ AS_IF([test "x$BASH_SHELL" = "xfalse"],
fi
unset bash_version_major bash_version_minor ok])
+# Check for libarchive
+PKG_CHECK_MODULES(LIBARCHIVE, [libarchive >= 2.8.0], ,
+ AC_MSG_ERROR([*** libarchive >= 2.8.0 is needed to build pacman-contrib!]))
+
+# Check for libalpm
+PKG_CHECK_MODULES(LIBALPM, [libalpm], ,
+ AC_MSG_ERROR([*** libalpm is needed to build pacman-contrib!]))
# Enable or disable use of git version in version string
AC_MSG_CHECKING(whether to use git version if available)
@@ -52,6 +66,9 @@ else
fi
AM_CONDITIONAL(USE_GIT_VERSION, test "x$usegitver" = "xyes")
+# Set root directory
+AC_SUBST(ROOTDIR)
+AC_DEFINE_UNQUOTED([ROOTDIR], "$ROOTDIR", [The location of the root operating directory])
AC_SUBST(vim_dir, [$vim_dir])
SIZECMD="stat -c %s"
diff --git a/src/Makefile.am b/src/Makefile.am
index 15d6bcf..cfeffaa 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -4,9 +4,15 @@ SUBDIRS=
DIST_SUBDIRS = $(SUBDIRS)
+# paths set at make time
+conffile = ${sysconfdir}/pacman.conf
+dbpath = ${localstatedir}/lib/pacman/
+
bin_SCRIPTS = \
$(OURSCRIPTS)
+bin_PROGRAMS = pactree
+
vim_ftdetect_dir = $(vim_dir)/ftdetect
vim_syntax_dir = $(vim_dir)/syntax
@@ -55,6 +61,17 @@ else
REAL_PACKAGE_VERSION = $(PACKAGE_VERSION)
endif
+AM_CPPFLAGS = \
+ -DLOCALEDIR=\"@localedir@\" \
+ -DCONFFILE=\"$(conffile)\" \
+ -DDBPATH=\"$(dbpath)\"
+
+AM_CFLAGS = \
+ -pedantic \
+ -D_GNU_SOURCE \
+ $(LIBARCHIVE_CFLAGS) \
+ $(LIBALPM_CFLAGS)
+
edit = sed \
-e 's|@sysconfdir[@]|$(sysconfdir)|g' \
-e 's|@localstatedir[@]|$(localstatedir)|g' \
@@ -95,4 +112,7 @@ updpkgsums: $(srcdir)/updpkgsums.sh.in
pacsearch: $(srcdir)/pacsearch.pl.in
+pactree_SOURCES = pactree.c
+pactree_LDADD = $(LIBARCHIVE_LIBS) $(LIBALPM_LIBS)
+
# vim:set noet:
diff --git a/src/pactree.c b/src/pactree.c
new file mode 100644
index 0000000..ef40757
--- /dev/null
+++ b/src/pactree.c
@@ -0,0 +1,542 @@
+/*
+ * pactree.c - a simple dependency tree viewer
+ *
+ * Copyright (c) 2010-2016 Pacman Development Team <pacman-dev at archlinux.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <ctype.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <string.h>
+#include <locale.h>
+#include <alpm.h>
+#include <alpm_list.h>
+
+#ifdef HAVE_LANGINFO_H
+#include <langinfo.h>
+#endif
+
+#define LINE_MAX 512
+
+typedef struct tdepth {
+ struct tdepth *prev;
+ struct tdepth *next;
+ int level;
+} tdepth;
+
+/* output */
+struct graph_style {
+ const char *provides;
+ const char *tip;
+ const char *last;
+ const char *limb;
+ int indent;
+};
+
+#define UTF_V "\342\224\202" /* U+2502, Vertical line drawing char */
+#define UTF_VR "\342\224\234" /* U+251C, Vertical and right */
+#define UTF_H "\342\224\200" /* U+2500, Horizontal */
+#define UTF_UR "\342\224\224" /* U+2514, Up and right */
+
+static struct graph_style graph_utf8 = {
+ " provides",
+ UTF_VR UTF_H,
+ UTF_UR UTF_H,
+ UTF_V " ",
+ 2
+};
+
+static struct graph_style graph_default = {
+ " provides",
+ "|-",
+ "`-",
+ "|",
+ 2
+};
+
+static struct graph_style graph_linear = {
+ "",
+ "",
+ "",
+ "",
+ 0
+};
+
+/* color choices */
+struct color_choices {
+ const char *branch1;
+ const char *branch2;
+ const char *leaf1;
+ const char *leaf2;
+ const char *off;
+};
+
+static struct color_choices use_color = {
+ "\033[0;33m", /* yellow */
+ "\033[0;37m", /* white */
+ "\033[1;32m", /* bold green */
+ "\033[0;32m", /* green */
+ "\033[0m"
+};
+
+static struct color_choices no_color = {
+ "",
+ "",
+ "",
+ "",
+ ""
+};
+
+/* long operations */
+enum {
+ OP_CONFIG = 1000
+};
+
+/* globals */
+alpm_handle_t *handle = NULL;
+alpm_list_t *walked = NULL;
+alpm_list_t *provisions = NULL;
+
+/* options */
+struct color_choices *color = &no_color;
+struct graph_style *style = &graph_utf8;
+int graphviz = 0;
+int max_depth = -1;
+int reverse = 0;
+int unique = 0;
+int searchsyncs = 0;
+const char *dbpath = DBPATH;
+const char *configfile = CONFFILE;
+
+/* Trim whitespace and newlines from a string
+ */
+size_t strtrim(char *str)
+{
+ char *end, *pch = str;
+
+ if(str == NULL || *str == '\0') {
+ /* string is empty, so we're done. */
+ return 0;
+ }
+
+ while(isspace((unsigned char)*pch)) {
+ pch++;
+ }
+ if(pch != str) {
+ size_t len = strlen(pch);
+ /* check if there wasn't anything but whitespace in the string. */
+ if(len == 0) {
+ *str = '\0';
+ return 0;
+ }
+ memmove(str, pch, len + 1);
+ pch = str;
+ }
+
+ end = (str + strlen(str) - 1);
+ while(isspace((unsigned char)*end)) {
+ end--;
+ }
+ *++end = '\0';
+
+ return end - pch;
+}
+
+static int register_syncs(void)
+{
+ FILE *fp;
+ char *section = NULL;
+ char line[LINE_MAX];
+ const alpm_siglevel_t level = ALPM_SIG_DATABASE | ALPM_SIG_DATABASE_OPTIONAL;
+
+ fp = fopen(configfile, "r");
+ if(!fp) {
+ fprintf(stderr, "error: config file %s could not be read\n", configfile);
+ return 1;
+ }
+
+ while(fgets(line, LINE_MAX, fp)) {
+ size_t linelen;
+ char *ptr;
+
+ /* ignore whole line and end of line comments */
+ if((ptr = strchr(line, '#'))) {
+ *ptr = '\0';
+ }
+
+ linelen = strtrim(line);
+
+ if(linelen == 0) {
+ continue;
+ }
+
+ if(line[0] == '[' && line[linelen - 1] == ']') {
+ free(section);
+ section = strndup(&line[1], linelen - 2);
+
+ if(section && strcmp(section, "options") != 0) {
+ alpm_db_t *db = alpm_register_syncdb(handle, section, level);
+ alpm_db_set_usage(db, ALPM_DB_USAGE_ALL);
+ }
+ }
+ }
+
+ free(section);
+ fclose(fp);
+
+ return 0;
+}
+
+static int parse_options(int argc, char *argv[])
+{
+ int opt, option_index = 0;
+ char *endptr = NULL;
+
+ static const struct option opts[] = {
+ {"ascii", no_argument, 0, 'a'},
+ {"dbpath", required_argument, 0, 'b'},
+ {"color", no_argument, 0, 'c'},
+ {"depth", required_argument, 0, 'd'},
+ {"graph", no_argument, 0, 'g'},
+ {"help", no_argument, 0, 'h'},
+ {"linear", no_argument, 0, 'l'},
+ {"reverse", no_argument, 0, 'r'},
+ {"sync", no_argument, 0, 's'},
+ {"unique", no_argument, 0, 'u'},
+
+ {"config", required_argument, 0, OP_CONFIG},
+ {0, 0, 0, 0}
+ };
+
+#ifdef HAVE_LANGINFO_H
+ setlocale(LC_ALL, "");
+ if(strcmp(nl_langinfo(CODESET), "UTF-8") == 0) {
+ style = &graph_utf8;
+ }
+#else
+ style = &graph_default;
+#endif
+
+ while((opt = getopt_long(argc, argv, "ab:cd:ghlrsu", opts, &option_index))) {
+ if(opt < 0) {
+ break;
+ }
+
+ switch(opt) {
+ case 'a':
+ style = &graph_default;
+ break;
+ case OP_CONFIG:
+ configfile = optarg;
+ break;
+ case 'b':
+ dbpath = optarg;
+ break;
+ case 'c':
+ color = &use_color;
+ break;
+ case 'd':
+ /* validate depth */
+ max_depth = (int)strtol(optarg, &endptr, 10);
+ if(*endptr != '\0') {
+ fprintf(stderr, "error: invalid depth -- %s\n", optarg);
+ return 1;
+ }
+ break;
+ case 'g':
+ graphviz = 1;
+ break;
+ case 'l':
+ style = &graph_linear;
+ break;
+ case 'r':
+ reverse = 1;
+ break;
+ case 's':
+ searchsyncs = 1;
+ break;
+ case 'u':
+ unique = 1;
+ style = &graph_linear;
+ break;
+ case 'h':
+ case '?':
+ default:
+ return 1;
+ }
+ }
+
+ if(!argv[optind]) {
+ return 1;
+ }
+
+ return 0;
+}
+
+static void usage(void)
+{
+ fprintf(stderr, "pactree (pacman) v" PACKAGE_VERSION "\n\n"
+ "A simple dependency tree viewer.\n\n"
+ "Usage: pactree [options] PACKAGE\n\n"
+ " -a, --ascii use ASCII characters for tree formatting\n"
+ " -b, --dbpath <path> set an alternate database location\n"
+ " -c, --color colorize output\n"
+ " -d, --depth <#> limit the depth of recursion\n"
+ " -g, --graph generate output for graphviz's dot\n"
+ " -h, --help display this help message\n"
+ " -l, --linear enable linear output\n"
+ " -r, --reverse list packages that depend on the named package\n"
+ " -s, --sync search sync databases instead of local\n"
+ " -u, --unique show dependencies with no duplicates (implies -l)\n"
+ " --config <path> set an alternate configuration file\n");
+}
+
+static void cleanup(void)
+{
+ alpm_list_free(walked);
+ alpm_list_free(provisions);
+ alpm_release(handle);
+}
+
+/* pkg provides provision */
+static void print_text(const char *pkg, const char *provision,
+ tdepth *depth, int last)
+{
+ const char *tip = "";
+ int level = 1;
+ if(!pkg && !provision) {
+ /* not much we can do */
+ return;
+ }
+
+ if(depth->level > 0) {
+ tip = last ? style->last : style->tip;
+
+ /* print limbs */
+ while(depth->prev) {
+ depth = depth->prev;
+ }
+ printf("%s", color->branch1);
+ while(depth->next) {
+ printf("%*s%-*s", style->indent * (depth->level - level), "",
+ style->indent, style->limb);
+ level = depth->level + 1;
+ depth = depth->next;
+ }
+ printf("%*s", style->indent * (depth->level - level), "");
+ }
+
+ /* print tip */
+ /* If style->provides is empty (e.g. when using linear style), we do not
+ * want to print the provided package. This makes output easier to parse and
+ * to reuse. */
+ if(!pkg && provision) {
+ printf("%s%s%s%s [unresolvable]%s\n", tip, color->leaf1,
+ provision, color->branch1, color->off);
+ } else if(provision && strcmp(pkg, provision) != 0 && *(style->provides) != '\0') {
+ printf("%s%s%s%s%s %s%s%s\n", tip, color->leaf1, pkg,
+ color->leaf2, style->provides, color->leaf1, provision,
+ color->off);
+ } else {
+ printf("%s%s%s%s\n", tip, color->leaf1, pkg, color->off);
+ }
+}
+
+static void print_graph(const char *parentname, const char *pkgname, const char *depname)
+{
+ if(depname) {
+ printf("\"%s\" -> \"%s\" [color=chocolate4];\n", parentname, depname);
+ if(pkgname && strcmp(depname, pkgname) != 0 && !alpm_list_find_str(provisions, depname)) {
+ printf("\"%s\" -> \"%s\" [arrowhead=none, color=grey];\n", depname, pkgname);
+ provisions = alpm_list_add(provisions, (char *)depname);
+ }
+ } else if(pkgname) {
+ printf("\"%s\" -> \"%s\" [color=chocolate4];\n", parentname, pkgname);
+ }
+}
+
+/* parent depends on dep which is satisfied by pkg */
+static void print(const char *parentname, const char *pkgname,
+ const char *depname, tdepth *depth, int last)
+{
+ if(graphviz) {
+ print_graph(parentname, pkgname, depname);
+ } else {
+ print_text(pkgname, depname, depth, last);
+ }
+}
+
+static void print_start(const char *pkgname, const char *provname)
+{
+ if(graphviz) {
+ printf("digraph G { START [color=red, style=filled];\n"
+ "node [style=filled, color=green];\n"
+ " \"START\" -> \"%s\";\n", pkgname);
+ } else {
+ tdepth d = {
+ NULL,
+ NULL,
+ 0
+ };
+ print_text(pkgname, provname, &d, 0);
+ }
+}
+
+static void print_end(void)
+{
+ if(graphviz) {
+ /* close graph output */
+ printf("}\n");
+ }
+}
+
+static alpm_list_t *get_pkg_dep_names(alpm_pkg_t *pkg)
+{
+ alpm_list_t *i, *names = NULL;
+ for(i = alpm_pkg_get_depends(pkg); i; i = alpm_list_next(i)) {
+ alpm_depend_t *d = i->data;
+ names = alpm_list_add(names, d->name);
+ }
+ return names;
+}
+
+/**
+ * walk dependencies, showing dependencies of the target
+ */
+static void walk_deps(alpm_list_t *dblist, alpm_pkg_t *pkg, tdepth *depth, int rev)
+{
+ alpm_list_t *deps, *i;
+
+ if(!pkg || ((max_depth >= 0) && (depth->level > max_depth))) {
+ return;
+ }
+
+ walked = alpm_list_add(walked, (void *)alpm_pkg_get_name(pkg));
+
+ if(rev) {
+ deps = alpm_pkg_compute_requiredby(pkg);
+ } else {
+ deps = get_pkg_dep_names(pkg);
+ }
+
+ for(i = deps; i; i = alpm_list_next(i)) {
+ const char *pkgname = i->data;
+ int last = alpm_list_next(i) ? 0 : 1;
+
+ alpm_pkg_t *dep_pkg = alpm_find_dbs_satisfier(handle, dblist, pkgname);
+
+ if(alpm_list_find_str(walked, dep_pkg ? alpm_pkg_get_name(dep_pkg) : pkgname)) {
+ /* if we've already seen this package, don't print in "unique" output
+ * and don't recurse */
+ if(!unique) {
+ print(alpm_pkg_get_name(pkg), alpm_pkg_get_name(dep_pkg), pkgname, depth, last);
+ }
+ } else {
+ print(alpm_pkg_get_name(pkg), alpm_pkg_get_name(dep_pkg), pkgname, depth, last);
+ if(dep_pkg) {
+ tdepth d = {
+ depth,
+ NULL,
+ depth->level + 1
+ };
+ depth->next = &d;
+ /* last dep, cut off the limb here */
+ if(last) {
+ if(depth->prev) {
+ depth->prev->next = &d;
+ d.prev = depth->prev;
+ depth = &d;
+ } else {
+ d.prev = NULL;
+ }
+ }
+ walk_deps(dblist, dep_pkg, &d, rev);
+ depth->next = NULL;
+ }
+ }
+ }
+
+ if(rev) {
+ FREELIST(deps);
+ } else {
+ alpm_list_free(deps);
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ int freelist = 0, ret = 0;
+ alpm_errno_t err;
+ const char *target_name;
+ alpm_pkg_t *pkg;
+ alpm_list_t *dblist = NULL;
+
+ if(parse_options(argc, argv) != 0) {
+ usage();
+ ret = 1;
+ goto finish;
+ }
+
+ handle = alpm_initialize(ROOTDIR, dbpath, &err);
+ if(!handle) {
+ fprintf(stderr, "error: cannot initialize alpm: %s\n",
+ alpm_strerror(err));
+ ret = 1;
+ goto finish;
+ }
+
+ if(searchsyncs) {
+ if(register_syncs() != 0) {
+ ret = 1;
+ goto finish;
+ }
+ dblist = alpm_get_syncdbs(handle);
+ } else {
+ dblist = alpm_list_add(dblist, alpm_get_localdb(handle));
+ freelist = 1;
+ }
+
+ /* we only care about the first non option arg for walking */
+ target_name = argv[optind];
+
+ pkg = alpm_find_dbs_satisfier(handle, dblist, target_name);
+ if(!pkg) {
+ fprintf(stderr, "error: package '%s' not found\n", target_name);
+ ret = 1;
+ goto finish;
+ }
+
+ print_start(alpm_pkg_get_name(pkg), target_name);
+
+ tdepth d = {
+ NULL,
+ NULL,
+ 1
+ };
+ walk_deps(dblist, pkg, &d, reverse);
+
+ print_end();
+
+ if(freelist) {
+ alpm_list_free(dblist);
+ }
+
+finish:
+ cleanup();
+ return ret;
+}
+
+/* vim: set noet: */
--
2.10.0
More information about the pacman-contrib
mailing list