File Coverage

blib/lib/Slay/Maker.pm
Criterion Covered Total %
statement 230 286 80.4
branch 58 118 49.1
condition 12 63 19.0
subroutine 37 40 92.5
pod 31 33 93.9
total 368 540 68.1


line stmt bran cond sub pod time code
1             package Slay::Maker ;
2              
3             our $VERSION=0.08 ;
4              
5             =head1 NAME
6              
7             Slay::Maker - An perl make engine using perl code for rules
8              
9             =head1 STATUS
10              
11             Beta. Pretty stable, though underdocumented.
12              
13             =head1 DESCRIPTION
14              
15             Slay::Maker is a make engine that uses perl declaration syntax for
16             rules, including regular expressions for targets and anonymous subs
17             for targets, dependencies, and actions.
18              
19             This allows you to tightly integrate a make engine in an application
20             and to exercise a large amount of control over the make process, taking
21             full advantage of Perl code at any point in the make cycle.
22              
23             =head1 RULE SYNTAX
24              
25             The rulebase syntax is:
26              
27             [ @targets1, ':', @dependencies1, '=', @actions1, { option => val } ],
28             [ @targets2, ':', @dependencies2, '=', @actions2, { option => val } ],
29             [ @targets3, ':', @dependencies3, '=', @actions3, { option => val } ],
30             [ @targets4, ':', @dependencies4, '=', @actions4, { option => val } ],
31             ...
32              
33             Each item in any of the three arrays may be a literal string or a
34             subroutine (CODE) reference. A literal string is pretty much the same as
35             using a literal string in a regular makefile. You may also use regular
36             expression ('Regexp') references (C) in @targets and the
37             C<$1>, C<$2>, ... variables inside strings in @dependencies:
38              
39             [ qr/(.*).pm/, ':', "$1.pm.in", '=', sub { ... } ],
40              
41             Subroutine references are evaluated as lazily as possible when the
42             make is being run, so any CODE refs in @targets will be called
43             each time a make is run, CODE refs in @dependencies will only be
44             called if a target matches, and CODE refs in @actions are only
45             called if the rule is fired.
46              
47             =head2 TARGET SUBS
48              
49             ** NOT IMPLEMENTED QUITE YET **. It's simple to do, just haven't needed
50             it yet.
51              
52             Aside from strings and Regexps, you will be able to use CODE refs in
53             the target list. These are called each time the rule is evaluated,
54             which will usually happen once per target or dependency being
55             checked when the make is run.
56              
57             A target sub declaration might look like:
58              
59             sub {
60             my ( $maker ) = @_ ;
61             ...
62             return @targets;
63             },
64              
65             (if target subs were implemented already).
66              
67             =head2 DEPENDENCIES
68              
69             Dependencies may be strings or CODE references. Plain strings have
70             $1, $2, ... interpolation done on them (remember to \ escape the $1, etc.).
71              
72             CODE refs will be called if the target matches and must return a
73             possibly empty list of strings containing the names of dependencies.
74             Variable interpolation will not be done on the returned strings. That
75             would be obscene.
76              
77             A dependency sub declaration might look like:
78              
79             sub {
80             my ( $maker, $target, $matches ) = @_ ;
81             ...
82             return @dependencies ;
83             },
84              
85             where
86              
87             $maker refers to the Slay::Maker (or subclass) being run
88             $target is the target that matched (in the event of multiple targets)
89             $matches is an ARRAY of the values extracted from $1, $2, etc.
90              
91             .
92              
93             =head2 ACTIONS
94              
95             If an $action is a plain string, it's passed to "sh -c '$string'". If
96             it's an ARRAY ref, it's run without interference from or benefit of
97             a shell (see L for details). If it's a CODE ref, it's
98             called.
99              
100             An action sub declaration might look like:
101              
102             sub {
103             my ( $maker, $target, $deps, $matches ) = @_ ;
104             ...
105             return @dependencies ;
106             },
107              
108             where
109              
110             $maker refers to the Slay::Maker (or subclass) being run
111             $target is the target that matched (in the event of multiple targets)
112             $deps is an ARRAY of the expanded dependencies. There's no way
113             of telling which are freshly rebuilt, but you can track that
114             yourself in the action rules of the dependencies, if you
115             like.
116             $matches is an ARRAY of the values extracted from $1, $2, etc.
117              
118             =head1 TARGET BACKUPS
119              
120             A target may be moved off to a backup location before it is rebuilt, so
121             that it may be restored if rebuilding fails. This is also used for
122             the optional restoration of modification times described below.
123              
124             Restoration needs to be done manually by calling the L method,
125             and you can call the L method, too.
126              
127             The L method will be called automatically if modification
128             time restoration is enabled for a target.
129              
130             =head1 MODIFICATION TIME RESTORATION
131              
132             One unusual option is that a target file's modification time can
133             be restored if it is unchanged after being updated. This can be
134             useful when checking files out of a repository: the files' mod times
135             won't be affected if their contents haven't changed.
136              
137             This can be done by a (fast but possibly misleading) check for a change
138             in file size or by executing 'diff --brief' between a target's backup
139             and it's new version. Other methods, such as hashing or block-by-block
140             binary comparison will be implemented in the future if needed.
141              
142             This is controlled by the L option passed to the
143             base class constructor:
144              
145             my $self = Slay::Maker->new( ..., options => { detect_no_diffs => 1 } ) ;
146              
147             and can be passed as an option to any rule.
148              
149             =head1 AN EXAMPLE
150              
151             Here's a real example, which will have to stand in for documentation
152             until further notice. If you need more, mail me (barries@slaysys.com)
153             and get me to do something productive for a change.
154              
155             This is a subclass that compiles a set of builtin rules at module
156             compilation time. It declares a method for spawning the cvs command
157             first, then builds some rules.
158              
159             package Safari::Cvs::Make ;
160              
161             use Class::Std ;
162             use base qw( Slay::Maker ) ;
163              
164             use strict ;
165             use IPC::Run ;
166              
167             sub cvs {
168             my Safari::Cvs::Make $maker = shift ;
169              
170             my $stdout_to ;
171             if ( $_[-1] =~ /^\s*>(.*)/ ) {
172             $stdout_to = $1 ;
173             pop ;
174             }
175              
176             my $cvs_out ;
177             run [ qw( cvs -q -l -z9 ), @_ ], \undef, \$cvs_out or die $! ;
178              
179             return $cvs_out ;
180             }
181              
182              
183             my $builtin_rules = Safari::Make->compile_rules(
184             [ 'meta/cvs_modules',
185             '=', sub { ## The action that occurs when the rule fires.
186             ## We could just do the cvs co -c here, but many pservers don't
187             ## have that implemented. so, check out the modules file and
188             ## parse it.
189             my ( $maker, $target ) = @_ ;
190             $maker->cvs( qw( checkout -p CVSROOT/modules ), ">$target" ) ;
191             },
192             ],
193             [ 'update',
194             ':' => sub {
195             my ( $maker ) = @_ ;
196              
197             my $context = $maker->{CONTEXT} ;
198              
199             my @modules ;
200              
201             my %args = $context->request->args ;
202             if ( defined $args{modules} ) {
203             @modules = split( ',', $args{modules} ) ;
204             }
205             elsif ( defined $args{module} ) {
206             @modules = $args{module} ;
207             }
208             else {
209             eval {
210             ## A recursive make
211             $maker->make( 'meta/cvs_modules', { force => 1 } ) ;
212             } ;
213             if ( $@ ) {
214             warn $@ ;
215             }
216              
217             if ( ! open( F, "
218             warn "$!: meta/cvs_modules, globbing" ;
219             @modules = map {
220             s{^meta/HEAD/}{}; $_
221             } grep {
222             -d $_
223             } glob( 'meta/HEAD/*' ) ;
224             }
225             else {
226             my $line ;
227             my %modules ;
228             while () {
229             next if /^\s*#|^\s*$/ ;
230             chomp ;
231             $line .= $_ ;
232             redo if $line =~ s{\\$}{} ;
233             $modules{$1} = 1 if $line =~ m{^\s*(\S+)} ;
234             $line = '' ;
235             }
236             close F ;
237             @modules = sort keys %modules ;
238             }
239             }
240              
241             debug 'modules', \@modules ;
242             die "No modules found\n" unless @modules ;
243             return map { "source/HEAD/$_/CVS/" } @modules ;
244             },
245             '=' => sub {
246             my ( $maker, $target, $deps ) = @_ ;
247              
248             my @dirs = map { s{/CVS/}{} ; $_ } @$deps ;
249              
250             ## We go ahead and update after creating modules for a couple of
251             ## reasons:
252             ## 1. It's rare that we've just checked out a module
253             ## 2. It's simpler this way
254             ## 3. If we just created a *big* module, then we might need to
255             ## update anyway.
256              
257             ## We set $logs{$filename} = 1 if we must replace the current log file,
258             ## or = 0 to just ensure that the log file is fetched.
259             my %logs ;
260             my $force_all ;
261              
262             my $cwd = cwd() ;
263             for ( @dirs ) {
264             chdir $_ or die "$!: $_" ;
265             my $module = $_ ;
266             $module =~ s{.*/}{} ;
267             ## -P: Prune empty dirs
268             ## -d: Add new dirs that we don't have
269             for ( split /^/m, $maker->cvs( qw( update -d -P ) ) ) {
270             chomp ;
271             if ( /^[UP]\s+(.*)/ ) {
272             $logs{"meta/HEAD/$module/$1.log"} = 1 ;
273             }
274             elsif ( /^\?\s+(.*)/ ) {
275             my $log_file = "meta/HEAD/$module/$1.log" ;
276             eval {
277             rmtree( [ $log_file ] ) ;
278             } ;
279             warn "Error removing ;$log_file'" if $@ ;
280             }
281             else {
282             warn "Unexpected CVS file mode: $_" ;
283             }
284             }
285             chdir $cwd or die "$!: $cwd" ;
286             }
287              
288             for ( sort keys %logs ) {
289             $maker->make(
290             $_,
291             {
292             force => $force_all || $logs{$_}
293             }
294             ) ;
295             }
296              
297             },
298             {
299             force => 1, # Always remake this target
300             }
301             ],
302             ) ;
303              
304              
305              
306              
307              
308             =cut
309              
310 1     1   21552 use strict ;
  1         4  
  1         43  
311 1     1   8 use Carp ;
  1         2  
  1         79  
312 1     1   17 use Cwd () ;
  1         3  
  1         21  
313 1     1   1143 use File::Copy qw( copy move ) ;
  1         5610  
  1         1210  
314 1     1   11 use File::Spec::Unix ;
  1         2  
  1         82  
315 1     1   2319 use Slay::MakerRule 0.03;
  1         47  
  1         22  
316              
317 1     1   47 use Class::Std;
  1         2  
  1         8  
318              
319             { # Creates the closure for the attributes
320              
321             # Attributes
322             my %comments_of : ATTR( :default<[]> );
323             my %errors_of : ATTR( :default<[]> );
324             my %in_queue_of : ATTR;
325             my %made_targets_of : ATTR;
326             my %options_of : ATTR( :init_arg :default<{}> );
327             my %output_of : ATTR;
328             my %rmake_stack_of : ATTR( :default<[]> );
329             my %rules_of : ATTR( :default<[]> );
330             my %queue_of : ATTR( :default<[]> );
331            
332             ## A few things that are cached for performane, so we're not always hittine
333             ## the kernel up for filesystem data.
334             my %stat_cache ;
335             my $cwd_cache ;
336              
337             ## When chdir()ing to a symlink, these two vars save the symbolic and real
338             ## values, so the symbolic one and the real one can both be checked when
339             ## looking at targets with absolute paths.
340             my $sym_cwd_cache ;
341             my $real_cwd_cache ;
342              
343             =head1 METHODS
344              
345             =over
346              
347             =item new
348              
349             Constructor.
350              
351             my $rules = [
352             # ...
353             ] ;
354             my $maker = Slay::Maker->new( { } );
355             my $maker = Slay::Maker->new( { rules => $rules } ) ;
356             my $maker = Slay::Maker->new( { rules => $rules,
357             options => { option => 1 } } ) ;
358              
359             options (which can also be defined on a per-rule basis) are:
360              
361             =over
362              
363             =item auto_create_dirs
364              
365             Creates directories that targets will be created in before executing
366             a target's actions.
367              
368             =item detect_no_diffs
369              
370             Copy the target before executing a rule, then restore
371             the original modification and access times if the
372             contents of the target are the same after the rule.
373              
374             =item detect_no_size_change
375              
376             Look for changes in the size of a file after executing
377             a rule and restore the original modification and access
378             times if it changes.
379              
380             =item force
381              
382             Always remake target, even if it does not appear to be
383             out of date
384              
385             =back
386              
387             Warning: options are not checked for spelling errors.
388              
389             Options may be passed to new(), make(), build_queue(), and to rules themselves.
390             Options passed to make() or build_queue() take precedence over rules' options,
391             and rules' options take precedence over those passed to new().
392              
393              
394             =cut
395              
396             sub BUILD {
397 1     1 0 225 my ($self, $ident, $args_ref) = @_;
398              
399 1         3 $self->add_rules( @{$self->builtin_rules} ) ;
  1         6  
400 1 50       7 $self->add_rules( @{$args_ref->{rules}} ) if $args_ref->{rules};
  0         0  
401             }
402              
403              
404             =item add_rules
405              
406             Add rules (compiled or not) to the rule base.
407              
408             =cut
409              
410              
411             sub add_rules {
412 17     17 1 2622 my Slay::Maker $self = shift ;
413              
414 17         52 my $ident = ident $self;
415 17         371 push @{$rules_of{$ident}}, @{$self->compile_rules( @_ )} ;
  17         63  
  17         70  
416             }
417              
418              
419             =item atime
420              
421             This returns the atime of a file, reading from the stat cache if possible.
422              
423             =cut
424              
425             sub atime {
426 0     0 1 0 my Slay::Maker $self = shift ;
427              
428 0         0 return ($self->stat( @_ ))[8] ;
429             }
430              
431              
432             =item build_queue
433              
434             Builds a new queue of rules to be exec()ed to make a target
435              
436             =cut
437              
438             sub build_queue {
439 28     28 1 377 my Slay::Maker $self = shift ;
440 28 100       105 my $options = ref $_[-1] ? pop : {} ;
441              
442 28         180 my $ident = ident $self;
443 28         71 $queue_of {$ident} = [];
444 28         67 $in_queue_of{$ident} = {};
445 28         522 $errors_of {$ident} = [];
446              
447 28         69 $cwd_cache = undef ;
448              
449 28         114 $self->check_targets( @_, $options ) ;
450             }
451              
452              
453             =item builtin_rules
454              
455             Returns [] by default. This is provided so that subclasses may overload it
456             to provide sets of rules. This is called by new() before adding any rules
457             passed to new().
458              
459             =cut
460              
461 1     1 1 5 sub builtin_rules { return [] }
462              
463              
464             =item canonpath
465              
466             Cleans up the path, much like File::Spec::Unix::canonpath(), but also
467             removes name/.. constructs.
468              
469             =cut
470              
471             sub canonpath {
472 56     56 1 132 my Slay::Maker $self = shift ;
473 56         402 my ( $path ) = @_ ;
474              
475 56         330 my $trailing_slash = $path =~ m!/$! ;
476 56         980 $path = File::Spec::Unix->canonpath( $path ) ;
477 56 0       278 1 while $path =~ s{(^|/)[^/]+/\.\.(/|\Z)}{'/' if length "$1$2"}ge ;
  0         0  
478 56 50       158 $path .= '/' if $trailing_slash ;
479 56         195 return $path ;
480             }
481              
482              
483             =item chdir
484              
485             Calls system chdir(), die()s on failure, and uses the parameter as the
486             current directory. The last is the main reason for this sub: if you chdir()
487             to a symbolic link, then we want to know the symbolic directory, not the
488             real one returned by cwd().
489              
490             =cut
491              
492             sub chdir {
493 0     0 1 0 my Slay::Maker $self = shift ;
494 0         0 my ( $path ) = @_ ;
495 0         0 $path = $self->canonpath( $path ) ;
496 0 0       0 chdir $path or die "$! chdir()ing to '$path'" ;
497 0         0 my $cwd = cwd() ;
498 0 0       0 if ( $path ne $cwd ) {
499 0         0 $sym_cwd_cache = $path ;
500 0         0 $real_cwd_cache = $cwd ;
501             }
502             else {
503 0         0 $sym_cwd_cache = undef ;
504 0         0 $real_cwd_cache = undef ;
505             }
506             }
507              
508              
509             =item check_targets
510              
511             Checks targets and adds them to queue if need be. Does I integrate
512             Slay::Maker options: this is left to the caller (usually the original
513             build_queue() call).
514              
515             =cut
516              
517             sub check_targets {
518 79     79 1 944 my Slay::Maker $self = shift ;
519 79 50       526 my $options = ref $_[-1] ? pop : {} ;
520 79         399 my ( @targets ) = @_ ;
521              
522 79         129 my $count=0;
523            
524 79         9840 my $ident = ident $self;
525 79         173 for ( @targets ) {
526 56         246 my ( $target, $r, $matches ) = $self->find_rule( $_, $options ) ;
527 56 100       186 if ( ! defined $r ) {
528 3 50       243 push @{$errors_of{$ident}}, "Don't know how to make $_"
  0         0  
529             if ! -e $_ ;
530 3         16 next ;
531             }
532 53         653 $count+=$r->check( $self, $target, $matches, $options ) ;
533             }
534 79         389 return $count;
535             }
536              
537              
538             =item clear_caches
539              
540             Clears the stat cache, so the filesystem must be reexamined. Only needed
541             if Slay::Maker is being called repetitively.
542              
543             =cut
544              
545             sub clear_caches {
546 8     8 1 139070 my Slay::Maker $self = shift ;
547              
548 8         82 %stat_cache = () ;
549 8         25 $cwd_cache = undef ;
550 8         17 $sym_cwd_cache = undef ;
551 8         29 $real_cwd_cache = undef ;
552             }
553              
554              
555             =item clear_stat
556              
557             Clears the stat cache for a given path, so the next stat() on that path
558             will read the filesystem.
559              
560             =cut
561              
562             sub clear_stat {
563 90     90 1 238 my Slay::Maker $self = shift ;
564              
565 90         164 my ( $path ) = @_ ;
566 90         442 delete $stat_cache{$path} ;
567             }
568              
569              
570             =item compile_rules
571              
572             Returns a rulebase compiled from the arguments. Rules that are already
573             compiled are passed through unchanged. This is a class method, so
574              
575             Slay::Maker->compile_rules(
576             [ 'a', [qw( b c )], 'cat b c > a' ],
577             ...
578             ) ;
579              
580             can be used to compile a rulebase once at startup
581              
582             =cut
583              
584              
585             sub compile_rules {
586 19     19 1 36 my Slay::Maker $self = shift ;
587             return [
588 41 50       730 map {
589 19         41 ref $_ eq 'ARRAY' ? Slay::MakerRule->new( { rule => $_ } ) : $_ ;
590             } @_
591             ] ;
592             }
593              
594              
595             =item backup
596              
597             Copies a file so that it can be restored later or checked for changes.
598              
599             If the target will only ever be replaced by the make, then it will not be
600             altered in-place, and the C option may be passed:
601              
602             $maker->backup( $target, { move => 1 } ) ;
603              
604             If the target is an file which always changes size when it is changed,
605             you may pass the C option:
606              
607             $maker->backup( $target, { stat_only => 1 } ) ;
608              
609             The return value can be passed to restore(), target_unchanged(),
610             and remove_backup().
611              
612             =cut
613              
614             sub backup {
615 2     2 1 13 my $self = shift ;
616 2 50       19 my $options = ref $_[-1] ? pop : {} ;
617 2         5 my ( $target ) = @_ ;
618              
619             ## TODO: Detect collisions.
620 2         12 my $temp_name = "$target.make_orig" ;
621 2 50 33     22 if ( ! $options->{stat_only} && $self->e( $target ) ) {
622 0 0       0 if ( $options->{move} ) {
623 0 0       0 print STDERR "Moving '$target' to '$temp_name'\n"
624             if $options->{debug} ;
625              
626 0         0 move( $target, $temp_name ) ;
627             }
628             else {
629 0 0       0 print STDERR "Copying '$target' to '$temp_name'\n"
630             if $options->{debug} ;
631              
632 0         0 copy( $target, $temp_name ) ;
633             }
634             }
635              
636             return {
637 2         8 OPTIONS => $options,
638             STAT => [ $self->stat( $target ) ],
639             FILE => $target,
640             BACKUP => $temp_name,
641             } ;
642             }
643              
644              
645             =item cwd
646              
647             Returns the current working directory, from the cache if that is possible.
648              
649             =cut
650              
651             sub cwd {
652 56     56 1 113 my Slay::Maker $self = shift ;
653 56 100       184 return $cwd_cache if defined $cwd_cache ;
654 28         302550 $cwd_cache = Cwd::cwd() ;
655 28         838 return $cwd_cache ;
656             }
657              
658              
659             =item e
660              
661             Returns true if the file exists, but uses the stat_cache if possible.
662              
663             =cut
664              
665             sub e {
666 55     55 1 715 my Slay::Maker $self = shift ;
667              
668 55         819 return defined $self->stat(@_) ;
669             }
670              
671              
672             =item exec_queue
673              
674             Executes the queued commands.
675              
676             =cut
677              
678             sub exec_queue {
679 28     28 1 628 my Slay::Maker $self = shift ;
680            
681 28         170 my $ident = ident $self;
682 28         52 for ( @{$queue_of{$ident}} ) {
  28         98  
683 44         609 my ( $target, $rule, @more ) = @$_ ;
684 44         345 $self->clear_stat( $target ) ;
685 44         62 push( @{$output_of{$ident}}, $rule->exec( $self, $target, @more ) ) ;
  44         289  
686 44         81 push( @{$made_targets_of{$ident}}, $target ) ;
  44         354  
687             }
688              
689 28         456 return @{$made_targets_of{$ident}} ;
  28         261  
690             }
691              
692              
693             =item find_rule
694              
695             Given a target, finds a rule.
696              
697             =cut
698              
699             sub find_rule {
700 56     56 1 100 my Slay::Maker $self = shift ;
701 56 50       367 my $options = ref $_[-1] eq 'HASH' ? pop : {} ;
702              
703 56         149 my $ident = ident $self;
704 56         96 my ( $target ) = @_ ;
705              
706 56         100 my $best_matches ;
707             my $best_rule ;
708 0         0 my $best_rank ;
709 0         0 my $best_target ;
710              
711 56         272 my $cwd = $self->cwd() ;
712 56         130 my $cwd_length = length $cwd ;
713              
714             ## If the target is absolute and is somewhere under the current dir, we
715             ## generate a relative target in addition to the absolute one.
716 56         119 my $rel_target ;
717              
718 56         2824 $target = $self->canonpath( $target ) ;
719 56         104 my $length = length $target ;
720              
721 56 50 33     1049 if ( $length && substr( $target, 0, 1 ) eq '/' ) {
722 0 0 0     0 if ( $length >= $cwd_length
    0 0        
      0        
      0        
723             && substr( $target, 0, $cwd_length ) eq $cwd
724             && ( $length == $cwd_length
725             || substr( $target, $cwd_length, 1 ) eq '/'
726             )
727             ) {
728 0         0 $rel_target = substr( $target, $cwd_length ) ;
729 0         0 $rel_target =~ s{^/+}{} ;
730             }
731             elsif ( defined $sym_cwd_cache && $real_cwd_cache eq $cwd ) {
732 0         0 $cwd_length = length( $sym_cwd_cache ) ;
733 0 0 0     0 if ( $length >= $cwd_length
      0        
      0        
734             && substr( $target, 0, $cwd_length ) eq $sym_cwd_cache
735             && ( $length == $cwd_length
736             || substr( $target, $cwd_length, 1 ) eq '/'
737             )
738             ) {
739 0         0 $rel_target = substr( $target, $cwd_length ) ;
740 0         0 $rel_target =~ s{^/+}{} ;
741             }
742             }
743             }
744              
745 56         110 for ( @{$rules_of{$ident}} ) {
  56         634  
746 133         1279 my ( $rank, $matches ) = $_->matches( $target, $options ) ;
747 133 100 100     1085 ( $best_target, $best_rule, $best_rank, $best_matches ) =
      66        
748             ( $target, $_, $rank, $matches )
749             if $rank && ( !defined $best_rank || $rank > $best_rank ) ;
750 133 50       451 if ( defined $rel_target ) {
751 0         0 my ( $rank, $matches ) = $_->matches( $rel_target, $options ) ;
752 0 0 0     0 ( $best_target, $best_rule, $best_rank, $best_matches ) =
      0        
753             ( $rel_target, $_, $rank, $matches )
754             if $rank && ( !defined $best_rank || $rank > $best_rank ) ;
755             }
756             }
757              
758 56         434 return ( $best_target, $best_rule, $best_matches ) ;
759             }
760              
761              
762             =item get_rule_info()
763              
764             Given a target that has already been processed with C,
765             either directly or indirectly, returns the rule that is used to
766             produce the target, a reference to an array of dependencies of the
767             target, and a reference to an array of the matches. Thus, you would call
768              
769             ($rule, $deps, $matches) = get_rule_info($target);
770              
771             Returns an undefined rule if there is no processed rule to produce the target.
772              
773             =cut
774              
775             sub get_rule_info {
776 0     0 1 0 my Slay::Maker $self = shift;
777 0         0 my ($target) = @_;
778 0         0 my ($rule, $deps, $matches);
779              
780 0         0 my $ident = ident $self;
781 0         0 foreach (@{$queue_of{$ident}}) {
  0         0  
782 0 0       0 return @$_[1..3] if $_->[0] eq $target;
783             }
784              
785 0         0 return;
786             }
787              
788             =item make
789              
790             Makes one or more target(s) if it is out of date. Throws exceptions if
791             the make fails. May partially make targets.
792              
793             =cut
794              
795              
796             sub make {
797 27     27 1 2009010 my Slay::Maker $self = shift ;
798 27 100       149 my $options = ref $_[-1] ? pop : {} ;
799              
800 27         111 my $ident = ident $self;
801 27 100       116 if ( ! $self->make_level ) {
802 26         162 $comments_of{$ident} = [] ;
803 26         94 $output_of{$ident} = [] ;
804 26         92 $made_targets_of{$ident} = [] ;
805             }
806              
807 27         133 $self->recurse_in ;
808              
809 27         64 eval {
810 27         153 $self->build_queue( @_, $options ) ;
811              
812 27 50       171 croak join( '', @{$errors_of{$ident}} ) if @{$errors_of{$ident}} ;
  0         0  
  27         221  
813              
814             # push(
815             # @{$self->{COMMENTS}},
816             # join( ', ', @_ ) . " up to date"
817             # ) unless $self->queue_size ;
818              
819 27         127 $self->exec_queue( $options ) ;
820             } ;
821 27         523 my $a = $@ ;
822              
823 27         75 eval {
824 27         209 $self->recurse_out ;
825             } ;
826              
827 27 50       250 if ( $a ) {
828 0         0 $@ = $a ;
829 0         0 die ;
830             }
831              
832 27 50 33     122 print STDERR map { "$_\n" } @{$comments_of{$ident}}
  0         0  
  0         0  
833             if $options->{debug} && ! $self->make_level ;
834              
835 27 50       126 croak join( '', @{$errors_of{$ident}} ) if @{$errors_of{$ident}} ;
  0         0  
  27         131  
836              
837 27         54 return @{$made_targets_of{$ident}} ;
  27         213  
838             }
839              
840              
841             =item make_level
842              
843             Returns 0 if make() has not been called (well, actually, if recurse_in()
844             has not been called). Returns number of recursive calls otherwise, so this
845             is equal to 1 when making something but not recursing.
846              
847             =cut
848              
849             sub make_level {
850 27     27 1 329 my Slay::Maker $self = shift ;
851 27         75 my $ident = ident $self;
852 27         48 return scalar( @{$rmake_stack_of{$ident}} ) ;
  27         235  
853             }
854              
855              
856             =item mtime
857              
858             This returns the mtime of a file, reading from the stat cache if possible.
859              
860             =cut
861              
862             sub mtime {
863 13     13 1 22 my Slay::Maker $self = shift ;
864              
865 13         39 return ($self->stat( @_ ))[9] ;
866             }
867              
868              
869             =item options
870              
871             Sets / gets a reference to the options hash.
872              
873             =cut
874              
875             sub options {
876 53     53 1 449 my Slay::Maker $self = shift ;
877 53         269 my $ident = ident $self;
878 53 50       152 $options_of{$ident} = shift if @_ ;
879 53         351 return $options_of{$ident} ;
880             }
881              
882              
883             sub output {
884 15     15 0 1518 my Slay::Maker $self = shift ;
885 15         51 my $ident = ident $self;
886 0         0 return wantarray ? @{$output_of{$ident}} :
  15         549  
887 15 50       56 join( '', @{$output_of{$ident}} ) ;
888             }
889              
890              
891             =item push
892              
893             Adds a ( target, rule ) tuple to the exec queue. Will not add the same target
894             twice.
895              
896             =cut
897              
898             sub push {
899 45     45 1 73 my Slay::Maker $self = shift ;
900 45         160 my ( $target, $rule ) = @_ ;
901              
902 45         204 my $ident = ident $self;
903 45 100       308 if ( $in_queue_of{$ident}{$target} ) {
904 1         14 push @{$comments_of{$ident}}, "Only making $target once" ;
  1         8  
905 1         3 return 1;
906             }
907              
908 44         605 push @{$queue_of{$ident}}, [ @_ ] ;
  44         214  
909 44         318 $in_queue_of{$ident}{$target} = $rule ;
910 44         140 return 1;
911             }
912              
913              
914             =item recurse_in
915              
916             Sets up for a recursive make. Called automatically by make() if make() is
917             already running.
918              
919             =cut
920              
921             sub recurse_in {
922 27     27 1 50 my ( $self ) = @_ ;
923 27         496 my $ident = ident $self;
924 27         44 CORE::push @{$rmake_stack_of{$ident}}, [ $queue_of{$ident},
  27         115  
925             $in_queue_of{$ident} ] ;
926 27         63 $queue_of{$ident} = [] ;
927 27         74 $in_queue_of{$ident} = {} ;
928             }
929              
930              
931             =item recurse_out
932              
933             Restored after a recursive make. Called automatically by make() if make() is
934             already running.
935              
936             =cut
937              
938             sub recurse_out {
939 27     27 1 60 my Slay::Maker $self = shift ;
940 27         104 my $ident = ident $self;
941 27         162 ($queue_of{$ident}, $in_queue_of{$ident}) =
942 27         114 @{pop @{$rmake_stack_of{$ident}}} ;
  27         46  
943             }
944              
945              
946             =item queue_size
947              
948             Number of rules that need to be made.
949              
950             =cut
951              
952             sub queue_size {
953 2     2 1 14 my Slay::Maker $self = shift ;
954 2         16 my $ident = ident $self;
955 2         5 scalar( @{$queue_of{$ident}} ) ;
  2         29  
956             }
957              
958              
959             =item remove_backup
960              
961             my $backup = $maker->backup( $target ) ;
962             ## ...
963             $maker->remove_backup(
964             $backup,
965             {
966             restore_if_unchanged => 1,
967             deps => \@deps,
968             }
969             ) ;
970              
971             Removes a backup of the target created with backup_target().
972              
973             =cut
974              
975             sub remove_backup {
976 2     2 1 10 my $self = shift ;
977              
978 2 50       16 my $options = ref $_[-1] ? pop : {} ;
979              
980 2         4 my ( $backup ) = @_ ;
981              
982 2 100 66     27 $self->restore( $backup, $options )
983             if $options->{restore_if_unchanged} && $self->target_unchanged( $backup );
984              
985 2 100       50 if ( defined $backup->{BACKUP} ) {
986 1 50       26 if ( -e $backup->{BACKUP} ) {
987 0 0 0     0 print STDERR "Unlinking $backup->{BACKUP}.\n"
988             if $options->{debug} || $backup->{OPTIONS}->{debug} ;
989              
990 0 0       0 unlink $backup->{BACKUP} or carp "$!: $backup->{BACKUP}" ;
991             }
992             else {
993 1 50 33     18 print STDERR "Can't unlink $backup->{BACKUP}: it's not present.\n"
994             if $options->{debug} || $backup->{OPTIONS}->{debug} ;
995             }
996             }
997             }
998              
999              
1000             =item replace_rules
1001              
1002             Replaces the rule for a target (or targets). The targets passed in must
1003             exactly match those of the rule to be replaced.
1004              
1005             =cut
1006              
1007             sub replace_rules {
1008 2     2 1 1619 my Slay::Maker $self = shift ;
1009 2         17 my $ident = ident $self;
1010 2         12 for my $new_rule ( @{$self->compile_rules( @_ )} ) {
  2         16  
1011 2         30 my $targets = $new_rule->targets ;
1012              
1013 2         4 for ( @{$rules_of{$ident}} ) {
  2         9  
1014 4 100       15 if ( $targets eq $_->targets ) {
1015 2         3 $_ = $new_rule ;
1016 2         35 return ;
1017             }
1018             }
1019 0         0 $self->add_rules( $new_rule ) ;
1020             }
1021             }
1022              
1023              
1024             =item restore
1025              
1026             my $backup = $maker->backup( $target, { move => 1 } ) ;
1027             ## Try to recreate target, setting $error on error
1028             $maker->restore( $backup )
1029             if $error ;
1030             $maker->restore( $backup, { deps => \@deps } )
1031             if ! $error && $maker->target_unchanged( $backup ) ;
1032             $maker->remove_backup( $backup ) ;
1033              
1034             Note that you only need this in case of an error. You can pass the
1035             restore_if_unchanged => 1 and deps => \@deps options to
1036             remove_backup().
1037              
1038             When backup() has been called, it's return value can be passed
1039             to restore_target() to restore the original target, timestamps and all.
1040              
1041             NOTE: restoring a target that's not changed is likely to cuase it to
1042             be remade every time once a dependency's timestamp becomes more recent.
1043             The C option allows the timestamps to be set to the newest of
1044             the original timestamps and the dependencies' timestamps. This should
1045             not be done if there was an error generating the file.
1046              
1047             =cut
1048              
1049             sub restore {
1050 1     1 1 3 my $self = shift ;
1051 1 50       17 my $options = ref $_[-1] ? pop : {} ;
1052 1         2 my ( $backup ) = @_ ;
1053              
1054 1 50       11 if ( -e $backup->{BACKUP} ) {
1055 0 0 0     0 print STDERR "Restoring '$backup->{BACKUP}' to '$backup->{FILE}'\n"
1056             if $options->{debug} || $backup->{OPTIONS}->{debug} ;
1057 0         0 move( $backup->{BACKUP}, $backup->{FILE} )
1058             }
1059              
1060 1 50       5 if ( defined $options->{deps} ) {
1061 1         6 my ( $atime, $mtime ) = ( 0, 0 ) ;
1062 1         4 ( $atime, $mtime ) = @{$backup->{STAT}}[8,9]
  1         5  
1063 1 50       5 if @{$backup->{STAT}} ;
1064              
1065 1         2 for ( @{$options->{deps}} ) {
  1         4  
1066 0         0 my $a = $self->atime( $_ ) ;
1067 0 0 0     0 $atime = $a if defined $a && $a > $atime ;
1068 0         0 my $m = $self->mtime( $_ ) ;
1069 0 0 0     0 $mtime = $m if defined $m && $m > $mtime ;
1070             }
1071 1         21 utime $atime, $mtime, $backup->{FILE} ;
1072             }
1073              
1074 1         3 $backup->{BACKUP} = undef ;
1075             }
1076              
1077             =item rules
1078              
1079             Gets or replaces the rules list
1080              
1081             =cut
1082              
1083             sub rules {
1084 19     19 1 7439 my Slay::Maker $self = shift ;
1085              
1086 19         71 my $ident = ident $self;
1087 19 100       77 if ( @_ ) {
1088              
1089 16         53 $rules_of{$ident} = [] ;
1090 16         332 $self->add_rules( @_ ) ;
1091             }
1092              
1093 19 50       270 return wantarray? @{$rules_of{$ident}} : $rules_of{$ident} ;
  0         0  
1094             }
1095              
1096             =item size
1097              
1098             This returns the size of a file, reading from the stat cache if possible.
1099              
1100             =cut
1101              
1102             sub size {
1103 2     2 1 3 my Slay::Maker $self = shift ;
1104              
1105 2         6 return ($self->stat( @_ ))[7] ;
1106             }
1107              
1108              
1109             =item stat
1110              
1111             Looks in the stat cache for the stat() results for a path. If not found,
1112             fills the cache. The cache is shared between all instances of this class,
1113             and may be cleared using clear_stat_cache().
1114              
1115             =cut
1116              
1117             sub stat {
1118 116     116 1 230 my Slay::Maker $self = shift ;
1119 116         265 my ( $path ) = @_ ;
1120              
1121 116 100       583 return @{$stat_cache{$path}}
  19         158  
1122             if defined $stat_cache{$path} ;
1123              
1124 97         1865 my @stats = stat $path ;
1125 97 100       1863 $stat_cache{$path} = \@stats if @stats ;
1126              
1127 97 100       271 return @{$stat_cache{$path}}
  31         419  
1128             if defined $stat_cache{$path} ;
1129              
1130 66         517 return () ;
1131             }
1132              
1133             =item target_unchanged
1134              
1135             Takes the result of backup_target() and checks to see if the target has
1136             been changed or removed.
1137              
1138             =cut
1139              
1140             sub target_unchanged {
1141 2     2 1 7 my $self = shift ;
1142 2         4 my ( $context ) = @_ ;
1143              
1144 2         6 my $target = $context->{FILE} ;
1145              
1146 2         8 $self->clear_stat( $target ) ;
1147              
1148             ## See if the file disappeared or appeared. This is an exclusive-or.
1149 2         15 return 0
1150 2 50       7 if @{$context->{STAT}}
    50          
1151             ? ! $self->e( $target )
1152             : $self->e( $target ) ;
1153              
1154             ## It's unchanged if neither existed.
1155 2 50       5 return 1 unless @{$context->{STAT}} ;
  2         11  
1156              
1157             ## It's not unchanged if it's size changed
1158 2 100       19 return 0
1159             if $context->{STAT}->[7] ne $self->size( $target ) ;
1160              
1161             ## TODO: Use Diff.pm to do this. Also, investigate using MD5 as an
1162             ## alternative to diffing, to save the copy operation.
1163 1 50 33     11 return 0
1164             if ! $context->{OPTIONS}->{stat_only} &&
1165             length `diff --brief "$context->{BACKUP}" "$target"` ;
1166              
1167 1         13 return 1 ;
1168             }
1169              
1170              
1171             =back
1172              
1173             =head1 TODO
1174              
1175             =over
1176              
1177             =item *
1178              
1179             Propagate effects of restored timestamps.
1180              
1181             If a target has it's timestamps restored as a result of detecting no
1182             change (see options detect_no_size_change and detect_no_diffs), then
1183             there may well be no need to actually execute later rules.
1184              
1185             One way to do this is to re-check the mtime dependencies when rebuilding.
1186             Another is to subscribe later items in the queue to earlier items and have
1187             the earlier items set a flag that tells the later items to go ahead and
1188             execute. Items could flag themselves to execute regardless, which we might
1189             want to do if a dependency is not present when make is run.
1190              
1191             =item *
1192              
1193             Don't really call diff(1) for detect_no_diffs
1194              
1195             =back
1196              
1197             =head1 AUTHOR
1198              
1199             Barrie Slaymaker
1200              
1201             =head1 LICENSE
1202              
1203             Copyright 2000, R. Barrie Slaymaker, Jr., All Rights Reserved.
1204              
1205             That being said, do what you will with this code, it's completely free.
1206              
1207             Please let me know of any improvements so I can have the option of folding
1208             them back in to the original.
1209              
1210             =cut
1211             }
1212              
1213             1 ;