2 * pactree.c - a simple dependency tree viewer
4 * Copyright (c) 2010-2011 Pacman Development Team <pacman-dev@archlinux.org>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
28 #include <alpm_list.h>
40 static struct graph_style graph_default
= {
47 static struct graph_style graph_linear
= {
55 struct color_choices
{
63 static struct color_choices use_color
= {
64 "\033[0;33m", /* yellow */
65 "\033[0;37m", /* white */
66 "\033[1;32m", /* bold green */
67 "\033[0;32m", /* green */
71 static struct color_choices no_color
= {
85 alpm_handle_t
*handle
= NULL
;
86 alpm_list_t
*walked
= NULL
;
87 alpm_list_t
*provisions
= NULL
;
90 struct color_choices
*color
= &no_color
;
91 struct graph_style
*style
= &graph_default
;
97 const char *dbpath
= DBPATH
;
98 const char *configfile
= CONFFILE
;
101 /* A quick and dirty implementation derived from glibc */
102 static size_t strnlen(const char *s
, size_t max
)
104 register const char *p
;
105 for(p
= s
; *p
&& max
--; ++p
);
109 char *strndup(const char *s
, size_t n
)
111 size_t len
= strnlen(s
, n
);
112 char *new = (char *) malloc(len
+ 1);
118 return (char *)memcpy(new, s
, len
);
122 static char *strtrim(char *str
)
126 if(str
== NULL
|| *str
== '\0') {
127 /* string is empty, so we're done. */
131 while(isspace((unsigned char)*pch
)) {
135 size_t len
= strlen(pch
);
137 memmove(str
, pch
, len
+ 1);
143 /* check if there wasn't anything but whitespace in the string. */
148 pch
= (str
+ (strlen(str
) - 1));
149 while(isspace((unsigned char)*pch
)) {
157 static int register_syncs(void) {
159 char *ptr
, *section
= NULL
;
161 const alpm_siglevel_t level
= ALPM_SIG_DATABASE
| ALPM_SIG_DATABASE_OPTIONAL
;
163 fp
= fopen(configfile
, "r");
168 while(fgets(line
, LINE_MAX
, fp
)) {
171 if(line
[0] == '#' || !strlen(line
)) {
175 if((ptr
= strchr(line
, '#'))) {
180 if(line
[0] == '[' && line
[strlen(line
) - 1] == ']') {
182 section
= strndup(&line
[1], strlen(line
) - 2);
184 if(section
&& strcmp(section
, "options") != 0) {
185 alpm_db_register_sync(handle
, section
, level
);
196 static int parse_options(int argc
, char *argv
[])
198 int opt
, option_index
= 0;
201 static const struct option opts
[] = {
202 {"dbpath", required_argument
, 0, 'b'},
203 {"color", no_argument
, 0, 'c'},
204 {"depth", required_argument
, 0, 'd'},
205 {"graph", no_argument
, 0, 'g'},
206 {"help", no_argument
, 0, 'h'},
207 {"linear", no_argument
, 0, 'l'},
208 {"reverse", no_argument
, 0, 'r'},
209 {"sync", no_argument
, 0, 'S'},
210 {"unique", no_argument
, 0, 'u'},
212 {"config", required_argument
, 0, OP_CONFIG
},
216 while((opt
= getopt_long(argc
, argv
, "b:cd:ghlrsu", opts
, &option_index
))) {
233 max_depth
= (int)strtol(optarg
, &endptr
, 10);
234 if(*endptr
!= '\0') {
235 fprintf(stderr
, "error: invalid depth -- %s\n", optarg
);
243 style
= &graph_linear
;
253 style
= &graph_linear
;
269 static void usage(void)
271 fprintf(stderr
, "pactree v" PACKAGE_VERSION
"\n"
272 "Usage: pactree [options] PACKAGE\n\n"
273 " -b, --dbpath <path> set an alternate database location\n"
274 " -c, --color colorize output\n"
275 " -d, --depth <#> limit the depth of recursion\n"
276 " -g, --graph generate output for graphviz's dot\n"
277 " -h, --help display this help message\n"
278 " -l, --linear enable linear output\n"
279 " -r, --reverse show reverse dependencies\n"
280 " -s, --sync search sync DBs instead of local\n"
281 " -u, --unique show dependencies with no duplicates (implies -l)\n"
282 " --config <path> set an alternate configuration file\n");
285 static void cleanup(void)
287 alpm_list_free(walked
);
288 alpm_list_free(provisions
);
289 alpm_release(handle
);
292 /* pkg provides provision */
293 static void print_text(const char *pkg
, const char *provision
, int depth
)
295 int indent_sz
= (depth
+ 1) * style
->indent
;
297 if(!pkg
&& !provision
) {
298 /* not much we can do */
302 if(!pkg
&& provision
) {
303 /* we failed to resolve provision */
304 printf("%s%*s%s%s%s [unresolvable]%s\n", color
->branch1
, indent_sz
,
305 style
->tip1
, color
->leaf1
, provision
, color
->branch1
, color
->off
);
306 } else if(provision
&& strcmp(pkg
, provision
) != 0) {
307 /* pkg provides provision */
308 printf("%s%*s%s%s%s%s %s%s%s\n", color
->branch2
, indent_sz
, style
->tip2
,
309 color
->leaf1
, pkg
, color
->leaf2
, style
->provides
, color
->leaf1
, provision
,
312 /* pkg is a normal package */
313 printf("%s%*s%s%s%s\n", color
->branch1
, indent_sz
, style
->tip1
, color
->leaf1
,
318 static void print_graph(const char *parentname
, const char *pkgname
, const char *depname
)
321 printf("\"%s\" -> \"%s\" [color=chocolate4];\n", parentname
, depname
);
322 if(pkgname
&& strcmp(depname
, pkgname
) != 0 && !alpm_list_find_str(provisions
, depname
)) {
323 printf("\"%s\" -> \"%s\" [arrowhead=none, color=grey];\n", depname
, pkgname
);
324 provisions
= alpm_list_add(provisions
, (char *)depname
);
327 printf("\"%s\" -> \"%s\" [color=chocolate4];\n", parentname
, pkgname
);
331 /* parent depends on dep which is satisfied by pkg */
332 static void print(const char *parentname
, const char *pkgname
, const char *depname
, int depth
)
335 print_graph(parentname
, pkgname
, depname
);
337 print_text(pkgname
, depname
, depth
);
341 static void print_start(const char *pkgname
, const char *provname
)
344 printf("digraph G { START [color=red, style=filled];\n"
345 "node [style=filled, color=green];\n"
346 " \"START\" -> \"%s\";\n", pkgname
);
348 print_text(pkgname
, provname
, 0);
352 static void print_end(void)
355 /* close graph output */
360 static alpm_pkg_t
*get_pkg_from_dbs(alpm_list_t
*dbs
, const char *needle
) {
364 for(i
= dbs
; i
; i
= alpm_list_next(i
)) {
365 ret
= alpm_db_get_pkg(alpm_list_getdata(i
), needle
);
374 * walk dependencies in reverse, showing packages which require the target
376 static void walk_reverse_deps(alpm_list_t
*dblist
, alpm_pkg_t
*pkg
, int depth
)
378 alpm_list_t
*required_by
, *i
;
380 if(!pkg
|| ((max_depth
>= 0) && (depth
== max_depth
+ 1))) {
384 walked
= alpm_list_add(walked
, (void *)alpm_pkg_get_name(pkg
));
385 required_by
= alpm_pkg_compute_requiredby(pkg
);
387 for(i
= required_by
; i
; i
= alpm_list_next(i
)) {
388 const char *pkgname
= alpm_list_getdata(i
);
390 if(alpm_list_find_str(walked
, pkgname
)) {
391 /* if we've already seen this package, don't print in "unique" output
392 * and don't recurse */
394 print(alpm_pkg_get_name(pkg
), pkgname
, NULL
, depth
);
397 print(alpm_pkg_get_name(pkg
), pkgname
, NULL
, depth
);
398 walk_reverse_deps(dblist
, get_pkg_from_dbs(dblist
, pkgname
), depth
+ 1);
402 FREELIST(required_by
);
406 * walk dependencies, showing dependencies of the target
408 static void walk_deps(alpm_list_t
*dblist
, alpm_pkg_t
*pkg
, int depth
)
412 if((max_depth
>= 0) && (depth
== max_depth
+ 1)) {
416 walked
= alpm_list_add(walked
, (void *)alpm_pkg_get_name(pkg
));
418 for(i
= alpm_pkg_get_depends(pkg
); i
; i
= alpm_list_next(i
)) {
419 alpm_depend_t
*depend
= alpm_list_getdata(i
);
420 alpm_pkg_t
*provider
= alpm_find_dbs_satisfier(handle
, dblist
, depend
->name
);
423 const char *provname
= alpm_pkg_get_name(provider
);
425 if(alpm_list_find_str(walked
, provname
)) {
426 /* if we've already seen this package, don't print in "unique" output
427 * and don't recurse */
429 print(alpm_pkg_get_name(pkg
), provname
, depend
->name
, depth
);
432 print(alpm_pkg_get_name(pkg
), provname
, depend
->name
, depth
);
433 walk_deps(dblist
, provider
, depth
+ 1);
436 /* unresolvable package */
437 print(alpm_pkg_get_name(pkg
), NULL
, depend
->name
, depth
);
442 int main(int argc
, char *argv
[])
444 int freelist
= 0, ret
= 0;
445 enum _alpm_errno_t err
;
446 const char *target_name
;
448 alpm_list_t
*dblist
= NULL
;
450 if(parse_options(argc
, argv
) != 0) {
456 handle
= alpm_initialize(ROOTDIR
, dbpath
, &err
);
458 fprintf(stderr
, "error: cannot initialize alpm: %s\n",
465 if(register_syncs() != 0) {
466 fprintf(stderr
, "error: failed to register sync DBs\n");
470 dblist
= alpm_option_get_syncdbs(handle
);
472 dblist
= alpm_list_add(dblist
, alpm_option_get_localdb(handle
));
476 /* we only care about the first non option arg for walking */
477 target_name
= argv
[optind
];
479 pkg
= alpm_find_dbs_satisfier(handle
, dblist
, target_name
);
481 fprintf(stderr
, "error: package '%s' not found\n", target_name
);
486 print_start(alpm_pkg_get_name(pkg
), target_name
);
489 walk_reverse_deps(dblist
, pkg
, 1);
491 walk_deps(dblist
, pkg
, 1);
497 alpm_list_free(dblist
);
505 /* vim: set ts=2 sw=2 noet: */