File Coverage

blib/lib/Slay/MakerRule.pm
Criterion Covered Total %
statement 188 196 95.9
branch 99 130 76.1
condition 22 36 61.1
subroutine 15 15 100.0
pod 5 6 83.3
total 329 383 85.9


line stmt bran cond sub pod time code
1             package Slay::MakerRule ;
2              
3             #
4             # Copyright (c) 1999 by Barrie Slaymaker, rbs@telerama.com
5             #
6             # You may distribute under the terms of either the GNU General Public
7             # License or the Artistic License, as specified in the README file.
8             #
9              
10             =head1 NAME
11              
12             Slay::MakerRule - a class for making things with dependencies
13              
14             =head1 SYNOPSIS
15              
16             use strict ;
17              
18             use Slay::MakerRule ;
19              
20             $t1 = Slay::MakerRule->new( { rule => [
21             \@target, ## Filenames made by \@actions
22             \@dependencies, ## Files or Slay::MakerRule objects
23             \@actions, ## Command lines or sub{}
24             ] }
25             ) ;
26              
27             Any or all of the three parameters may be scalars if there is only one
28             thing to pass:
29              
30             $t1 = Slay::MakerRule->new( { rule => [
31             $target,
32             $dependency,
33             $action,
34             ] }
35             ) ;
36              
37             New can also be called with separate hash elements for each part:
38              
39             $t1 = Slay::MakerRule->new( {
40             PATS => \@target, ## Filenames made by \@actions
41             DEPS => \@dependencies, ## Files or Slay::MakerRule objects
42             ACTS => \@actions, ## Command lines or sub{}
43             ] }
44             ) ;
45              
46             =head1 DESCRIPTION
47              
48             =over
49              
50             =cut
51              
52 1     1   7 use strict ;
  1         2  
  1         48  
53              
54 1     1   6 use Carp ;
  1         2  
  1         72  
55 1     1   6 use Fcntl qw( :DEFAULT :flock ) ;
  1         2  
  1         1755  
56 1     1   16 use File::Basename ;
  1         2  
  1         88  
57 1     1   7 use File::Path ;
  1         1  
  1         57  
58 1     1   17149 use IPC::Run qw( run ) ;
  1         127850  
  1         88  
59              
60             our $VERSION = 0.06;
61              
62 1     1   1940 use Class::Std;
  1         20253  
  1         8  
63              
64             { # Creates the closure for the attributes
65              
66             # Attributes
67              
68             my %acts_of : ATTR( :init_arg :default<[]> );
69             my %cmd_of : ATTR ;
70             my %compiled_pats_of : ATTR ;
71             my %deps_of : ATTR( :init_arg :default<[]> );
72             my %opts_of : ATTR( :init_arg :default<{}> );
73             my %pats_of : ATTR( :init_arg :default<[]> );
74             my %in_make_of : ATTR ;
75              
76             sub START {
77 51     51 0 29549 my ($self, $ident, $args_ref) = @_;
78              
79 51         147 my $rule = $args_ref->{rule};
80 51 100       379 my @rule = ref $rule eq 'ARRAY' ? @$rule : $rule if defined $rule;
    50          
81              
82 51 50       168 if (@rule) {
83             ## It's qw( patterns, ':', dependencies, '=', actions ).
84             ## NB: The ':' and '=' may appear as the last char of a scalar param.
85 51 50       216 $acts_of{$ident} = [] unless $acts_of{$ident};
86 51 50       138 $deps_of{$ident} = [] unless $deps_of{$ident};
87 51 50       136 $opts_of{$ident} = {} unless $opts_of{$ident};
88 51 50       110 $pats_of{$ident} = [] unless $pats_of{$ident};
89              
90 51 100       230 $opts_of{$ident} = pop @rule if ref $rule[-1] eq 'HASH' ;
91 51         77 my $a = $pats_of{$ident} ;
92 51         60 my $e ;
93             my $na ;
94 51         235 for ( @rule ) {
95 150         230 $e = $_ ;
96 150         175 $na = undef ;
97 150 100       377 unless ( ref $e ) {
98 115 100       390 if ( $e =~ /^:$/ ) { $a = $deps_of{$ident} ; next }
  9         21  
  9         22  
99 106 100       681 if ( $e =~ /^=$/ ) { $a = $acts_of{$ident} ; next }
  22         47  
  22         38  
100 84 100       304 if ( $e =~ s/:$// ) { $na = $deps_of{$ident} }
  12         32  
101 84 100       253 if ( $e =~ s/=$// ) { $na = $acts_of{$ident} }
  18         126  
102             }
103 119         344 push @$a, $e ;
104 119 100       654 $a = $na if defined $na ;
105             }
106             }
107            
108             }
109              
110             =item var_expand_dep($dependency,$target,$matches)
111              
112             Static function, mostly for internal use. Called by L to
113             expand variables inside a dependency. Returns the expanded string.
114              
115             Recognized expansions:
116              
117             =over 8
118              
119             =item C<$>I, C<${>IC<}>
120              
121             Expands to the value of C<< $matches->[ >>IC<-1]> (like in the
122             normal C operator)
123              
124             =item C<$TARGET>, C<${TARGET}>
125              
126             Expands to the value of C<$target>
127              
128             =item C<$ENV{>IC<}>
129              
130             Expands to the value of the environment variable I.
131              
132             =back
133              
134             =cut
135              
136             sub var_expand_dep {
137 3     3 1 8 my ($dep,$target,$matches)=@_;
138 3         31 $dep=~s{
139             \$(?:
140             (?:\{(\d+)\})
141             |(?:(\d+))
142             |(?:(TARGET\b))
143             |(?:\{(TARGET)\})
144             |(?:ENV\{(.*?)\})
145             )
146             }{
147 3 0 100     86 defined($1)||defined($2) ? $matches->[($1||$2)-1] :
    50 66        
    100 33        
148             defined($3)||defined($4) ? $target :
149             defined($5) ? $ENV{$5} : die('Something wrong')
150             }gsmxe;
151 3         12 return $dep;
152             }
153              
154             =item check
155              
156             Builds the queue of things to make if this target or its dependencies are
157             out of date.
158              
159             =cut
160              
161             sub check {
162 53     53 1 107 my Slay::MakerRule $self = shift ;
163 53         141 my $ident = ident $self;
164 53 50       205 my $user_options = ref $_[-1] ? pop : {} ;
165 53         108 my ( $make, $target, $matches ) = @_ ;
166              
167             ## We join the options sets so that passed-in override new()ed, and
168             ## we copy them in case somebody changes their mind.
169 53         388 my $options = {
170 53         493 %{$make->options},
171 53         104 %{$opts_of{$ident}},
172             %$user_options,
173             } ;
174              
175 53 50       178 print STDERR "$target: checking ".$self->targets." ", %$options, "\n"
176             if $options->{debug} ;
177 53 100       336 if ( $in_make_of{$ident}++ ) {
178 2         18 warn "Ignoring recursive dependency on " . $self->targets ;
179 2         25 $in_make_of{$ident} = 0;
180 2         15 return 0;
181             }
182              
183 51         89 my @required ;
184 51 100       185 push @required, "forced" if $options->{force} ;
185 51 100       203 push @required, "!exists" unless $make->e( $target ) ;
186              
187 51 50 33     326 if ( $options->{debug} && $make->e( $target ) ) {
188 0         0 print STDERR (
189             "$target: size, atime, mtime: ",
190             join(
191             ', ',
192             $make->size( $target ),
193             scalar( localtime $make->atime( $target ) ),
194             scalar( localtime $make->mtime( $target ) ),
195             ),
196             "\n"
197             ) ;
198             }
199              
200             my @deps = map {
201 27 100       129 if ( ref $_ eq 'CODE' ) {
  51 100       305  
202 3         34 $_->( $make, $target, $matches ) ;
203             }
204             elsif ( /\$/ ) {
205 3         15 my $dep = $_ ;
206 3         17 var_expand_dep($dep,$target,$matches);
207             }
208             else {
209 21         159 $_ ;
210             }
211 51         99 } @{$deps_of{$ident}} ;
212              
213 51 50 33     627 print STDERR "$target: deps: ", join( ', ', @deps ), "\n"
214             if $options->{debug} && @deps ;
215              
216             ## If the deps are to be rebuilt when our dependencies are checked,
217             ## then we must be remade as well.
218 51         351 my $count=$make->check_targets( @deps, $user_options ) ;
219 51 100       567 push @required, "!deps" if $count;
220              
221 51 100       256 unless ( @required ) {
222             ## The target exists && no deps need to be rebuilt. See if the
223             ## target is up to date.
224 10         23 my $max_mtime ;
225 10         108 for ( @deps ) {
226 7 50       116 print STDERR "$target: checking " . Cwd::cwd() . " $_\n"
227             if $options->{debug} ;
228 7         44 my $dep_mtime = $make->mtime( $_ ) ;
229 7 50       31 print STDERR "$target: $_ mtime " . localtime( $dep_mtime ) . "\n"
230             if $options->{debug} ;
231 7 100 66     87 $max_mtime = $dep_mtime
      33        
232             if defined $dep_mtime
233             && ( ! defined $max_mtime || $dep_mtime > $max_mtime ) ;
234             }
235 10 100 100     164 push @required, "out of date"
236             if defined $max_mtime && $max_mtime > $make->mtime( $target ) ;
237              
238              
239             }
240              
241 51         116 $count=0;
242              
243 51 100       165 if ( @required ) {
244 45 50       134 print STDERR "$target: required ( ", join( ', ', @required ), " )\n"
245             if $options->{debug} ;
246 45         326 $count+=$make->push( $target, $self, \@deps, $matches, $options ) ;
247             }
248             else {
249 6 50       603 print STDERR "$target: not required\n"
250             if $options->{debug} ;
251             }
252 51         196 $in_make_of{$ident}--;
253 51         419 return $count;
254             }
255              
256              
257             sub _compile_pattern {
258 51     51   111 my ( $pat ) = @_ ;
259              
260 51         72 my $exactness = -1 ;
261 51         73 my $lparens = 0 ;
262 51         1158 my $re ;
263 51 100       183 if ( ref $pat ne 'Regexp' ) {
264 49         101 $re = $pat ;
265             ## '\a' => 'a'
266             ## '\*' => '\*'
267             ## '**' => '.*'
268             ## '*' => '[^/]*'
269             ## '?' => '.'
270 49         720 $re =~ s{
271             ( \\.
272             | \*\*
273             | .
274             )
275             }{
276 181 100       1644 if ( $1 eq '?' ) {
    100          
    100          
    100          
    100          
    100          
277 4         28 --$exactness ;
278 4         34 '[^/]' ;
279             }
280             elsif ( $1 eq '*' ) {
281 8         14 --$exactness ;
282 8         44 '[^/]*' ;
283             }
284             elsif ( $1 eq '**' ) {
285 3         7 --$exactness ;
286 3         24 '.*' ;
287             }
288             elsif ( $1 eq '(' ) {
289 6         10 ++$lparens ;
290 6         32 '(' ;
291             }
292             elsif ( $1 eq ')' ) {
293 6         23 ')' ;
294             }
295             elsif ( length $1 > 1 ) {
296 6         19 quotemeta(substr( $1, 1 ) );
297             }
298             else {
299 148         545 quotemeta( $1 ) ;
300             }
301             }xeg ;
302 49         142 $re = "^$re\$" ;
303             }
304             else {
305             ## Destroy it in order to get metrics.
306 2         5 $re = "$pat" ;
307 2         23 $re =~ s{
308             (
309             \\.
310             |\(\??
311             |(?:
312             .[?+*]+
313             |\.[?+*]*
314             )+
315             )
316             }{
317 7 100       47 if ( substr( $1, 0, 1 ) eq '\\' ) {
    100          
318             }
319             elsif ( substr( $1, 0, 1 ) eq '(' ) {
320 4 100       109 ++$lparens
321             if substr( $1, 0, 2 ) ne '(?' ;
322             }
323             else {
324 2         3 --$exactness ;
325             }
326             ## Return the original value, just for speed's sake
327 7         44 $1 ;
328             }xeg ;
329             ## Ok, now copy it for real
330 2         6 $re = $pat ;
331             }
332              
333             # print STDERR (
334             # "re: $re\n",
335             # "lparens: $lparens\n",
336             # "exactness: $exactness\n",
337             # ) if $options->{debug} ;
338              
339 51         621 return [ $re, $exactness, $lparens ] ;
340             }
341              
342              
343             =item exec
344              
345             Executes the action(s) associated with this rule.
346              
347             =cut
348              
349             sub exec {
350 44     44 1 197 my Slay::MakerRule $self = shift ;
351 44 50       404 my $options = ref $_[-1] eq 'HASH' ? pop : {} ;
352 44         96 my ( $make, $target, $deps, $matches ) = @_ ;
353              
354 44         135 my $ident = ident $self;
355 44         103 my @output ;
356 44 50       119 print STDERR "$target: in exec() for ". $self->targets.", ", %$options, "\n"
357             if $options->{debug} ;
358              
359 44         127 my $target_backup ;
360              
361 44 100 66     604 if ( ( $options->{detect_no_size_change} || $options->{detect_no_diffs} )
      66        
362             && ! -d $target
363             ) {
364 2         46 $target_backup = $make->backup(
365             $target,
366             {
367             stat_only => ! $options->{detect_no_diffs},
368             move => $options->{can_move_target},
369             debug => $options->{debug},
370             }
371             ) ;
372             }
373              
374 44 50       131 if ( $options->{auto_create_dirs} ) {
375             ## Use dirname so that 'a/b/c/' only makes 'a/b', leaving it up to the
376             ## make rule to mkdir c/. fileparse would return 'a/b/c'.
377 0         0 my ( $dir ) = dirname( $target ) ;
378 0 0       0 if ( ! -d $dir ) {
379 0         0 mkpath( [ $dir ] ) ;
380 0 0       0 warn "Failed to create $dir" unless -d $dir ;
381             }
382             }
383              
384 44         67 for my $act ( @{$acts_of{$ident}} ) {
  44         282  
385 44         4615 local %ENV = %ENV ;
386 44         588 $ENV{TARGET} = $target ;
387 44         332 delete $ENV{$act} for grep {/^(DEP|MATCH)\d+$/} keys %ENV ;
  1056         3689  
388 44         434 $ENV{"DEP$_"} = $deps->[$_] for (0..$#$deps) ;
389 44         161 $ENV{"MATCH$_"} = $matches->[$_] for (0..$#$matches) ;
390              
391 44 100       302 if ( ref $act eq 'CODE' ) {
    100          
    50          
392 30 50       98 print STDERR "$target: execing CODE\n"
393             if $options->{debug} ;
394 30         274 my $out = $act->( $make, $target, $deps, $matches ) ;
395 30 50       555247 $out = '' unless defined $out ;
396 30         1159 push @output, $out ;
397             }
398             elsif ( ref $act eq 'ARRAY' ) {
399 1 50       25 print STDERR "$target: execing ", join( ' ', map {"'$_'"} @$act ), "\n"
  0         0  
400             if $options->{debug} ;
401             ## It's a command line in list form, so don't exec the shell
402 1         9 my $out ;
403 1         17 run $act, \undef, \$out ;
404 1         21130 push( @output, $out ) ;
405             }
406             elsif ( ! ref $act ) {
407 13         56 $_ = $act; # N.B. Work on a copy...
408 13         332 s/\$(\d+)/$matches->[$1-1]/g ;
409 13         31 s/\$\{(\d+)\}/$matches->[$1-1]/g ;
410 13 50       55 print STDERR "$target: execing '$_' \n"
411             if $options->{debug} ;
412             ## It's a command line in string form
413 13         17 my $out ;
414 13         174 run [ 'sh', '-c', $_ ], \undef, \$out ;
415 13         514626 $_ =~ m{(\S*)} ;
416 13         147 my $cmd = $1 ;
417 13         1709 push( @output, $out ) ;
418             }
419             else {
420 0         0 confess "Invalid type for a Slay::MakerRule rule: " . ref $act ;
421             }
422             }
423              
424 44         556 $make->clear_stat( $target ) ;
425 44         575 my @new_stats = $make->stat( $target ) ;
426              
427 44 100       208 if ( defined $target_backup ) {
428 2         20 $make->remove_backup(
429             $target_backup,
430             {
431             restore_if_unchanged => 1,
432             deps => $deps
433             }
434             ) ;
435             }
436              
437 44 50       937 return wantarray ? @output : join( '', @output ) ;
438             }
439              
440              
441             =item targets
442              
443             returns either ( target1, target2, ... ) or "target1, target2, ..." depending
444             on context.
445              
446             =cut
447              
448             sub targets {
449 8     8 1 16 my Slay::MakerRule $self = shift ;
450 8         33 my $ident = ident $self;
451 8 50       28 return wantarray ? @{$pats_of{$ident}} : join( ', ', @{$pats_of{$ident}} );
  0         0  
  8         335  
452             }
453              
454              
455             =item matches
456              
457             Checks the target list to see if it matches the target passed in.
458              
459             =cut
460              
461             sub matches {
462 145     145 1 683 my Slay::MakerRule $self = shift ;
463 145 100       700 my $options = ref $_[-1] eq 'HASH' ? pop : {} ;
464              
465 145         569 my $ident = ident $self;
466 145         669 my ( $target ) = @_ ;
467              
468 145         1264 my $max_exactness ;
469             my @matches ;
470              
471 145 100       655 if ( ! $compiled_pats_of{$ident} ) {
472 51         330 $compiled_pats_of{$ident} = [
473             map {
474 51         164 _compile_pattern $_
475             } grep {
476 49         182 ref $_ ne 'CODE'
477 49         79 } @{$pats_of{$ident}}
478             ] ;
479             }
480             #print STDERR join("\n",map { join(',', @$_ ) } @{$self->{COMPILED_PATS}} ), "\n" ;
481 145         501 for ( @{$compiled_pats_of{$ident}} ) {
  145         424  
482 151         661 my ( $re, $exactness, $lparens ) = @$_ ;
483             #print STDERR "$target: ?= $re\n" ;
484 151 100 66     4734 if ( $target =~ $re &&
      66        
485             ( ! defined $max_exactness || $exactness > $max_exactness )
486             ) {
487 69         407 $max_exactness = $exactness ;
488 1     1   6261 no strict 'refs' ;
  1         2  
  1         376  
489 9         168 @matches = map {
490 69         333 ${$_}
  9         13  
491             } (1..$lparens) ;
492             # print STDERR (
493             # "$target: matches: ",
494             # join( ',', map { defined $_ ? "'$_'" : '' } @matches),
495             # "\n"
496             # ) if $options->{debug} ;
497              
498             }
499             }
500              
501 145 100       1077 return defined $max_exactness ? ( $max_exactness, \@matches ) : () ;
502             }
503              
504             =back
505              
506             =cut
507              
508             }
509              
510             1 ;