The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/perl -w

eval 'exec /usr/bin/perl -w -S $0 ${1+"$@"}'
    if 0; # not running under some shell
use strict;
$|=1;

# $Id: smokeperl.pl 1363 2012-05-10 21:21:26Z abeltje $
use vars qw( $VERSION );
$VERSION = Test::Smoke->VERSION;

use Cwd;
use File::Spec::Functions qw(:DEFAULT rel2abs);
use File::Path;
use File::Copy;
my $findbin;
use File::Basename;
BEGIN { $findbin = rel2abs(dirname $0); }
use lib catdir($findbin, 'lib');
use lib catdir($findbin, 'lib', 'inc');
use lib $findbin;
use lib catdir($findbin, 'inc');

use Config;
use Test::Smoke::Syncer;
use Test::Smoke::Patcher;
use Test::Smoke;
use Test::Smoke::Reporter;
use Test::Smoke::Mailer;
use Test::Smoke::Util qw( get_patch calc_timeout do_pod2usage );

use Getopt::Long;
Getopt::Long::Configure( 'pass_through' );
my %options = (
    config       => 'smokecurrent_config',
    run          => 1,
    pfile        => undef,
    fetch        => 1,
    patch        => 1,
    mail         => undef,
    smokedb_url  => undef,
    send_out     => "never",
    send_log     => "on_fail",
    archive      => undef,
    continue     => 0,
    ccp5p_onfail => undef,
    killtime     => undef,
    is56x        => undef,
    defaultenv   => undef,
    smartsmoke   => undef,
    delay_report => undef,
    v            => undef,
    cfg          => undef
);

my $myusage = "Usage: $0 [-c configname]";
GetOptions( \%options, 
    'config|c=s', 
    'fetch!', 
    'patch!',
    'ccp5p_onfail!',
    'mail!',
    'smokedb_url=s',
    'delay_report!',
    'run!',
    'archive!',
    'is56x',
    'defaultenv!',
    'continue!',
    'smartsmoke!',
    'patchlevel=i',
    'snapshot|s=i',
    'killtime=s',
    'pfile=s',
    'cfg=s',

    'v|verbose=i',
    'help|h', 'man',
) or do_pod2usage(  verbose => 1, myusage => $myusage );

$options{ man} 
    and do_pod2usage( verbose => 2, exitval => 0, myusage => $myusage );
$options{help} 
    and do_pod2usage( verbose => 1, exitval => 0, myusage => $myusage );

=head1 NAME

smokeperl.pl - The perl Test::Smoke suite

=head1 SYNOPSIS

    $ ./smokeperl.pl [-c configname]

or

    C:\smoke>perl smokeperl.pl [-c configname]

=head1 OPTIONS

It can take these options

  --config|-c <configname> See configsmoke.pl (smokecurrent_config)
  --nofetch                Skip the synctree step
  --nopatch                Skip the patch step
  --nomail                 Skip the mail step
  --noarchive              Skip the archive step (if applicable)
  --[no]ccp5p_onfail       Do (not) send failure reports to perl5-porters
  --[no]delay_report       Do (not) create the report now

  --[no]continue           Try to continue an interrupted smoke
  --is56x                  This is a perl-5.6.x smoke
  --defaultenv             Run a smoke in the default environment
  --[no]smartsmoke         Don't smoke unless patchlevel changed
  --patchlevel <plevel>    Set old patchlevel for --smartsmoke --nofetch
  --snapshot <patchlevel>  Set a new patchlevel for snapshot smokes
  --killtime (+)hh::mm     (Re)set the guard-time for this smoke

  --pfile <patchesfile>    Set a patches-to-apply-file
  --cfg <buildcfg>         Set a Build Configurations File

All other arguments are passed to F<Configure>!

=head1 DESCRIPTION

F<smokeperl.pl> is the main program in this suite. It combines all the
front-ends internally and does some sanity checking.

=cut

# Try cwd() first, then $findbin
my $config_file = catfile( cwd(), $options{config} );
unless ( read_config( $config_file ) ) {
    $config_file = catfile( $findbin, $options{config} );
    read_config( $config_file );
}
defined Test::Smoke->config_error and 
    die "!!!Please run 'configsmoke.pl'!!!\nCannot find configuration: $!";

# smartsmoke doesn't make sense with nofetch (unless you say so)
defined $options{fetch} && !$options{fetch} && !defined $options{smartsmoke}
    and $options{smartsmoke} = 0;

# Correction for backward compatability
!defined $options{ $_ } && !exists $conf->{ $_ } and $options{ $_ } = 1
    for qw( run fetch patch archive v );
!defined $options{ $_ } && !exists $conf->{ $_ } and $options{ $_ } = 0
    for qw( delay_report mail );

# Make command-line options override configfile
defined $options{ $_ } and $conf->{ $_ } = $options{ $_ }
    for qw( is56x defaultenv continue killtime pfile cfg delay_report v run
            smartsmoke fetch patch mail smokedb_url ccp5p_onfail archive );

# Make sure the --pfile command-line override works
$options{pfile} and $conf->{patch_type} ||= 'multi';

$options{transport} or $conf->{transport_url} = undef;

if ( $options{continue} ) {
    $options{v} and print "Will try to continue current smoke\n";
} else {
    synctree();
    patchtree();
}

my $cwd = cwd();
chdir $conf->{ddir} or die "Cannot chdir($conf->{ddir}): $!";
call_mktest( $cwd );
chdir $cwd;
unless ( $conf->{delay_report} ) {
    sendrpt(genrpt());
} else {
    $conf->{v} and print "Delayed creation of the report. See 'mailrpt.pl'\n";
}
archiverpt();

sub synctree {
    my $now_patchlevel = get_patch( $conf->{ddir} )->[0] || -1;
    my $was_patchlevel = $options{smartsmoke} && $options{patchlevel}
        ? $options{patchlevel}
        : $now_patchlevel;
    FETCHTREE: {
        unless ( $options{fetch} && $options{run} ) {
            $conf->{v} and print "Skipping synctree\n";
            last FETCHTREE;
        }
        # TODO: It might be very useful to do a 'make distclean'
        # *before* syncing the tree, as the sync might update MANIFEST
        # and make distclean will not see leftovers
        if ( $options{snapshot} ) {
            if ( $conf->{sync_type} eq 'snapshot' ||
               ( $conf->{sync_type} eq 'forest'   && 
                 $conf->{fsync} eq 'snapshot' ) ) {

                $conf->{sfile} = snapshot_name();
            } else {
                die "<--snapshot> is not valid now, please reconfigure!";
            }
            $conf->{sfile} = snapshot_name();
        }
        my $syncer = Test::Smoke::Syncer->new( $conf->{sync_type}, $conf );
        $now_patchlevel = $syncer->sync;
        $conf->{v} and 
            print "$conf->{ddir} now up to patchlevel $now_patchlevel\n";
    }

    if ( $conf->{smartsmoke} && ($was_patchlevel eq $now_patchlevel) ) {
        $conf->{v} and 
            print "Skipping this smoke, patchlevel ($was_patchlevel)" .
                  " did not change.\n";
        exit(0);
    }
}

sub patchtree {
    PATCHAPERL: {
        unless ( $options{patch} && $options{run} ) {
            $conf->{v} && exists $conf->{patch_type} &&
            $conf->{patch_type} eq 'multi' and
                print "Skipping patching ($conf->{pfile})\n";
            last PATCHAPERL;
        }
        last PATCHAPERL unless exists $conf->{patch_type} && 
                               $conf->{patch_type} eq 'multi' && 
                               $conf->{pfile};
        my $patcher = Test::Smoke::Patcher->new( $conf->{patch_type}, $conf );
        eval { $patcher->patch };
    }
}

sub call_mktest {
    my $cwd = shift;
    my $timeout = 0;
    if ( $Config{d_alarm} && $conf->{killtime} ) {
        $timeout = calc_timeout( $conf->{killtime} );
        $conf->{v} and printf "Setup alarm: %s\n",
                              scalar localtime( time() + $timeout );
    }
    $timeout and local $SIG{ALRM} = sub {
        warn "This smoke is aborted ($conf->{killtime})\n";
        chdir $cwd;
        sendrpt(genrpt());
        exit(42);
    };
    $Config{d_alarm} and alarm $timeout;

    run_smoke( $conf->{continue}, @ARGV );
}

sub genrpt {
    return unless $options{run};
    my $reporter = Test::Smoke::Reporter->new( $conf );
    $reporter->write_to_file;
    return $reporter;
}

sub sendrpt {
    my $reporter = shift;

    unless ( $options{run} ) {
        $conf->{v} and print "Skipping mailrpt and transport\n";
        return;
    }

    if ( $conf->{mail} ) {
        my $mailer = Test::Smoke::Mailer->new( $conf->{mail_type}, $conf );
        $mailer->mail or warn "[$conf->{mail_type}] " . $mailer->error;
    }
    else {
        $conf->{v} and print "Skipping mailrpt\n";
    }

    if ($conf->{smokedb_url}) {
        require LWP::UserAgent;
        my $ua = LWP::UserAgent->new(
            agent => "Test::Smoke/$Test::Smoke::VERSION",
        );
        $conf->{v} and print "Posting to SmokeDB ($conf->{smokedb_url})\n";
        my $response = $ua->post(
            $conf->{smokedb_url},
            {json => $reporter->smokedb_data}
        );
        $conf->{v} and print $response->content;
    }
    else {
        $conf->{v} and print "Skipping smokedb_send\n";
    }
}

sub archiverpt {
    return unless $conf->{archive};
    return unless exists $conf->{adir};
    return if $conf->{adir} eq "";
    -d $conf->{adir} or do {
        mkpath( $conf->{adir}, 0, 0775 ) or 
            die "Cannot create '$conf->{adir}': $!";
    };

    my $patch_level = get_patch( $conf->{ddir} )->[0];
    $patch_level =~ tr/ //sd;

    SKIP_RPT: {
        my $archived_rpt = "rpt${patch_level}.rpt";
        # Do not archive if it is already done
        last SKIP_RPT
            if -f catfile( $conf->{adir}, $archived_rpt );

        copy( catfile( $conf->{ddir}, 'mktest.rpt' ),
              catfile( $conf->{adir}, $archived_rpt ) ) or
            die "Cannot copy to '$archived_rpt': $!";
    }

    SKIP_OUT: {
        my $archived_out = "out${patch_level}.out";
        # Do not archive if it is already done
        last SKIP_OUT
            if -f catfile( $conf->{adir}, $archived_out );

        copy( catfile( $conf->{ddir}, 'mktest.out' ),
              catfile( $conf->{adir}, $archived_out ) ) or
            die "Cannot copy to '$archived_out': $!";
    }

    SKIP_LOG: {
        my $archived_log = "log${patch_level}.log";
        last SKIP_LOG unless defined $conf->{lfile};
        last SKIP_LOG 
            unless -f $conf->{lfile};
        copy( $conf->{lfile},
              catfile( $conf->{adir}, $archived_log ) ) or
            die "Cannot copy to '$archived_log': $!";
    }

    SKIP_JSN: {
        my $archived_jsn = "out${patch_level}.jsn";
        last SKIP_JSN
            if -f catfile($conf->{adir}, $archived_jsn);
        copy(
            catfile($conf->{ddir}, 'mktest.jsn'),
            catfile($conf->{adir}, $archived_jsn)
        ) or die "Cannot copy to $archived_jsn: $!";
    }
}

sub snapshot_name {
    my( $plevel ) = $options{snapshot} =~ /(\d+)/;
    my $sfile = $conf->{sfile};
    if ( $sfile ) {
        $sfile =~ s/([-@])\d+\./$1$plevel./;
    } else {
        my $sep = $conf->{is56x} ? '562-' : '@';
        my $ext = $conf->{snapext} || 'tar.gz';
        $sfile = "perl${sep}${plevel}.$ext";
    }
    return $sfile;
}

=head1 SEE ALSO

L<README>, L<FAQ>, L<configsmoke.pl>, L<mktest.pl>, L<mkovz.pl>

=head1 REVISION

$Id: smokeperl.pl 1363 2012-05-10 21:21:26Z abeltje $

=head1 COPYRIGHT

(c) 2002-2003, All rights reserved.

  * Abe Timmerman <abeltje@cpan.org>

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.

See:

=over 4

=item * L<http://www.perl.com/perl/misc/Artistic.html>

=item * L<http://www.gnu.org/copyleft/gpl.html>

=back

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.

=cut