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 =head2 EXPORT Nothing. =head1 AUTHOR Khemir Nadim ibn Hamouda. nadim@khemir.net =head1 SEE ALSO B reference manual. =cut