summaryrefslogtreecommitdiffstats
path: root/lib/Lintian/Pool.pm
diff options
context:
space:
mode:
Diffstat (limited to 'lib/Lintian/Pool.pm')
-rw-r--r--lib/Lintian/Pool.pm412
1 files changed, 412 insertions, 0 deletions
diff --git a/lib/Lintian/Pool.pm b/lib/Lintian/Pool.pm
new file mode 100644
index 0000000..db153f9
--- /dev/null
+++ b/lib/Lintian/Pool.pm
@@ -0,0 +1,412 @@
+# Copyright (C) 2011 Niels Thykier <niels@thykier.net>
+# Copyright (C) 2020-2021 Felix Lechner
+#
+# 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, you can find it on the World Wide
+# Web at https://www.gnu.org/copyleft/gpl.html, or write to the Free
+# Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+
+## Represents a pool of processables (Lintian::Processable)
+package Lintian::Pool;
+
+use v5.20;
+use warnings;
+use utf8;
+
+use Const::Fast;
+use Cwd qw(getcwd);
+use List::SomeUtils qw(any);
+use Time::HiRes qw(gettimeofday tv_interval);
+use Path::Tiny;
+use POSIX qw(:sys_wait_h);
+use Proc::ProcessTable;
+use Unicode::UTF8 qw(encode_utf8);
+
+use Lintian::Group;
+
+const my $SPACE => q{ };
+const my $COMMA => q{,};
+const my $SEMICOLON => q{;};
+const my $LEFT_PARENS => q{(};
+const my $RIGHT_PARENS => q{)};
+const my $PLURAL_S => q{s};
+
+const my $ANY_CHILD => -1;
+const my $WORLD_WRITABLE_FOLDER => oct(777);
+
+use Moo;
+use namespace::clean;
+
+=head1 NAME
+
+Lintian::Pool -- Pool of processables
+
+=head1 SYNOPSIS
+
+ use Lintian::Pool;
+
+ my $pool = Lintian::Pool->new;
+ $pool->add_file('foo.changes');
+ $pool->add_file('bar.dsc');
+ $pool->add_file('baz.deb');
+ $pool->add_file('qux.buildinfo');
+ foreach my $gname ($pool->get_group_names){
+ my $group = $pool->get_group($gname);
+ process($gname, $group);
+ }
+
+=head1 METHODS
+
+=over 4
+
+=item $pool->groups
+
+Returns a hash reference to the list of processable groups that are currently
+in the pool. The key is a unique identifier based on name and version.
+
+=item C<savedir>
+
+=cut
+
+has groups => (is => 'rw', default => sub{ {} });
+
+has savedir => (is => 'rw', default => sub{ getcwd; });
+
+# must be absolute; frontend/lintian depends on it
+has basedir => (
+ is => 'rw',
+ default => sub {
+
+ my $absolute
+ = Path::Tiny->tempdir(TEMPLATE => 'lintian-pool-XXXXXXXXXX');
+
+ $absolute->mkpath({mode => $WORLD_WRITABLE_FOLDER});
+
+ return $absolute;
+ }
+);
+
+=item $pool->basedir
+
+Returns the base directory for the pool. Most likely it's a temporary directory.
+
+=item $pool->add_group($group)
+
+Adds a group to the pool.
+
+=cut
+
+sub add_group {
+ my ($self, $group) = @_;
+
+ my $name = $group->name;
+
+ unless (exists $self->groups->{$name}){
+
+ # group does not exist; just add whole
+ $self->groups->{$name} = $group;
+
+ return 1;
+ }
+
+ # group exists; merge & accept all new
+ my $added = 0;
+
+ my $old = $self->groups->{$name};
+
+ for my $type (qw/source buildinfo changes/) {
+
+ if (!defined $old->$type && defined $group->$type) {
+ $old->add_processable($group->$type);
+ $added = 1;
+ }
+ }
+
+ for my $installable ($group->get_installables){
+ # New binary package ?
+ my $was_new = $old->add_processable($installable);
+ $added ||= $was_new;
+ }
+
+ return $added;
+}
+
+=item $pool->process
+
+Process the pool.
+
+=cut
+
+sub process{
+ my ($self, $PROFILE, $exit_code_ref, $option)= @_;
+
+ if ($self->empty) {
+ say {*STDERR} encode_utf8('No packages selected.');
+ return;
+ }
+
+ my %reported_count;
+ my %override_count;
+ my %ignored_overrides;
+ my $unused_overrides = 0;
+
+ for my $group (values %{$self->groups}) {
+
+ my $total_start = [gettimeofday];
+
+ $group->profile($PROFILE);
+ $group->jobs($option->{'jobs'});
+
+ my $success= $group->process(\%ignored_overrides, $option);
+
+ for my $processable ($group->get_processables){
+
+ my @keep;
+ for my $hint (@{$processable->hints}) {
+
+ my $tag = $PROFILE->get_tag($hint->tag_name);
+
+ # discard experimental tags
+ next
+ if $tag->experimental
+ && !$option->{'display-experimental'};
+
+ # discard overridden tags
+ next
+ if defined $hint->override
+ && !$option->{'show-overrides'};
+
+ # discard outside the selected display level
+ next
+ unless $PROFILE->display_level_for_tag($hint->tag_name);
+
+ if (!defined $hint->override) {
+
+ ++$reported_count{$tag->visibility}
+ if !$tag->experimental;
+
+ ++$reported_count{experimental}
+ if $tag->experimental;
+ }
+
+ ++$reported_count{override}
+ if defined $hint->override;
+
+ ++$unused_overrides
+ if $hint->tag_name eq 'unused-override'
+ || $hint->tag_name eq 'mismatched-override';
+
+ push(@keep, $hint);
+ }
+
+ $processable->hints(\@keep);
+ }
+
+ ${$exit_code_ref} = 2
+ if $success && any { $reported_count{$_} } @{$option->{'fail-on'}};
+
+ # interruptions can leave processes behind (manpages); wait and reap
+ if (${$exit_code_ref} == 1) {
+ 1 while waitpid($ANY_CHILD, WNOHANG) > 0;
+ }
+
+ if ($option->{debug}) {
+ my $process_table = Proc::ProcessTable->new;
+ my @leftover= grep { $_->ppid == $$ } @{$process_table->table};
+
+ # announce left over processes, see commit 3bbcc3b
+ if (@leftover) {
+ warn encode_utf8(
+ "\nSome processes were left over (maybe unreaped):\n");
+
+ my $FORMAT = ' %-12s %-12s %-8s %-24s %s';
+ say encode_utf8(
+ sprintf(
+ $FORMAT,'PID', 'TTY', 'STATUS', 'START', 'COMMAND'
+ )
+ );
+
+ say encode_utf8(
+ sprintf($FORMAT,
+ $_->pid,$_->ttydev,
+ $_->state,scalar(localtime($_->start)),
+ $_->cmndline)
+ )for @leftover;
+
+ ${$exit_code_ref} = 1;
+ die encode_utf8("Aborting.\n");
+ }
+ }
+
+ my $total_raw_res = tv_interval($total_start);
+ my $total_tres = sprintf('%.3fs', $total_raw_res);
+
+ my $status = $success ? 'complete' : 'error';
+ say {*STDERR}
+ encode_utf8($status . $SPACE . $group->name . " ($total_tres)")
+ if $option->{'status-log'};
+ say {*STDERR} encode_utf8('Finished processing group ' . $group->name)
+ if $option->{debug};
+
+ ${$exit_code_ref} = 1
+ unless $success;
+ }
+
+ my $OUTPUT;
+ if ($option->{'output-format'} eq 'html') {
+ require Lintian::Output::HTML;
+ $OUTPUT = Lintian::Output::HTML->new;
+ } elsif ($option->{'output-format'} eq 'json') {
+ require Lintian::Output::JSON;
+ $OUTPUT = Lintian::Output::JSON->new;
+ } elsif ($option->{'output-format'} eq 'universal') {
+ require Lintian::Output::Universal;
+ $OUTPUT = Lintian::Output::Universal->new;
+ } else {
+ require Lintian::Output::EWI;
+ $OUTPUT = Lintian::Output::EWI->new;
+ }
+
+ # pass everything, in case some groups or processables have no hints
+ $OUTPUT->issue_hints($PROFILE, [values %{$self->groups}], $option);
+
+ my $errors = $override_count{error} // 0;
+ my $warnings = $override_count{warning} // 0;
+ my $info = $override_count{info} // 0;
+ my $total = $errors + $warnings + $info;
+
+ if ( $option->{'output-format'} eq 'ewi'
+ && !$option->{'no-override'}
+ && !$option->{'show-overrides'}
+ && ($total > 0 || $unused_overrides > 0)) {
+
+ my @details;
+ push(@details, quantity($errors, 'error'))
+ if $errors;
+ push(@details, quantity($warnings, 'warning'))
+ if $warnings;
+ push(@details, "$info info")
+ if $info;
+
+ my $text = quantity($total, 'hint') . ' overridden';
+ $text
+ .= $SPACE
+ . $LEFT_PARENS
+ . join($COMMA . $SPACE, @details)
+ . $RIGHT_PARENS
+ if @details;
+ $text
+ .= $SEMICOLON
+ . $SPACE
+ . quantity($unused_overrides, 'unused override');
+
+ say encode_utf8("N: $text");
+ }
+
+ if ($option->{'output-format'} eq 'ewi' && %ignored_overrides) {
+ say encode_utf8('N: Some overrides were ignored.');
+
+ if ($option->{verbose}) {
+ say encode_utf8(
+'N: The following tags had at least one override but are mandatory:'
+ );
+ say encode_utf8("N: - $_") for sort keys %ignored_overrides;
+
+ } else {
+ say encode_utf8('N: Use --verbose for more information.');
+ }
+ }
+
+ path($self->basedir)->remove_tree
+ if length $self->basedir && -d $self->basedir;
+
+ return;
+}
+
+=item quantity
+
+=cut
+
+sub quantity {
+ my ($count, $unit) = @_;
+
+ my $text = $count . $SPACE . $unit;
+ $text .= $PLURAL_S
+ unless $count == 1;
+
+ return $text;
+}
+
+=item $pool->get_group_names
+
+Returns the name of all the groups in this pool.
+
+Do not modify the list nor its contents.
+
+=cut
+
+sub get_group_names{
+ my ($self) = @_;
+
+ return keys %{ $self->groups };
+}
+
+=item $pool->get_group($name)
+
+Returns the group called $name or C<undef>
+if there is no group called $name.
+
+=cut
+
+sub get_group{
+ my ($self, $group) = @_;
+
+ return $self->groups->{$group};
+}
+
+=item $pool->empty
+
+Returns true if the pool is empty.
+
+=cut
+
+sub empty{
+ my ($self) = @_;
+
+ return scalar keys %{$self->groups} == 0;
+}
+
+=back
+
+=head1 AUTHOR
+
+Originally written by Niels Thykier <niels@thykier.net> for Lintian.
+
+=head1 SEE ALSO
+
+lintian(1)
+
+L<Lintian::Processable>
+
+L<Lintian::Group>
+
+=cut
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et