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 # Include `Self` dependency for every target (aur-sync, #1065)
40 push(@
{$reqby{$name}{'Self'}}, $name);
42 # XXX: no check if provides has valid package version (vercmp)
43 for my $spec (@
{$node->{'Provides'} // []}) {
44 my ($prov, $op, $ver) = split(/(<=|>=|<|=|>)/, $spec);
46 # XXX: only keeps the first provider
47 if (not defined $shlibs{$prov}) {
48 # Use weights to make explicit provides take precedence
49 $shlibs{$prov} = [$node->{'Name'}, $a]
53 # Filter out dependency types early (#882)
54 for my $deptype (@
{$types}) {
55 if (not defined($node->{$deptype})) {
56 next; # no dependency of this type
59 for my $spec (@
{$node->{$deptype}}) {
60 # valid operators (important: <= before <)
61 my ($dep, $op, $ver) = split(/(<=|>=|<|=|>)/, $spec);
63 # populate reverse depends
64 push(@
{$reqby{$dep}{$deptype}}, $node->{'Name'});
66 # avoid querying duplicate packages (#4)
67 next if (defined $results{$dep});
70 # mark as incomplete (retrieved in next step or repo package)
71 $results{$dep} = 'None';
75 if (not scalar(@depends)) {
76 last; # no further results
79 return \
%results, \
%reqby, \
%shlibs;
83 my ($results, $reqby, $shlibs, $provides, $showall) = @_;
84 %{$shlibs} = () if not $provides;
87 # Add reverse dependencies for AUR targets
88 say STDERR
"query: filtering results" if defined $ENV{'AUR_DEBUG'};
90 # Take provides on the command-line into account (#837)
91 if (defined $shlibs->{$_} and $shlibs->{$_}->[1] == 1) {
92 my $name = $shlibs->{$_}->[0];
94 # Preserve `Self` deptype of command-line target
95 $mod{$name} = $results->{$name};
96 $mod{$name}->{'RequiredBy'} = {%{$reqby->{$_}}, %{$reqby->{$name}}};
98 # Avoid overriding provides with later target (random ordering of hash!)
99 elsif (not defined $mod{$_}) {
100 $mod{$_} = $results->{$_};
101 $mod{$_}->{'RequiredBy'} = $reqby->{$_};
104 $results->{$_} ne 'None'
107 # Merge reverse dependencies for purely virtual targets, with
108 # corresponding packages specified on the command-line
110 my $name = $shlibs->{$_}[0];
112 for my $deptype (keys %{$reqby->{$_}}) {
113 push(@
{$mod{$name}->{'RequiredBy'}{$deptype}}, @
{$reqby->{$_}{$deptype}});
116 $results->{$_} eq 'None' and defined $shlibs->{$_}
119 # Add reverse dependencies for any remaining targets
122 $mod{$_}->{'Name'} = $_;
123 $mod{$_}->{'RequiredBy'} = $reqby->{$_};
125 $results->{$_} eq 'None'
129 # Remove `None` reverse dependencies (#1062)
133 $results->{$_} eq 'None'
137 return \
%mod, $reqby, $shlibs;
140 # Recursively remove nodes from dependency graph (#592)
141 # Operates on modified graph: provides are solved first
143 my ($mod, $installed) = @_;
145 # XXX: use Schwartzian transform
147 # Every reverse dependency needs to be checked against every pkgname
148 # that is assumed to be installed (quadratic complexity)
149 my $reqby = $mod->{$_}->{'RequiredBy'}; # reference to RequiredBy{ Depends [] }
151 for my $deptype (keys %{$reqby}) { # list returned by keys is a copy
154 for my $dep (@
{$reqby->{$deptype}}) {
155 my $found = first
{ $dep eq $_ } @
{$installed};
157 if (not defined ($found)) {
161 if (scalar @pruned) {
162 @
{$reqby->{$deptype}} = @pruned;
165 delete $reqby->{$deptype};
172 if (not scalar keys %{$mod->{$name}->{'RequiredBy'}}) {
173 delete $mod->{$name}; # remove targets that are no longer required
175 my $found = first
{ $name eq $_ } @
{$installed};
176 if (defined $found) {
177 delete $mod->{$name}; # remove targets that are installed
179 } keys %{$mod}; # list returned by keys is a copy
184 # tsv output for usage with aur-sync (aurutils <=10)
185 sub table_v10_compat
{
186 my ($results, $types) = @_;
189 my ($name, $base, $version) = ($_->{'Name'}, $_->{'PackageBase'}, $_->{'Version'});
190 say join("\t", $name, $name, $base, $version, 'Self');
192 for my $deptype (@
{$types}) {
193 my $depends = $_->{$deptype};
194 next if (ref($depends) ne 'ARRAY');
196 for my $dep (@
{$depends}) {
197 say join("\t", $name, $dep, $base, $version, $deptype);
200 } grep { defined $_->{'PackageBase'} } values %{$results};
203 # tsv output for usage with aur-sync (aurutils >=11)
207 for my $pkg (values %{$results}) {
208 my ($name, $base, $version) = ($pkg->{'Name'}, $pkg->{'PackageBase'}, $pkg->{'Version'});
210 for my $deptype (keys %{$pkg->{'RequiredBy'}}) {
212 say join("\t", $name, $_, $base // '-', $version // '-', $deptype);
213 } @
{$pkg->{'RequiredBy'}{$deptype}};
218 # package/dependency pairs for use with tsort(1) or aur-graph
219 # XXX: include optional column for versioned dependencies
221 my ($results, $key, $reverse) = @_;
223 for my $pkg (values %{$results}) {
224 my $target = $pkg->{$key};
226 for my $reqby (values %{$pkg->{'RequiredBy'}}) {
228 my $rdep = $key eq 'Name' ?
$_ : $results->{$_}->{$key} // '-';
229 my @pair = $reverse ?
($target, $rdep) : ($rdep, $target);
231 say join("\t", @pair);
240 my $opt_makedepends = 1;
241 my $opt_checkdepends = 1;
242 my $opt_optdepends = 0;
243 my $opt_mode = "pairs";
245 my $opt_show_all = 0; # implies $opt_pkgname = 1
247 my $opt_provides = 1;
248 my $opt_installed = [];
251 'assume-installed=s' => $opt_installed,
252 'no-depends' => sub { $opt_depends = 0 },
253 'no-makedepends' => sub { $opt_makedepends = 0 },
254 'no-checkdepends' => sub { $opt_checkdepends = 0 },
255 'optdepends' => \
$opt_optdepends,
256 'no-provides' => sub { $opt_provides = 0 },
257 'n|pkgname' => \
$opt_pkgname,
258 'b|pkgbase' => sub { $opt_pkgname = 0 },
259 'G|graph' => sub { }, # noop
260 't|table' => sub { $opt_mode = "table" },
261 'J|json' => sub { $opt_mode = "json" },
262 'jsonl' => sub { $opt_mode = "jsonl" },
263 'r|reverse' => \
$opt_reverse,
264 'a|all|show-all' => \
$opt_show_all
267 if (not scalar(@ARGV)) {
268 say STDERR
"$argv0: at least one argument required";
272 # Handle '-' to take packages from stdin
273 add_from_stdin
(\
@ARGV, ['-', '/dev/stdin']);
275 # Exit gracefully on empty stdin, e.g. when piping from `aur repo -u`
276 exit(0) if not scalar(@ARGV);
278 # Variable dependency types (#826)
280 push(@types, 'Depends') if $opt_depends;
281 push(@types, 'MakeDepends') if $opt_makedepends;
282 push(@types, 'CheckDepends') if $opt_checkdepends;
283 push(@types, 'OptDepends') if $opt_optdepends;
285 # Array notation for `--assume-installed`
286 @
{$opt_installed} = map { split(',', $_) } @
{$opt_installed};
288 # Resolve dependency tree
289 my ($results, $reqby, $shlibs) = chain_mod
(chain
(\
@ARGV, \
@types, 30), $opt_provides, $opt_show_all);
291 # Remove virtual dependencies with `--show-all` (#1063)
292 if ($opt_show_all and $opt_provides) {
293 # XXX: also include `None` dependencies with `Self` RequiredBy
295 $_ ne $shlibs->{$_}->[0] and $shlibs->{$_}->[1] > 0
298 $results = prune
($results, \
@virtual);
301 # Remove transitive dependencies for installed targets (#592)
302 if (scalar @
{$opt_installed}) {
303 $results = prune
($results, $opt_installed);
307 if ($opt_mode eq 'pairs') {
308 pairs
($results, ($opt_pkgname or $opt_show_all) ?
'Name' : 'PackageBase', $opt_reverse);
310 elsif ($opt_mode eq 'table' and $opt_reverse) {
313 elsif ($opt_mode eq 'table') {
314 table_v10_compat
($results, \
@types);
316 elsif ($opt_mode eq 'json') {
317 say write_json
($results);
319 elsif ($opt_mode eq 'jsonl') {
320 map { say write_json
$results->{$_} } keys %{$results};
323 say STDERR
"$argv0: invalid mode selected";
328 # vim: set et sw=4 sts=4 ft=perl: