[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