#!perl
use strict;
use warnings;
use Carp qw(carp croak);
use FindBin qw($RealBin);
use File::Spec::Functions;
use File::Path;
use File::Copy;
use lib 'libcpan';
use Data::Dump qw(dump);
use File::Copy::Recursive qw(dircopy);
use lib 'lib';
use Watchdog qw(sys sys_for_watchdog);
use SVNShell qw(svnversion svnup);
use lib "$FindBin::Bin/lib";
use Term::ReadKey;
ReadMode('cbreak');
my $ver = 5;
print "Working path: '" . $RealBin . "'\n" if $ver > 3;
print "Loading config file.\n" if $ver > 1;
my $fp_conf = catfile( $RealBin, 'conf.pl' );
print "Config file path: '" . $fp_conf . "'\n" if $ver > 1;
my $conf = require $fp_conf;
dump( $conf ) if $ver > 4;
print "Validating config.\n" if $ver > 1;
foreach my $c_num ( 0..$#$conf ) {
my $ck = $conf->[$c_num];
print "num: $c_num\n" if $ver > 3;
# global keys
foreach my $mck ( qw(name repository commands) ) {
croak "Option '$mck' not found for '$ck'!\n" unless defined $ck->{$mck};
print "$mck: '" . $ck->{$mck} . "'\n" if !ref($ck->{$mck}) && $ver > 3;
}
# commands keys
foreach my $ac_num ( 0..$#{$ck->{commands}} ) {
my $ack = $ck->{commands}->[$ac_num];
print " num: $ac_num\n" if $ver > 3;
foreach my $mack ( qw(name cmd) ) {
croak "Option '$mack' not found for '$ack'!\n" unless defined $ack->{$mack};
print " $mack: '" . $ack->{$mack} . "'\n" if !ref($ack->{$mack}) && $ver > 3;
}
}
$ck->{src_dn} = $ck->{name} . '-src' unless exists $ck->{src_dn};
$ck->{temp_dn} = $ck->{name} . '-temp' unless exists $ck->{temp_dn};
$ck->{results_dn} = $ck->{name} . '-results' unless exists $ck->{results_dn};
$ck->{src_add_dn} = $ck->{name} . '-src-add' unless exists $ck->{src_add_dn};
# todo
$ck->{temp_dn_back} = '..' unless exists $ck->{temp_dn_back};
# todo
$ck->{rm_temp_dir} = 1 unless exists $ck->{rm_temp_dir};
$ck->{rewrite_temp_dir} = 1 unless exists $ck->{rewrite_temp_dir};
}
print "\n" if $ver > 3;
dump( $conf ) if $ver > 3;
print "\n" if $ver > 3;
# todo - load from dump
sub default_cmd_state {
return {
'cmd_num' => 0,
'before_done' => 0,
'cmd_done' => 0,
'after_done' => 0,
};
}
sub default_state {
return {
'svnup_done' => 0,
'rm_temp_dir_done' => 0,
'copy_src_dir_done' => 0,
'after_temp_copied_done' => 0,
'cmd' => default_cmd_state(),
};
}
my $fp_state = catfile( $RealBin, 'state.pl' );
print "Runstate config file path: '" . $fp_state . "'\n" if $ver > 1;
my $state;
if ( -e $fp_state ) {
$state = require $fp_state;
} else {
$state = default_state();
}
my $conf_last = $#$conf;
my $conf_first = ( exists $state->{ck_num} ) ? $state->{ck_num} : 0;
my $first_time = 1;
while ( 1 ) {
NEXT_CONF: foreach my $ck_num ( $conf_first..$conf_last ) {
if ( $first_time ) {
$conf_first = 0;
} else {
$state = default_state();
}
$first_time = 0;
my $ck = $conf->[$ck_num];
# todo
# $state->{ck_hash} = hash($ck)
# $conf_a->{ignore_ck_hash}
$state->{ck_num} = $ck_num;
print
( $ck->{skip} ? 'Skipping' : 'Running' ) .
" testing for configuration: '$ck->{name}' (" .
($ck_num+1) . " of " . ($conf_last+1). ")\n"
if $ver > 1;
next if $ck->{skip};
# 'svn co' if needed
unless ( -d $ck->{src_dn} ) {
print "Source dir not found: 'svn co'\n";
my $cmd = 'svn co "' . $ck->{repository} . '" "' . $ck->{src_dn} . '"';
my ( $cmd_rc, $out ) = sys_for_watchdog(
$cmd,
$ck->{results_dn} . '/svn_co.txt'
);
if ( $cmd_rc ) {
print "svn co failed, return code: $cmd_rc\n" if $ver > 1;
print "svn co output: '$out'\n" if $ver > 2;
next;
}
$state->{svnup_done} = 1;
} else {
print "Source dir found: '$ck->{src_dn}'\n" if $ver > 3;
}
# get revision num
unless ( $state->{svnup_done} ) {
print "Getting revision number for src dir.\n" if $ver > 1;
my ( $o_rev, $o_log ) = svnversion( $ck->{src_dn} );
die "svn info failed: $o_log" unless defined $o_rev;
$state->{src_rev} = $o_rev;
print "Src revision number: $state->{src_rev}\n" if $ver > 1;
if ( $state->{src_rev} !~ /^(\d+)$/ ) {
print "Bad revision number. No clean src dir.\n" if $ver > 1;
next NEXT_CONF;
}
}
# svn up
while ( not $state->{svnup_done} ) {
my $to_rev = 'HEAD';
# $to_rev = $state->{src_rev} + 1;
my $slt = 5*30;
my ( $up_ok, $o_log, $new_rev ) = svnup(
$ck->{src_dn},
$to_rev,
'README'
);
if ( $up_ok ) {
if ( $new_rev > $state->{src_rev} ) {
$state->{svnup_done} = 1;
} else {
print "Newer revision not found in repository!\n" if $ver > 1;
}
} else {
print "org dir svn up to $to_rev failed:\n" if $ver > 1;
print "'$o_log'\n" if $ver > 2 && defined($o_log);
print "waiting for $slt s ...\n";
}
if ( !$state->{svnup_done} ) {
next NEXT_CONF if $conf_last > $conf_first;
sleep $slt;
next;
}
print "Src dir svn up to $to_rev done\n" if $ver > 1;
print "New src revision number: $new_rev \n" if $ver > 1;
$state->{src_rev} = $new_rev;
}
if ( $ck->{rm_temp_dir} ) {
if ( $state->{rm_temp_dir_done} ) {
print "SKIP: Remove temp dir.\n" if $ver > 2;
} else {
print "Removing temp dir '$ck->{temp_dn}' ...\n" if $ver > 2;
if ( -d $ck->{temp_dn} ) {
rmtree( $ck->{temp_dn} ) or die $!;
while ( -d $ck->{temp_dn} ) {
print "Remove temp dir failed.\n" if $ver > 1;
my $wait_time = 10;
print "Waiting for ${wait_time}s.\n" if $ver > 1;
sleep $wait_time;
rmtree( $ck->{temp_dn} ) or die $!;
}
print "Remove temp dir done.\n" if $ver > 1;
} else {
print "Temp dir not found.\n" if $ver > 1;
}
$state->{rm_temp_dir_done} = 1;
}
}
if ( $ck->{rm_temp_dir} || $ck->{rewrite_temp_dir} ) {
if ( $state->{copy_src_dir_done} ) {
print "SKIP: Copy src dir.\n" if $ver > 2;
} else {
print "Copying src '$ck->{src_dn}' to temp '$ck->{temp_dn}' ...\n" if $ver > 2;
dircopy( $ck->{src_dn}, $ck->{temp_dn} ) or die "$!\n$@";
print "Copy src dir to temp dir done.\n" if $ver > 1;
$state->{copy_src_dir_done} = 1;
}
}
if ( $ck->{after_temp_copied} ) {
if ( $state->{after_temp_copied_done} ) {
print "SKIP: after_temp_copied hook.\n" if $ver > 1;
} else {
print "Running after_temp_copied hook.\n" if $ver > 2;
my $after_temp_copied_ret_code = $ck->{after_temp_copied}->( $ck, $state, $ver );
print "After_temp_copied hook return: $after_temp_copied_ret_code\n" if $ver > 2;
unless ( $after_temp_copied_ret_code ) {
print "Running after_temp_copied hook failed." if $ver > 1;
next;
}
}
}
my $cmd_last = $#{ $ck->{commands} };
my $cmd_first;
if ( $state->{cmd} ) {
$cmd_first = $state->{cmd}->{cmd_num};
die "cmd_first > cmd_last" if $cmd_first > $cmd_last;
} else {
$cmd_first = 0;
}
chdir( $ck->{temp_dn} ) or die $!;
my ( $o_rev, $o_log );
# get revision num
( $o_rev, $o_log ) = svnversion( '.' );
die "svn info failed: $o_log" unless defined $o_rev;
$state->{temp_rev} = $o_rev;
print "Revision number: $state->{temp_rev}\n" if $ver > 1;
my $timestamp = time();
$state->{results_path_prefix} =
$RealBin . '/'
. $ck->{results_dn} . '/'
. 'r' . $state->{temp_rev} . '-' . $timestamp . '/'
;
unless ( mkpath( $state->{results_path_prefix} ) ) {
print "Can't create results dir '$state->{results_path_prefix}'.\n" if $ver > 1;
next;
}
print "Results dir: '$state->{results_path_prefix}'.\n" if $ver > 3;
my $cmd_first_time = 1;
foreach my $cmd_num ( $cmd_first..$cmd_last ) {
if ( $cmd_first_time ) {
$cmd_first_time = 0;
} else {
$state->{cmd} = default_cmd_state();
}
my $cmd_conf = $ck->{commands}->[$cmd_num];
my $cmd = $cmd_conf->{cmd};
my $cmd_name = $cmd_conf->{name};
my $cmd_say = "'$cmd_name' (" . ($cmd_num+1) . " of " . ($cmd_last+1). ")";
print "Command: $cmd_say\n" if $ver > 1;
# max_time for watchdog
my $cmd_mt = $cmd_conf->{mt};
$state->{cmd}->{num} = $cmd_num;
if ( $cmd_conf->{before} ) {
if ( $state->{cmd}->{before_done} ) {
print "SKIP: before_cmd hook.\n" if $ver > 2;
} else {
print "Running before_cmd hook.\n" if $ver > 2;
my $before_ret_code = $cmd_conf->{before}->( $ck, $state, $ver );
print "Before cmd hook return: $before_ret_code\n" if $ver > 2;
unless ( $before_ret_code ) {
print "Running before_cmd hook failed.\n" if $ver > 1;
next;
}
}
}
$state->{cmd}->{before_done} = 1;
if ( $state->{cmd}->{cmd_done} ) {
print "SKIP: after_cmd hook.\n" if $ver > 2;
} else {
my $cmd_log_fp =
$state->{results_path_prefix}
. ($cmd_num+1) . '-' . $cmd_name . '.out'
;
# todo, .out to configure
my ( $cmd_rc, $out ) = sys_for_watchdog(
$cmd,
$cmd_log_fp,
$cmd_mt
);
print "Command '$cmd_name' return $cmd_rc.\n" if $ver > 3;
}
$state->{cmd}->{cmd_done} = 1;
if ( $cmd_conf->{after} ) {
if ( $state->{cmd}->{after_done} ) {
print "SKIP: after_cmd hook.\n" if $ver > 2;
} else {
print "Running after_cmd hook.\n" if $ver > 2;
my $after_ret_code = $cmd_conf->{after}->( $ck, $state, $ver );
print "After_cmd hook return: $after_ret_code\n" if $ver > 3;
unless ( $after_ret_code ) {
print "Running after_cmd hook failed.\n" if $ver > 1;
}
}
}
$state->{cmd}->{after_done} = 1;
}
chdir( $ck->{temp_dn_back} ) or die $!;
}
my $char = undef;
while ( defined ($char = ReadKey(-1)) ) { 1; }
if ( $char ) {
print "User press '$char'.\n" if $ver > 3;
$char = uc( $char );
# TODO
if ( $char eq 'P' ) {
print "User press pause key.\n" if $ver > 2;
} elsif ( $char eq 'C' ) {
print "User press continue key.\n" if $ver > 2;
} elsif ( $char eq 'Q' || $char eq 'E' ) {
print "User press exit key.\n" if $ver > 2;
exit;
}
}
print "\n" if $ver > 1;
}