6 use List
::Util
qw(first);
7 use AUR
::Json
qw(parse_json_aur write_json);
8 use AUR
::Query
qw(query_multi);
9 use AUR
::Options
qw(add_from_stdin);
10 my $argv0 = 'depends';
13 my ($targets, $types, $max_req) = @_;
14 my @depends = @
{$targets};
15 my (%results, %reqby, %shlibs);
17 # Make every explicit target provide itself
18 map { $shlibs{$_} = [$_, 0] } @
{$targets};
20 for my $a (1..$max_req) {
21 say STDERR
join(" ", "query: [$a]", @depends) if defined $ENV{'AUR_DEBUG'};
24 say STDERR
"$argv0: total requests: $a (out of range)";
27 my @level = query_multi
(terms
=> \
@depends, type
=> 'info', callback
=> \
&parse_json_aur
);
29 if (not scalar(@level) and $a == 1) {
30 say STDERR
"$argv0: no packages found";
35 for my $node (@level) {
36 my $name = $node->{'Name'};
37 $results{$name} = $node;
39 # XXX: no check if provides has valid package version (vercmp)
40 for my $spec (@
{$node->{'Provides'} // []}) {
41 my ($prov, $op, $ver) = split(/(<=|>=|<|=|>)/, $spec);
43 # XXX: only keeps the first provider
44 if (not defined $shlibs{$prov}) {
45 # Use weights to make explicit provides take precedence
46 $shlibs{$prov} = [$node->{'Name'}, $a];
50 # Filter out dependency types early (#882)
51 for my $deptype (@
{$types}) {
52 next if not defined($node->{$deptype}); # no dependency of this type
54 for my $spec (@
{$node->{$deptype}}) {
55 # valid operators (important: <= before <)
56 my ($dep, $op, $ver) = split(/(<=|>=|<|=|>)/, $spec);
58 # populate reverse depends
59 $reqby{$dep}{$node->{'Name'}} = $deptype;
61 # avoid querying duplicate packages (#4)
62 next if defined $results{$dep};
65 # mark as incomplete (retrieved in next step or repo package)
66 $results{$dep} = 'None';
70 last if not scalar(@depends); # no further results
72 return \
%results, \
%reqby, \
%shlibs;
76 my ($results, $reqby, $shlibs, $provides, $showall) = @_;
77 %{$shlibs} = () if not $provides;
80 say STDERR
"query: filtering results" if defined $ENV{'AUR_DEBUG'};
82 for my $name (keys %{$results}) {
83 if ($results->{$name} eq 'None') {
85 $mod{$name}->{'Name'} = $name;
86 $mod{$name}->{'RequiredBy'} = $reqby->{$name};
91 # Take provides on the command-line into account (#837)
93 if (defined $shlibs->{$name} and $shlibs->{$name}->[1] == 1) {
94 $prov = $shlibs->{$name}->[0];
96 # Avoid overriding provides with later target (random ordering of hash!)
97 elsif (defined $mod{$name}) {
101 # Add data and reverse dependencies for AUR targets
102 $mod{$prov} = $results->{$prov};
103 $mod{$prov}->{'RequiredBy'} = $reqby->{$name};
105 # Include `Self` dependency for every target (aur-sync, #1065)
106 $mod{$prov}->{'RequiredBy'}->{$prov} = 'Self';
109 # Merge reverse dependencies for purely virtual targets, with corresponding
110 # packages specified on the command-line
111 for my $name (keys %{$results}) {
112 if ($results->{$name} eq 'None' and defined $shlibs->{$name}) {
113 my $prov = $shlibs->{$name}[0];
115 $mod{$prov}->{'RequiredBy'} = {
116 %{$mod{$prov}->{'RequiredBy'}}, %{$reqby->{$prov}}
120 return \
%mod, $reqby, $shlibs;
123 # Recursively remove nodes from dependency graph (#592)
124 # Operates on modified graph: provides are solved first
126 my ($mod, $installed) = @_;
128 for my $name (keys %{$mod}) { # list returned by `keys` is a copy
129 # Remove installed targets from reverse dependencies
130 my $mod_reqby = $mod->{$name}->{'RequiredBy'};
132 for my $dep (keys %{$mod_reqby}) {
133 # Every reverse dependency needs to be checked against every pkgname
134 # that is assumed to be installed (quadratic complexity)
135 my $found = first
{ $dep eq $_ } @
{$installed};
137 if (defined $found) {
138 delete $mod_reqby->{$found};
143 for my $name (keys %{$mod}) {
144 my $mod_reqby = $mod->{$name}->{'RequiredBy'};
145 if (not scalar keys %{$mod_reqby}) {
146 delete $mod->{$name}; # remove targets that are no longer required
149 my $found = first
{ $name eq $_ } @
{$installed};
150 if (defined $found) {
151 delete $mod->{$name}; # remove targets that are installed
157 # tsv output for usage with aur-sync (aurutils <=10)
158 sub table_v10_compat
{
159 my ($results, $types) = @_;
161 for my $pkg (values %{$results}) {
162 next if not defined $pkg->{'PackageBase'};
164 my ($name, $base, $version) = (
165 $pkg->{'Name'}, $pkg->{'PackageBase'}, $pkg->{'Version'}
167 say join("\t", $name, $name, $base, $version, 'Self');
169 for my $deptype (@
{$types}) {
170 my $depends = $pkg->{$deptype};
171 next if (ref($depends) ne 'ARRAY');
173 for my $dep (@
{$depends}) {
174 say join("\t", $name, $dep, $base, $version, $deptype);
180 # tsv output for usage with aur-sync (aurutils >=11)
184 for my $pkg (values %{$results}) {
185 my ($name, $base, $version, $reqby) = (
186 $pkg->{'Name'}, $pkg->{'PackageBase'}, $pkg->{'Version'}, $pkg->{'RequiredBy'}
189 for my $dep (keys %{$reqby}) {
190 say join("\t", $name, $dep, $base // '-', $version // '-', $reqby->{$dep});
195 # package/dependency pairs for use with tsort(1) or aur-graph
196 # XXX: include optional column for versioned dependencies
198 my ($results, $key, $reverse) = @_;
200 for my $pkg (values %{$results}) {
201 my $target = $pkg->{$key};
203 for my $reqby (keys %{$pkg->{'RequiredBy'}}) {
204 my $rdep = $key eq 'Name' ?
$reqby : $results->{$reqby}->{$key} // '-';
205 my @pair = $reverse ?
($target, $rdep) : ($rdep, $target);
207 say join("\t", @pair);
215 my $opt_makedepends = 1;
216 my $opt_checkdepends = 1;
217 my $opt_optdepends = 0;
218 my $opt_mode = "pairs";
220 my $opt_show_all = 0; # implies $opt_pkgname = 1
222 my $opt_provides = 1;
223 my $opt_installed = [];
226 'assume-installed=s' => $opt_installed,
227 'no-depends' => sub { $opt_depends = 0 },
228 'no-makedepends' => sub { $opt_makedepends = 0 },
229 'no-checkdepends' => sub { $opt_checkdepends = 0 },
230 'optdepends' => \
$opt_optdepends,
231 'no-provides' => sub { $opt_provides = 0 },
232 'n|pkgname' => \
$opt_pkgname,
233 'b|pkgbase' => sub { $opt_pkgname = 0 },
234 'G|graph' => sub { }, # noop
235 't|table' => sub { $opt_mode = "table" },
236 'J|json' => sub { $opt_mode = "json" },
237 'jsonl' => sub { $opt_mode = "jsonl" },
238 'r|reverse' => \
$opt_reverse,
239 'a|all|show-all' => \
$opt_show_all
242 if (not scalar(@ARGV)) {
243 say STDERR
"$argv0: at least one argument required";
247 # Handle '-' to take packages from stdin
248 add_from_stdin
(\
@ARGV, ['-', '/dev/stdin']);
250 # Exit gracefully on empty stdin, e.g. when piping from `aur repo -u`
251 exit(0) if not scalar(@ARGV);
253 # Variable dependency types (#826)
255 push(@types, 'Depends') if $opt_depends;
256 push(@types, 'MakeDepends') if $opt_makedepends;
257 push(@types, 'CheckDepends') if $opt_checkdepends;
258 push(@types, 'OptDepends') if $opt_optdepends;
260 # Array notation for `--assume-installed`
261 @
{$opt_installed} = map { split(',', $_) } @
{$opt_installed};
263 # Resolve dependency tree
264 my ($results, $reqby, $shlibs) = chain_mod
(chain
(\
@ARGV, \
@types, 30), $opt_provides, $opt_show_all);
266 # Remove virtual dependencies with `--show-all` (#1063)
267 if ($opt_show_all and $opt_provides) {
268 # XXX: also include `None` dependencies with `Self` RequiredBy
270 $_ ne $shlibs->{$_}->[0] and $shlibs->{$_}->[1] > 0
273 $results = prune
($results, \
@virtual);
276 # Remove transitive dependencies for installed targets (#592)
277 if (scalar @
{$opt_installed}) {
278 $results = prune
($results, $opt_installed);
282 if ($opt_mode eq 'pairs') {
283 pairs
($results, ($opt_pkgname or $opt_show_all) ?
'Name' : 'PackageBase', $opt_reverse);
285 elsif ($opt_mode eq 'table' and $opt_reverse) {
288 elsif ($opt_mode eq 'table') {
289 table_v10_compat
($results, \
@types);
291 elsif ($opt_mode eq 'json') {
292 say write_json
($results);
294 elsif ($opt_mode eq 'jsonl') {
295 map { say write_json
$results->{$_} } keys %{$results};
298 say STDERR
"$argv0: invalid mode selected";
303 # vim: set et sw=4 sts=4 ft=perl: