#!/usr/bin/perl -C63

eval 'exec /usr/bin/perl -C63 -S $0 ${1+"$@"}'
    if 0; # not running under some shell
#
# rpmgrill - script description here FIXME
#
# $Id$
#
package RPM::Grill::Checker;

use strict;
use warnings;

(our $ME = $0) =~ s|.*/||;
our $VERSION = "0.01";

# For debugging, show data structures using DumpTree($var)
#use Data::TreeDumper; $Data::TreeDumper::Displayaddress = 0;

###############################################################################
# BEGIN user-customizable section

#
# FIXME: need something that will unpack an RPM set
#
# Our called script can assume:
#
#   params - N-V-R, etc
#   environment
#   cd'ed to WHERE

# Set up safe, well-known environment
$ENV{PATH}   = '/usr/bin:/bin';
$ENV{CDPATH} = '';
$ENV{IFS}    = '';

# END   user-customizable section
###############################################################################

use RPM::Grill;
use List::Util          qw(max);
use utf8;

###############################################################################
# BEGIN boilerplate args checking, usage messages

sub usage {
    print  <<"END_USAGE";
Usage: $ME [OPTIONS] ARGS [...]

$ME analyzes a set of RPMs (typically from a Koji build),
looking for nits to pick. Unlike rpmlint, $ME analyzes
*full builds*.

$ME is not intended to be run by mortals. It requires a
pre-set-up set of extracted RPMs and metadata.

OPTIONS:

  --list-plugins     list available plugins
  --disable=[LIST]   disable one or more plugins, e.g.: --disable=virus,buildlog

  -v, --verbose  show verbose progress indicators
  -n, --dry-run  make no actual changes

  --help         display this message
  --man          display program man page
  --version      display program name and version
END_USAGE

    exit;
}

sub man {
    # Read the POD contents.  If it hasn't been filled in yet, abort.
    my $pod = do { local $/; <DATA>; };
    if ($pod =~ /=head1 \s+ NAME \s+ FIXME/xm) {
        warn "$ME: No man page available.  Please try $ME --help\n";
        exit;
    }

    # Use Pod::Man to convert our __DATA__ section to *roff
    eval { require Pod::Man }
        or die "$ME: Cannot generate man page; Pod::Man unavailable: $@\n";
    my $parser = Pod::Man->new(name => $ME, release => $VERSION, section => 1);

    # If called without output redirection, man-ify.
    my $out_fh;
    if (-t *STDOUT) {           ## no critic
        my $pager = $ENV{MANPAGER} || $ENV{PAGER} || 'less';
        open $out_fh, "| nroff -man | $pager";  ## no critic
    }
    else {
        open $out_fh, '>&', \*STDOUT;
    }

    # Read the POD contents, and have Pod::Man read from fake filehandle.
    # This requires 5.8.0.
    open my $pod_handle, '<', \$pod;
    $parser->parse_from_filehandle($pod_handle, $out_fh);
    exit;
}


# Command-line options.  Note that this operates directly on @ARGV !
our $debug   = 0;
our $force   = 0;
our $verbose = 0;
our $NOT     = '';              # print "blahing the blah$NOT\n" if $debug
our $list_plugins;              # list plugins, and quit
our @disable_plugin;            # plugins to disable
sub handle_opts {
    use Getopt::Long;
    GetOptions(
        'debug!'     => \$debug,
        'dry-run|n!' => sub { $NOT = ' [NOT]' },
        'force'      => \$force,
        'verbose|v'  => \$verbose,

        'list-plugins' => \$list_plugins,
        'disable=s'  => \@disable_plugin,

        help         => \&usage,
        man          => \&man,
        version      => sub { print "$ME version $VERSION\n"; exit 0 },
    ) or die "Try `$ME --help' for help\n";

    if ($list_plugins) {
        my $maxlen = max map {
            my $tmp = $_;
            $tmp =~ s/^.*:://;
            length($tmp);
        } RPM::Grill->plugins;

        print "Available $ME plugins, in the order in which they run:\n\n";
        for my $plugin (RPM::Grill->plugins) {
            (my $shortname = $plugin) =~ s/^.*:://;

            printf "  %-*s %s\n", $maxlen, $shortname, $plugin->blurb();
        }
        exit;
    }

    # Validate the disable opt: each arg must be an existing plugin
    if (@disable_plugin) {
        # Handle comma-separated list as input, eg 'virus,patches'
        my @tmp = split(',',join(',',@disable_plugin));

        @disable_plugin = RPM::Grill->matching_plugins(@tmp);
        # @disable_plugin now contains exact, canonical plugin names
    }
}

# END   boilerplate args checking, usage messages
###############################################################################

############################## CODE BEGINS HERE ###############################

# The term is "modulino".
__PACKAGE__->main()                                     unless caller();

# Main code.
sub main {
    # Note that we operate directly on @ARGV, not on function parameters.
    # This is deliberate: it's because Getopt::Long only operates on @ARGV
    # and there's no clean way to make it use @_.
    handle_opts();                      # will set package globals

    # Fetch command-line arguments.  Barf if too many.
    my $base_dir = shift(@ARGV)
        or die "$ME: missing BASEDIR argument; try $ME --help\n";
    die "$ME: Too many arguments; try $ME --help\n"                 if @ARGV;

    -d $base_dir
        or die "$ME: Directory does not exist: $base_dir\n";
    -d "$base_dir/src"
        or die "$ME: Base directory $base_dir does not have a 'src' subdir\n";

    # Find all plugins. Do this before cd'ing, in case we have relative
    # paths in our @INC.
    my @all_plugins = RPM::Grill->plugins();

    # We now have the path to a directory containing unpacked RPMs.
    # cd into it, so our output files (intentional as well as un)
    # will be in one uncluttered place. (unintentional files may
    # be the result of running 'rpm -qi --specfile').
    chdir $base_dir
        or die "$ME: Cannot cd $base_dir: $!\n";

    #
    # This is what does all the work!
    #
    my $grill = RPM::Grill->new('.');
    for my $plugin (@all_plugins) {
        $grill->invoke_plugin( $plugin )
            unless grep { $plugin =~ /::$_$/ } @disable_plugin;
    }

    #
    # Done. Write XML, YAML, and JSON output files for our caller.
    #
    for my $format (qw(json xml yaml)) {
        my $outfile = "$ME.$format";
        my $tmpfile = "$outfile.tmp.$$";
        unlink $tmpfile;

        open my $fh_out, '>', $tmpfile
            or die "$ME: Cannot create $tmpfile: $!\n";
        print { $fh_out } $grill->results_as($format);
        close $fh_out
            or die "$ME: Cannot error writing $tmpfile: $!\n";
        chmod 0444 => $tmpfile;
        rename $tmpfile => $outfile
            or die "$ME: Cannot rename $tmpfile: $!\n";
    }
}


1;

__DATA__

###############################################################################
#
# Documentation
#

=head1	NAME

rpmgrill - static analysis of koji builds

=head1	SYNOPSIS

rpmgrill [--disable=LIST] [--verbose] BASEDIR

rpmgrill  B<--help>  |  B<--version> | B<--man>

=head1	DESCRIPTION

rpmgrill analyzes a set of RPMs (typically from a Koji build),
looking for problems with the source and/or generated RPMs.
Unlike rpmlint, rpmgrill analyzes B<full builds>, not individual
RPMs.

rpmgrill is not intended to be run directly by humans: it requires
complex prep work to fetch and unpack the build RPMs. rpmgrill is
best suited toward a CI environment in which other tools monitor
builds, fetch them, run rpmgrill, and report results.

If you are reading this as a casual user, you are almost certainly
better off looking into B<rpmgrill-analyze-local>.

=head1	OPTIONS

=over 4

=item B<--list-plugins>

List available plugins, then exit.

=item B<--disable=LIST>

Disable the given plugins when running. LIST is a comma-separated list
of plugin names as listed with C<--list-plugins>. This can be useful
for development, when you might want to do quick iterations of a test
but not spend 10 minutes in the VirusCheck plugin.

=item B<--verbose>

Show progress messages.

=item B<--help>

Emit usage hints.

=item B<--version>

Display program version.

=item B<--man>

Display this man page.

=back


=head1	DIAGNOSTICS

rpmgrill writes its results to the files C<rpmgrill.*> in its
working directory, for C<*> in json, xml, yaml. Unexpected errors
(probably bugs or FIXMEs in rpmgrill) are emitted to stderr.

=head1	FILES

rpmgrill requires a pre-set-up directory hierarchy containing
unpacked RPMs. Documenting that is way beyond the scope of this
man page. See README.unpacking in the rpmgrill source tree,
or the rpmgrill-fetch-build tool, for more details.

=head1	SEE ALSO

rpmgrill-analyze-local, rpmgrill-fetch-build, rpmgrill-unpack-rpms

=head1	AUTHOR

Ed Santiago <santiago@redhat.com>

Please report bugs or suggestions on
L<bugzilla|https://bugzilla.redhat.com/enter_bug.cgi?product=Fedora&component=rpmgrill>

=cut
