forked from cromer/pamac-classic
287 lines
7.8 KiB
Vala
287 lines
7.8 KiB
Vala
/*
|
|
* pactree.vala - a simple dependency tree viewer translated in Vala
|
|
*
|
|
* Copyright (C) 2014-2015 Guillaume Benoit <guillaume@manjaro.org>
|
|
* Copyright (c) 2010-2011 Pacman Development Team <pacman-dev@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/>.
|
|
*/
|
|
|
|
// Compile with: valac --pkg=libalpm --vapidir=../vapi pactree.vala
|
|
|
|
using Alpm;
|
|
|
|
/* output */
|
|
string provides;
|
|
string unresolvable;
|
|
string branch_tip1;
|
|
string branch_tip2;
|
|
int indent_size;
|
|
|
|
/* color */
|
|
string branch1_color;
|
|
string branch2_color;
|
|
string leaf1_color;
|
|
string leaf2_color;
|
|
string color_off;
|
|
|
|
/* globals */
|
|
Handle handle;
|
|
unowned DB localdb;
|
|
Alpm.List<string> walked;
|
|
Alpm.List<string> provisions;
|
|
|
|
/* options */
|
|
bool color;
|
|
bool graphviz;
|
|
bool linear;
|
|
int max_depth;
|
|
bool reverse;
|
|
bool unique;
|
|
string dbpath;
|
|
|
|
const OptionEntry[] options = {
|
|
{ "dbpath", 'b', 0, OptionArg.STRING, ref dbpath, "set an alternate database location", "path" },
|
|
{ "color", 'c', 0, OptionArg.NONE, ref color, "colorize output", null },
|
|
{ "depth", 'd', 0, OptionArg.INT, ref max_depth, "limit the depth of recursion", "number" },
|
|
{ "graph", 'g', 0, OptionArg.NONE, ref graphviz, "generate output for graphviz", null },
|
|
{ "linear", 'l', 0, OptionArg.NONE, ref linear, "enable linear output", null },
|
|
{ "reverse", 'r', 0, OptionArg.NONE, ref reverse, "show reverse dependencies", null },
|
|
{ "unique", 'u', 0, OptionArg.NONE, ref unique, "show dependencies with no duplicates (implies -l)", null },
|
|
{ null }
|
|
};
|
|
|
|
static void init_options() {
|
|
/* initialize options */
|
|
color = false;
|
|
graphviz = false;
|
|
linear = false;
|
|
max_depth = -1;
|
|
reverse = false;
|
|
unique = false;
|
|
dbpath = "/var/lib/pacman";
|
|
|
|
/* output */
|
|
provides = " provides";
|
|
unresolvable = " [unresolvable]";
|
|
branch_tip1 = "|--";
|
|
branch_tip2 = "+--";
|
|
indent_size = 3;
|
|
|
|
/* color */
|
|
branch1_color = "\033[0;33m"; /* yellow */
|
|
branch2_color = "\033[0;37m"; /* white */
|
|
leaf1_color = "\033[1;32m"; /* bold green */
|
|
leaf2_color = "\033[0;32m"; /* green */
|
|
color_off = "\033[0m";
|
|
}
|
|
|
|
static int parse_options(ref unowned string[] args) {
|
|
var opts = new OptionContext("");
|
|
opts.set_help_enabled(true);
|
|
opts.add_main_entries(options, null);
|
|
|
|
try {
|
|
bool b = opts.parse(ref args);
|
|
if (!b) {
|
|
stderr.puts(opts.get_help(false, null));
|
|
return 1;
|
|
}
|
|
}
|
|
catch (OptionError e)
|
|
{
|
|
stderr.puts("Unable to parse options : " + e.message + "\n");
|
|
return 1;
|
|
}
|
|
/* there must be (at least) one argument left */
|
|
if (args.length == 1) return 1;
|
|
/* unique implies linear */
|
|
if (unique) linear = true;
|
|
|
|
/* no color */
|
|
if (!color) {
|
|
branch1_color = branch2_color = "";
|
|
leaf1_color = leaf2_color = "";
|
|
color_off = "";
|
|
}
|
|
|
|
/* linear */
|
|
if (linear) {
|
|
provides = "";
|
|
branch_tip1 = branch_tip2 = "";
|
|
indent_size = 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void local_init() {
|
|
Alpm.Errno error;
|
|
handle = new Handle ("/", dbpath, out error);
|
|
assert (error == 0);
|
|
localdb = handle.localdb;
|
|
assert (localdb != null);
|
|
}
|
|
|
|
static int main (string[] args) {
|
|
init_options();
|
|
int ret = parse_options(ref args);
|
|
if (ret != 0) return ret;
|
|
|
|
local_init();
|
|
string? target_name = args[1];
|
|
|
|
unowned Package? pkg = find_satisfier(localdb.pkgcache, target_name);
|
|
if (pkg == null) {
|
|
stderr.printf("Error: package '%s' not found\n", target_name);
|
|
return 1;
|
|
}
|
|
|
|
/* begin writing */
|
|
print_start(pkg.name, target_name);
|
|
if(reverse)
|
|
walk_reverse_deps(pkg, 1);
|
|
else
|
|
walk_deps(pkg, 1);
|
|
|
|
print_end();
|
|
return 0;
|
|
}
|
|
|
|
static void print_text(string? pkg, string? provision, int depth)
|
|
{
|
|
int indent_sz = (depth + 1) * indent_size;
|
|
|
|
if ((pkg == null) && (provision == null)) return;
|
|
|
|
if (pkg == null) {
|
|
/* we failed to resolve provision */
|
|
stdout.printf("%s%*s%s%s%s%s%s\n", branch1_color, indent_sz, branch_tip1,
|
|
leaf1_color, provision, branch1_color, unresolvable, color_off);
|
|
} else if ((provision != null) && (provision != pkg)) {
|
|
/* pkg provides provision */
|
|
stdout.printf("%s%*s%s%s%s%s %s%s%s\n", branch2_color, indent_sz, branch_tip2,
|
|
leaf1_color, pkg, leaf2_color, provides, leaf1_color, provision,
|
|
color_off);
|
|
} else {
|
|
/* pkg is a normal package */
|
|
stdout.printf("%s%*s%s%s%s\n", branch1_color, indent_sz, branch_tip1, leaf1_color,
|
|
pkg, color_off);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* walk dependencies in reverse, showing packages which require the target
|
|
*/
|
|
static void walk_reverse_deps(Package pkg, int depth) {
|
|
if((max_depth >= 0) && (depth > max_depth)) return;
|
|
|
|
walked.add(pkg.name);
|
|
Alpm.List<string> required_by = pkg.compute_requiredby();
|
|
|
|
unowned Alpm.List<string> list = required_by;
|
|
while (list != null) {
|
|
unowned string pkgname = list.data;
|
|
if (walked.find_str(pkgname) != null) {
|
|
/* if we've already seen this package, don't print in "unique" output
|
|
* and don't recurse */
|
|
if (!unique) {
|
|
print(pkg.name, pkgname, null, depth);
|
|
}
|
|
} else {
|
|
print(pkg.name, pkgname, null, depth);
|
|
walk_reverse_deps(localdb.get_pkg(pkgname), depth + 1);
|
|
}
|
|
list.next ();
|
|
}
|
|
required_by.free_inner(GLib.free);
|
|
}
|
|
|
|
/**
|
|
* walk dependencies, showing dependencies of the target
|
|
*/
|
|
static void walk_deps(Package pkg, int depth)
|
|
{
|
|
if((max_depth >= 0) && (depth > max_depth)) return;
|
|
|
|
walked.add(pkg.name);
|
|
|
|
unowned Alpm.List<unowned Depend> depends = pkg.depends;
|
|
while (depends != null) {
|
|
unowned Alpm.Depend depend = depends.data;
|
|
unowned string depname = depend.name;
|
|
unowned Package? provider = find_satisfier (localdb.pkgcache, depname);
|
|
|
|
if (provider != null) {
|
|
string provname = provider.name;
|
|
|
|
if (walked.find_str (provname) != null) {
|
|
/* if we've already seen this package, don't print in "unique" output
|
|
* and don't recurse */
|
|
if (!unique) {
|
|
print (pkg.name, provname, depname, depth);
|
|
}
|
|
} else {
|
|
print (pkg.name, provname, depname, depth);
|
|
walk_deps(provider, depth + 1);
|
|
}
|
|
} else {
|
|
/* unresolvable package */
|
|
print(pkg.name, null, depname, depth);
|
|
}
|
|
depends.next ();
|
|
}
|
|
}
|
|
|
|
static void print_graph(string parentname, string? pkgname, string? depname)
|
|
{
|
|
if(depname != null) {
|
|
stdout.printf("\"%s\" -> \"%s\" [color=chocolate4];\n", parentname, depname);
|
|
if((pkgname != null) && (depname != pkgname) && (provisions.find_str(depname) != null)) {
|
|
stdout.printf("\"%s\" -> \"%s\" [arrowhead=none, color=grey];\n", depname, pkgname);
|
|
provisions.add(depname);
|
|
}
|
|
} else if(pkgname != null) {
|
|
stdout.printf("\"%s\" -> \"%s\" [color=chocolate4];\n", parentname, pkgname);
|
|
}
|
|
}
|
|
|
|
/* parent depends on dep which is satisfied by pkg */
|
|
static void print(string? parentname, string? pkgname, string? depname, int depth)
|
|
{
|
|
if(graphviz) {
|
|
print_graph(parentname, pkgname, depname);
|
|
} else {
|
|
print_text(pkgname, depname, depth);
|
|
}
|
|
}
|
|
|
|
static void print_start(string pkgname, string provname)
|
|
{
|
|
if(graphviz) {
|
|
stdout.printf("digraph G { START [color=red, style=filled];\n" +
|
|
"node [style=filled, color=green];\n" +
|
|
" \"START\" -> \"%s\";\n", pkgname);
|
|
} else {
|
|
print_text(pkgname, provname, 0);
|
|
}
|
|
}
|
|
|
|
static void print_end()
|
|
{
|
|
if(graphviz) {
|
|
/* close graph output */
|
|
stdout.printf("}\n");
|
|
}
|
|
}
|