# --8<--8<--8<--8<-- # # Copyright (C) 2008 Smithsonian Astrophysical Observatory # # This file is part of Decision::Depends # # Decision-Depends is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or (at # your option) any later version. # # 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. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # -->8-->8-->8-->8-- package Decision::Depends; require 5.005_62; use strict; use warnings; require Exporter; our @ISA = qw(Exporter); # Items to export into callers namespace by default. Note: do not export # names by default without a very good reason. Use EXPORT_OK instead. # Do not simply export all your public functions/methods/constants. # This allows declaration use Decision::Depends ':all'; # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK # will save memory. our %EXPORT_TAGS = ( 'all' => [ qw( ) ] ); our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); our @EXPORT = qw( if_dep action test_dep ); our $VERSION = '0.20'; use Carp; use Decision::Depends::OO; our $self = Decision::Depends::OO->new(); ## no critic ( ProhibitSubroutinePrototypes ) sub if_dep(&@) { my ( $deps, $run ) = @_; my @args = &$deps; local $Carp::CarpLevel = $Carp::CarpLevel + 1; $self->if_dep( \@args, $run ); } sub action(&) { $_[0] } sub test_dep { local $Carp::CarpLevel = $Carp::CarpLevel + 1; $self->test_dep( @_ ); } sub Configure { local $Carp::CarpLevel = $Carp::CarpLevel + 1; $self->configure( @_ ); } sub init { my ( $state_file, $attr ) = @_; print STDERR "Decision::Depends::init is obsolete. Please use Decision::Depends::Configure instead\n"; Configure( { File => $state_file, $attr ? %$attr : () } ); } sub renew { undef $self; $self = Decision::Depends::OO->new(); } 1; __END__ =head1 NAME Decision::Depends - Perform actions based upon file dependencies =head1 SYNOPSIS use Decision::Depends; Decision::Depends::Configure( { File => $depfile } ); if_dep { @targ_dep_list } action { action }; =head1 DESCRIPTION B is a module which simplifies the creation of procedures with intermediate steps which can be skipped if certain dependencies are met. Think of it as a procedural version of B. B is useful when there are several steps in a process, each of which depends upon the last. If the process is interrupted, or if it is to be redone with changes to parameters in later steps, and if intermediate results can be kept, then B can insure that only the minimal number of steps be redone. Each step must result in a tangible product (a file). For complicated steps with many products the step's successful completion may be indicated by creating an empty file whose existance indicates completion. This file (a C file in B terminology) can be automatically created if requested. B determines if the product for a given step is older than any files required to produce it. It can also check whether the contents of a file have changed since the product was last created. This is useful in the case where a configuration file must be created anew each time, but results in action only if changed since the product was last created. Finally, it can determine if a variable's value has changed since the product was last created. =head2 Dependency history B must keep some dependency information between runs (for signature and variable dependencies). It stores this in a file, which must be named by the application. The application indicates the file by calling the B subroutine. This file is updated after completion of successful actions and when the program is exited. =head2 Dry Runs and Changing other behavior B can be put into a state where it checks dependencies and pretends to update targets in order to check what actions might need to be taken. This is done by passing the C attribute to B. In this mode no actions are actually performed, but are assumed to have successfully created their products. B will output to STDOUT its musings if the C attribute is passed to B. To simply test if a dependency exists, without requiring that an action be performed, use the B function. =head2 Targets and Dependencies List Each step must construct a single Perl list of products, also called targets (as in B), and dependencies. The list has a simple syntax - it is a sequence of values, each of which may have one or more attributes. Attributes precede values and apply only to the next value (unless values are grouped), and always begin with a C<-> character. Multiple attributes may be applied to a single value. -target => $file, -depend => -sig => $dep (Note the use of the perl C<< => >> operator to avoid quoting of attributes.) Values which begin with the C<-> character (which may be confused with attributes) may be passed by reference. B recognizes negative numbers, so those need not be handled specially. -target => \'-strange_file', -target => -33.99e24 Values may be grouped by placing them in anonymous arrays: -target => [ $file1, $file2 ] Attributes are applied to all elements of the group; additional attributes may modify individual group members: -target => [ -sfile => $file1, $file2 ] Groups may be nested. To negate an attribute, introduce the same attribute with a prefix of C<-no_>: -target => -sfile => [ $file1, -no_sfile => $file2 ] Attributes may have values, although they are in general boolean values. The syntax is '-attr=value'. Note that because of the C<=> character, Perl's automatic quoting rules when using the C<< => >> operator are insufficient to ensure appropriate quoting. For example '-slink=foo' => $target assigns the C<-slink> attribute to C<$target> and gives the attribute the value C. If no value is specified, a default value of C<1> is assigned. Most attributes are boolean, so no value need be assigned them. Hash references may be used to pair attribute values with ordinary values. For example, the following -var => { $attr1 => $val1, $attr2 => $val2 } assigns C<$val1> the attribute C<-var> with the value C<$attr1>, C<$val2> the attribute C<-var> with the value C<$attr2>, etc. This is most useful when specifying variable dependencies (see L). =head2 Targets Targets are identified either by having the C<-target> or C<-targets> attributes, or by being the first value (or group) in the target-dependency list and not having the C<-depend> attribute. For example, the following are equivalent ( -target => $targ1, -target => $targ2, ... ) ( -target => [ $targ1, $targ2 ], ... ) ( [ $targ1, $targ2 ], ... ) There must be at least one target. Target values may have the following attributes: =over 8 =item B<-target> This indicates the value is a target. =item B<-sfile> This indicates that the target is a status file. It will be automatically created upon successful completion of the step. =item B<-slink=> This indicates that the target is a status file which is linked to an imaginary file C. Any step which explicitly depends upon C will instead depend upon the target file instead. Multiple links to C may be created. Links are checked in order of appearance, and are useful only as time dependencies. For example, rather than depending upon the target of the previous step, a step might depend upon the C. It's then possible to introduce new intermediate steps which link their status files to C without having to rewrite the current step. For example ( -target => '-slink=step1' => 'step1a', ... ) ( -target => '-slink=step1' => 'step1b', ... ) ( -target => $result, -depend => 'step1' ) In this case, the final step will depend upon F and F. One could later add a F and not have to change the dependencies for the final step. The target status file will be automatically created upon successful completion of the step. =item C<-force> If set to non-zero (the default if no value is specified), this will force the target to always be out-of-date. This can be used to override a global forcing of out-of-dateness (done via the B function) by setting it to zero. It is probably most useful for targets which have no dependencies. =back =head2 Dependencies Dependencies are identified either as I being the first value (or group) in the list and not having the C<-target> attribute, or by having the attributes C<-depend> or C<-depends>. There need not be any dependencies. There are three types of dependencies: I