The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package PBS::Rules::Builders ;

use PBS::Debug ;

use 5.006 ;

use strict ;
use warnings ;
use Data::TreeDumper ;
use Carp ;
 
require Exporter ;
use AutoLoader qw(AUTOLOAD) ;

our @ISA = qw(Exporter) ;
our %EXPORT_TAGS = ('all' => [ qw() ]) ;
our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ) ;
our @EXPORT = qw(GenerateBuilder) ;
our $VERSION = '0.01' ;

use File::Basename ;

use PBS::Shell ;
use PBS::PBSConfig ;
use PBS::Output ;
use PBS::Constants ;
use PBS::Rules ;
use PBS::Plugin;

#-------------------------------------------------------------------------------

sub GenerateBuilder
{
my ($shell, $builder, $package, $name, $file_name, $line) = @_ ;

my @builder_node_subs_and_type ;

if(defined $builder)
	{
	for (ref $builder)
		{
		($_ eq '' || $_ eq 'ARRAY') and do
			{
			@builder_node_subs_and_type = GenerateBuilderFromStringOrArray(@_) ;
			last ;
			} ;
			
		($_ eq 'CODE') and do
			{
			@builder_node_subs_and_type = GenerateBuilderFromSub(@_) ;
			last ;
			} ;
			
		die ERROR "Invalid Builder definition for '$name' at '$file_name:$line'\n" ;
		}
	}
	
	
return(@builder_node_subs_and_type) ;
}

#-------------------------------------------------------------------------------

sub GenerateBuilderFromStringOrArray
{
# generate sub that runs a shell command from the definition given in the Pbsfile

my ($shell, $builder, $package, $name, $file_name, $line) = @_ ;

$shell = new PBS::Shell() unless defined $shell ;
 
my $shell_commands ;
if(ref $builder eq '')
	{
	$shell_commands = [$builder] ; # single string
	}
else
	{
	$shell_commands = $builder ; # array of strings and perl sub refs
	}

my $builder_uses_perl_sub ;
# we must mark the rule as meta rules shall not be marked as builders using sub if the used slave rule doesn't use a sub!

for (@$shell_commands)
	{
	if(ref $_ eq '')
		{
		next ;
		}
		
	if(ref $_ eq 'CODE')
		{
		$builder_uses_perl_sub++ ;
		next ;
		}
		
	die ERROR "Invalid command for '$name' at '$file_name:$line'\n" ;
	}

my @node_subs_from_builder_generator ;

my %rule_type ;
unless($builder_uses_perl_sub)
	{
	my $shell_command_generator =
		# nadim 12 june 2005, let's try to minimize  memory consumption
		# more can be done but this was an easy testl
		sub 
		{
		return
			(
			ShellCommandGenerator
				(
				$shell_commands, $name, $file_name, $line
				, @_
				)
			) ;
		} ;
			
	$rule_type{SHELL_COMMANDS_GENERATOR} = $shell_command_generator ;
	
	push @node_subs_from_builder_generator,
		sub # node_sub
		{
		my (
		  $dependent_to_check
		, $config
		, $tree
		, $inserted_nodes
		) = @_ ;
		
		$tree->{__SHELL_COMMANDS_GENERATOR} = $shell_command_generator ;
		push @{$tree->{__SHELL_COMMANDS_GENERATOR_HISTORY}}, "rule '$name' @ '$file_name:$line'";
		} ;
	}
	
# nadim 12 june 2005, let's try to minimize  memory consumption
# more can be done but this was an easy test
my $generated_builder = 
	sub 
	{
	return
		(
		BuilderFromStringOrArray
			(
			$shell_commands, $shell, $package, $name, $file_name, $line
			, @_
			)
		) ;
	} ;

return($generated_builder, \@node_subs_from_builder_generator, \%rule_type) ;
}

#-------------------------------------------------------------------------------

# nadim 12 june 2005, let's try to minimize  memory consumption
sub ShellCommandGenerator
{
my (
# these could be computed from the tree (if the information is pushed before this sub is called)
$shell_commands, $name, $file_name, $line

# this is passed by pbs when inserting nodes
, $tree
) = @_;

my @evaluated_shell_commands ;
for my $shell_command (@{[@$shell_commands]}) # use a copy of @shell_commands, perl bug ???
	{
	push @evaluated_shell_commands, EvaluateShellCommandForNode
						(
						$shell_command
						, "rule '$name' at '$file_name:$line'"
						, $tree
						) ;
	}
	
return(@evaluated_shell_commands) ;
}

#-------------------------------------------------------------------------------

# nadim 12 june 2005, let's try to minimize  memory consumption
sub BuilderFromStringOrArray
{
#this is generated but it should be possible to generate it at node build  time
my($shell_commands, $shell, $package, $name, $file_name, $line) = splice(@_, 0, 6) ;

# the rest is generic and we should generate a sub for each rule  but reuse
my ($config, $file_to_build, $dependencies, $triggering_dependencies, $tree, $inserted_nodes) = @_ ;

my $node_shell = $shell ;
my $is_node_local_shell = '' ;

if(exists $tree->{__SHELL_OVERRIDE})
	{
	if(defined $tree->{__SHELL_OVERRIDE})
		{
		$node_shell = $tree->{__SHELL_OVERRIDE} ;
		$is_node_local_shell = ' [N]'
		}
	else
		{
		Carp::carp ERROR("Node defined shell override for node '$tree->{__NAME}' exists but is not defined!\n") ;
		die ;
		}
	}
	
$tree->{__SHELL_INFO} = $node_shell->GetInfo() ; # :-) doesn't help as this might not be in the root process
if($tree->{__PBS_CONFIG}{DISPLAY_SHELL_INFO})
	{
	PrintWarning "Using shell$is_node_local_shell: '$tree->{__SHELL_INFO}' " ;
	
	if(exists $tree->{__SHELL_ORIGIN} && $tree->{__PBS_CONFIG}{ADD_ORIGIN})
		{
		PrintWarning "set at $tree->{__SHELL_ORIGIN}" ;
		}
		
	print "\n" ;
	}
	
for my $shell_command (@{[@$shell_commands]}) # use a copy of @shell_commands, perl bug ???
	{
	if('CODE' eq ref $shell_command)
		{
		my @result = $node_shell->RunPerlSub($shell_command, @_) ;
		
		if($result[0] == 0)
			{
			# command failed
			return(@result) ;
			}
			
		}
	else
		{
		my $command = EvaluateShellCommandForNode
						(
						$shell_command
						, "rule '$name' at '$file_name:$line'"
						, $tree
						, $dependencies
						, $triggering_dependencies
						) ;
						
		$node_shell->RunCommand($command) ;
		}
	}
	
return(1 , "OK Building $file_to_build") ;
}

#-------------------------------------------------------------------------------

sub GenerateBuilderFromSub
{
my ($shell, $builder, $package, $name, $file_name, $line) = @_ ;

$shell = new PBS::Shell() unless defined $shell ;
 
my $generated_builder = 
	sub
	{ 
	return(BuilderFromSub($shell, $builder, $package, $name, $file_name, $line, @_)) ;
	} ;

my %rule_type ;

return($generated_builder, undef, \%rule_type) ;
}

#-------------------------------------------------------------------------------

# nadim 12 june 2005, let's try to minimize  memory consumption
sub BuilderFromSub
{
# note that this sub does very little. it only does some display to finally call the suplied sub

# could be computed at node build time
my ($shell, $builder, $package, $name, $file_name, $line) = splice(@_, 0, 6) ;

my ($config, $file_to_build, $dependencies, $triggering_dependencies, $tree, $inserted_nodes) = @_ ;

my $node_shell = $shell ;
my $is_node_local_shell = '' ;

if(exists $tree->{__SHELL_OVERRIDE})
	{
	if(defined $tree->{__SHELL_OVERRIDE})
		{
		$node_shell = $tree->{__SHELL_OVERRIDE} ;
		$is_node_local_shell = ' [N]'
		}
	else
		{
		Carp::carp ERROR("Node defined shell for node '$tree->{__NAME}' exists but is not defined!\n") ;
		die ;
		}
	}
	
$tree->{__SHELL_INFO} = $node_shell->GetInfo() ; # :-) doesn't help as this might not be in the root process
	
if($tree->{__PBS_CONFIG}{DISPLAY_SHELL_INFO})
	{
	PrintWarning "Using shell$is_node_local_shell: '$tree->{__SHELL_INFO}' " ;
	
	if(exists $tree->{__SHELL_ORIGIN} && $tree->{__PBS_CONFIG}{ADD_ORIGIN})
		{
		PrintWarning "set at $tree->{__SHELL_ORIGIN}" ;
		}
		
	print "\n" ;
	}
	
return
	(
	$node_shell->RunPerlSub($builder, @_)
	) ;
} ;

#-------------------------------------------------------------------------------

sub EvaluateShellCommandForNode
{
my($shell_command, $shell_command_info, $tree, $dependencies, $triggered_dependencies) = @_ ;

RunPluginSubs($tree->{__PBS_CONFIG}, 'EvaluateShellCommand', \$shell_command, $tree, $dependencies, $triggered_dependencies) ;

my $config = $tree->{__CONFIG} ;
my $file_to_build = $tree->{__BUILD_NAME} || GetBuildName($tree->{__NAME}, $tree);

my @dependencies ;
unless(defined $dependencies)
	{
	#extract them from tree if not passed as argument
	@dependencies = map {$tree->{$_}{__BUILD_NAME} ;} grep { $_ !~ /^__/ && exists $tree->{$_}{__BUILD_NAME}}(keys %$tree) ;
	}
else
	{
	#~ @dependencies = grep {defined $_} @$dependencies ;
	@dependencies = @$dependencies ;
	}

my $dependency_list = join ' ', @dependencies ;

my $build_directory = $tree->{__PBS_CONFIG}{BUILD_DIRECTORY} ;
my $dependency_list_relative_build_directory = join(' ', map({my $copy = $_; $copy =~ s/\Q$build_directory\E[\/|\\]// ; $copy} @dependencies)) ;

my @triggered_dependencies ;

unless(defined $dependencies)
	{
	# build a list of triggering dependencies and weed out doublets
	my %triggered_dependencies_build_names ;
	for my $triggering_dependency (@{$tree->{__TRIGGERED}})
		{
		my $dependency_name = $triggering_dependency->{NAME} ;
		
		if($dependency_name !~ /^__/ && ! exists $triggered_dependencies_build_names{$dependency_name})
			{
			push @triggered_dependencies, $tree->{$dependency_name}{__BUILD_NAME} ;
			$triggered_dependencies_build_names{$dependency_name} = $tree->{$dependency_name}{__BUILD_NAME} ;
			}
		}
	}
else
	{
	@triggered_dependencies = @$triggered_dependencies ;
	}
	
my $triggered_dependency_list = join ' ', @triggered_dependencies ;

my ($basename, $path, $ext) = File::Basename::fileparse($file_to_build, ('\..*')) ;
$path =~ s/\/$// ;

$shell_command =~ s/\%BUILD_DIRECTORY/$build_directory/g ;

$shell_command =~ s/\%FILE_TO_BUILD_PATH/$path/g ;
$shell_command =~ s/\%FILE_TO_BUILD_NAME/$basename$ext/g ;
$shell_command =~ s/\%FILE_TO_BUILD_BASENAME/$basename/g ;
$shell_command =~ s/\%FILE_TO_BUILD_NO_EXT/$path\/$basename/g ;
$shell_command =~ s/\%FILE_TO_BUILD/$file_to_build/g ;

$shell_command =~ s/\%DEPENDENCY_LIST_RELATIVE_BUILD_DIRECTORY/$dependency_list_relative_build_directory/g ;
$shell_command =~ s/\%TRIGGERED_DEPENDENCY_LIST/$triggered_dependency_list/g ;
$shell_command =~ s/\%DEPENDENCY_LIST/$dependency_list/g ;

$shell_command = PBS::Config::EvalConfig($shell_command, $config, "Shell command", $shell_command_info) ;

return($shell_command) ;
}

#-------------------------------------------------------------------------------

sub GetBuildName
{
my ($dependent, $file_tree) = @_ ;

my $build_directory    = $file_tree->{__PBS_CONFIG}{BUILD_DIRECTORY} ;
my $source_directories = $file_tree->{__PBS_CONFIG}{SOURCE_DIRECTORIES} ;

my ($full_name, $is_alternative_source, $other_source_index) = PBS::Check::LocateSource($dependent, $build_directory, $source_directories) ;

return($full_name) ;
}

#-------------------------------------------------------------------------------

1 ;

__END__
=head1 NAME

PBS::Rules::Builders -

=head1 DESCRIPTION

This package provides support function for B<PBS::Rules::Rules>

=head2 EXPORT

Nothing.

=head1 AUTHOR

Khemir Nadim ibn Hamouda. nadim@khemir.net

=head1 SEE ALSO

B<PBS> reference manual.

=cut