File Coverage

blib/lib/Slay/Makefile.pm
Criterion Covered Total %
statement 215 215 100.0
branch 107 112 95.5
condition 23 26 88.4
subroutine 17 17 100.0
pod 5 5 100.0
total 367 375 97.8


line stmt bran cond sub pod time code
1             package Slay::Makefile;
2              
3 3     3   249061 use warnings;
  3         35  
  3         108  
4 3     3   16 no warnings qw(void);
  3         6  
  3         84  
5 3     3   14 use strict;
  3         6  
  3         135  
6              
7             =head1 NAME
8              
9             Slay::Makefile - Wrapper to Slay::Maker that reads the rules from a file
10              
11             =cut
12              
13             our $VERSION = '0.13_50';
14              
15             =head1 DESCRIPTION
16              
17             C is a make engine that uses perl declaration syntax for
18             rules, including regular expressions for targets and anonymous subs
19             for targets, dependencies, and actions. This C
20             wrapper allows for the rules to be contained within a SlayMakefile
21             file whose syntax is similar to that of a normal Makefile.
22              
23             =head1 FILE SYNTAX
24              
25             The file syntax is a series of statements where each statement is one of:
26              
27            
28              
29             :
30            
31              
32             [-] include
33              
34             # Comment
35              
36             has the syntax:
37              
38             {
39            
40             }
41              
42             where is any series of perl statements.
43              
44             ) is either a space-delimited set of targets, each of which
45             is either a literal string or a which returns an array,
46             each of which is either a literal string or a regular expression
47             ('Regexp') reference (C). A literal string can contain a
48             C<%> character to act as a wild-card, just as with GNU make. However,
49             the Regexp feature is more general, since it can capture more than one
50             substring and use the values C<$!>, C<$2>, ... inside the
51             dependencies. Note that only one target can realistically contain
52             wildcards, whether in a Regexp or using C<%>, since there is only one
53             set of C<$1>, C<$2>, ... variables.
54              
55             The colon separating a for must be on the
56             same line as the closing brace of the .
57              
58             is either a space-delimited set of dependency strings
59             or a which returns an array of dependencies (or a
60             combination). The dependency string can contain C<$1>, C<$2>, ..., or
61             C<%>, which is synonymous with C<$1> and C<${TARGET}>, which gets the
62             target name. They can also use any scalar global variables previously
63             defined in a . A dependency is called with
64             the values C<($make, $target, $matches)>, where C<$make> is a
65             C object, C<$target> is the target name, and C<$matches> is
66             a reference to an array containing the captured values from that
67             target's Regexp (if any).
68              
69             The colon separating a for must be on the
70             same line as the opening brace of the .
71              
72             is a series of zero or more action "lines", where each
73             action is either a string, which will be executed inside a shell, a
74             perl anonymous array, which is executed without a shell (see
75             IPC::Run), or a . For purposes of this discussion, a
76             "line" continues as long as the lines of string action end with "\" or
77             as long as a perl anonymous array or do not have their
78             closing punctuation. A string action can use the strings C<$1>,
79             C<$2>, ..., for the matches, C<$DEP0>, C<$DEP1>, ..., for the
80             dependencies, and C<$TARGET>, which represents the target being built.
81             For make enthusiasts, C<$*> can be used for C<$1>. A string action
82             can also use any scalar global variables previously defined in a
83             . An action is called with the values
84             C<($make, $target, $deps, $matches)>, where C<$make> is a C
85             object, C<$target> is the target name, C<$deps> is a reference to the
86             array of dependencies and $matches is a reference to an array
87             containing the captured values from that target's Regexp (if any).
88              
89             An include line includes the content of a file with as a
90             SlayMakefile file. If there is no such file, C tries
91             to build it using rules that have already been presented. If there is
92             no such rule, C exits with an error unless there was a
93             C<-> before the "include".
94              
95             The equivalent of make's defines are handled by setting perl global
96             variables. Each main is executed in the order it appears
97             in the file, but any that is part of a dependency or
98             action is evaluated lazily, so that all the global variables will have
99             been set. A main is called with the value
100             C<($makefile)>, where C<$makefile> is the C object,
101             so that such code can, for example, recursively call the parse method.
102              
103             Comments begin with a C<#> and extend to the end of line.
104              
105             Continuation lines can be specified by putting a backslash at the end
106             of the previous line, provided however, that continuation lines are
107             unnecessary (automatic) within a perl block or perl anonymous array.
108             Although continuation lines in a perl dependency or action must begin
109             with at least one space so a that the parser does not think a new rule
110             is beginning, the minimum indentation is removed prior to evaluation
111             so that HEREIS strings can be used.
112              
113             =head1 METHODS
114              
115             =over
116              
117             =cut
118              
119 3     3   15 use Carp;
  3         4  
  3         260  
120 3     3   2078 use Slay::Maker 0.04;
  3         197976  
  3         9228  
121              
122             =item C
123              
124             Class method. Creates a new C object using the
125             optional C<$options> argument. It also process the following options
126             out of C<$options>:
127              
128             strict: If 0, do not enforce strict checking on perl blocks
129              
130             =cut
131              
132             sub new {
133 36     36 1 193689 my ($class, $options) = @_;
134              
135 36         243 my $self = bless {}, $class;
136 36 100       256 $options = {} unless $options;
137              
138 36         809 $self->{maker} = new Slay::Maker({options => $options});
139 36         19644 $self->{options} = $options;
140              
141 36         158 return $self;
142             }
143              
144             =item C
145              
146             Method. Returns the C object used by this
147             C object.
148              
149             =cut
150              
151             sub maker : method {
152 111     111 1 719 return $_[0]{maker};
153             }
154              
155             =item C
156              
157             Method. Calls the C object's make method to build the list
158             of targets. If no targets are given, makes the targets of the first rule
159             with constant targets.
160              
161             =cut
162              
163             # '
164             sub make : method {
165 58     58 1 2043890 my $self = shift;
166            
167             $self->_croak('No targets specified and no default target provided')
168 58 100 100     490 if ! @_ && ! $self->{first};
169 57 100       890 $self->{maker}->make(@_ ? @_ : $self->{first}->targets);
170             }
171              
172             =item C
173              
174             Method. Parses file C<$filename> as a SlayMakefile and populates the
175             C object with its rules. Returns a reference to an array
176             of parse errors.
177              
178             =cut
179              
180             sub parse : method {
181 62     62 1 963 my ($self, $filename) = @_;
182              
183 62 50       2578 open IN, $filename or croak "Cannot open $filename";
184 62         2795 my $string = join '', ;
185 62         999 close IN;
186 62         563 return $self->parse_string($string, $filename);
187             }
188              
189             =item C
190              
191             Method. Parses C<$string> as a SlayMakefile. If C<$filename> and/or
192             C<$lineno> arguments are provided, they are used for more detailed
193             error reporting. Returns a reference to an array of parse errors.
194              
195             =cut
196              
197             sub parse_string : method {
198 62     62 1 528 my ($self, $string, $filename, $lineno) = @_;
199              
200 62         366 $self->{errors} = [];
201 62   50     623 $lineno ||= 1;
202              
203 62         125 my $in_braces = 0;
204 62         150 my $stmt = '';
205 62         138 my $stmt_line = $lineno;
206              
207 62         249 my $EOL = '(?:\n\r?|\r\n?)';
208 62         1602 my @lines = split /$EOL/o, $string;
209             parse_stmt:
210 62         492 for (my $l=0; $l < @lines; $l++) {
211 489         1050 $_ = $lines[$l];
212             # TODO: The following does not check whether braces are in
213             # strings, comments, or are backslash-quoted...
214 489 100       1760 s/^\s*\#.*// if $in_braces == 0; # Delete comments
215 489         1134 my $net_braces = tr/\{// - tr/\}//;
216 489         709 $in_braces += $net_braces;
217             # Append this line to the previous statement
218 489         1064 $stmt .= "$_\n";
219 489 100 100     2055 if ($in_braces <= 0 && ! /\\$/) {
220             # We may have a statement to process
221 331 100       2155 if ($stmt =~ /^\s*$/) {
    100          
222             # Ignore null statement
223             }
224             elsif ($stmt =~ /^\s*(-?)\s*include\s+(?!:)(.*)/) {
225             # include directive
226 27         230 my ($opt, $incfile) = ($1, $2);
227 27         2221 $incfile = eval qq(package Slay::Makefile::Eval; "$incfile");
228 27 100       632 if (! -f $incfile) {
229             # Check if we can build it with rules we already have
230 4         26 eval { $self->{maker}->make($incfile) } ;
  4         68  
231             }
232 27 100       20817 if (-f $incfile) {
    100          
233 25         57 1; # Coverage misses next statement without this
234 25         152 $self->parse($incfile);
235             }
236             elsif (! $opt) {
237 1         49 1; # Coverage misses next statement without this
238 1         30 $self->_croak("Cannot build include file '$incfile'",
239             $filename, $stmt_line);
240             }
241             }
242             else {
243 114         234 my $braces;
244             # Need to collapse matching { } pairs
245 114         511 ($stmt, $braces) = _collapse($stmt);
246 114 100       559 my $re = %$braces ? join('|', keys %$braces) : "\n";
247 114 100       901 if ($stmt =~ /^(?!\s)(.*?)\s*:\s*(.*)/) {
248 78         383 my ($raw_tgts, $raw_deps) = ($1, $2);
249 78         159 my (@tgts, @deps, @acts);
250 78         140 my $rule_line = $stmt_line;
251             # It's a rule
252              
253             # Process the targets
254 78         357 my @raw_tgts = split /\s+/, $raw_tgts;
255 78         300 foreach my $target (@raw_tgts) {
256 82 100       809 if ($target =~ s/^($re)//) {
257             # A perl expression
258 6         70 my $perl = _expand($1, $braces);
259 6 100       38 if ($perl eq '') { # It was a \ at end of line
260 1         3 $rule_line++;
261 1         2 next;
262             }
263 5         44 my @targets = $self->_eval($perl,
264             $filename, $rule_line);
265 5         37 foreach (@targets) {
266 8         38 my $ref = ref $_;
267 8 100 100     79 if ($ref eq 'Regexp' || $ref eq '') {
268 7         16 1; # Coverage misses next stmt without this
269 7         23 push @tgts, $_;
270             }
271             else {
272 1         26 $self->_carp("Illegal return type for target: $ref",
273             $filename, $rule_line);
274             }
275             }
276 5 100       194 $self->_carp("Extraneous input: $target",
277             $filename, $rule_line)
278             if $target !~ /^\s*$/;
279 5         237 $rule_line += $perl =~ tr/\n//;
280             }
281             else {
282             # A string target
283 76 100       291 if ($target =~ /\%/) {
284 33         396 my @const = split /(\%)/, $target;
285 33         105 grep do { $_ = "\Q$_" }, @const;
  202         497  
286 33 100       457 my $qr = 'qr(^' .
287             join('', map($_ eq '\%' ? '(.*)' : $_,
288             @const)) . '$)';
289 33         155 ($target) = $self->_eval($qr, $filename,
290             $rule_line);
291             }
292 76         371 push @tgts, $target;
293             }
294             }
295              
296             # Process the dependencies
297 78         281 my @raw_deps = split /\s+/, $raw_deps;
298 78         282 grep s/\%/\$1/g, @raw_deps; # Handle % in dependencies;
299 78         285 foreach my $dep (@raw_deps) {
300 62 100       530 if ($dep =~ s/^($re)//) {
301             # A perl expression
302 11         61 my $perl = _expand($1, $braces);
303 11 100       43 if ($perl eq '') { # It was a \ at end of line
304 2         5 $rule_line++;
305 2         17 next;
306             }
307 9         58 my ($sub) = $self->_eval("sub { $perl }",
308             $filename, $rule_line);
309 9         30 push @deps, $sub;
310 9 100       96 $self->_carp("Extraneous input: $dep",
311             $filename, $rule_line)
312             if $dep !~ /^\s*$/;
313 9         340 $rule_line += $perl =~ tr/\n//;
314             }
315             else {
316             # A string dependency
317 51         225 push @deps, _substitute($dep);
318             }
319             }
320              
321             # Read the actions
322 78         175 my $act = '';
323 78         156 my $in_braces = 0; # Shadows outer $in_braces
324 78         154 $stmt_line = $lineno+$l+1;
325 78   100     770 while ($l < $#lines && ($lines[++$l] =~ /^\s/ ||
      100        
326             $lines[$l] =~ /^\z/ && $in_braces))
327             {
328 169         350 $_ = $lines[$l];
329 169         356 my $net_braces = tr/\{\[// - tr/\}\]//;
330 169         247 $in_braces += $net_braces;
331 169         564 s/^\t//;
332 169         400 $act .= "$_\n";
333 169 100 66     796 if ($in_braces <= 0 && ! /\\$/) {
334             # We have another action
335 66         194 my ($act1, $braces) = _collapse($act);
336 66 100       363 my $braces_re = %$braces ?
337             join('|', keys %$braces) : "\n";
338 66         187 my ($act2, $brackets) = _collapse($act1, qw([ ]));
339 66 100       213 my $brackets_re = %$brackets ?
340             join('|', keys %$brackets) : "\n";
341 66 100       1333 if ($act2 =~ s/^\s*($braces_re)//) {
    100          
342             # It's a perl block
343 49         197 my $exp = _expand($1, $braces);
344 49         397 my ($sub) =
345             $self->_eval("sub { $exp }",
346             $filename, $stmt_line);
347 49         224 push @acts, $sub;
348             }
349             elsif ($act2 =~ s/^\s*($brackets_re)//) {
350             # It's an anonymous array
351 1         12 my $array = _expand(_expand($1, $brackets,
352             '[', ']'),
353             $braces);
354 1         8 my ($array_p) =
355             $self->_eval("do { $array }",
356             $filename, $stmt_line);
357 1         3 push @acts, $array_p;
358             }
359             else {
360             # It's a command
361 16         87 $act2 = _expand($act2, $brackets, qw([ ]));
362 16         41 chomp ($act2 =
363             _substitute(_expand($act2, $braces)));
364 16         66 $act2 =~ s/^\s*\#.*//;
365             # Allow use of $* for $1
366 16         39 $act2 =~ s/\$\*/\$1/g;
367 16 100       52 push @acts, $act2 if $act2 ne '';
368 16         28 $act2 = ''
369             }
370 66         194 chomp $act2;
371 66 100       465 $self->_carp("Extraneous input: $act2",
372             $filename, $stmt_line)
373             if $act2 !~ /^\s*$/;
374            
375 66         422 $act = '';
376 66         149 $stmt_line = $lineno+$l+1;
377 66         962 $in_braces = 0;
378             }
379             }
380 78 100       208 if ($in_braces) {
381 2         16 1; # Coverage misses next statement without this
382 2         18 $self->_carp("Unmatched '{' or '['",
383             $filename, $stmt_line);
384             }
385 78 100       448 $l-- unless $l == $#lines;
386              
387             # Process the rule
388 78         259 $self->maker->add_rules([@tgts, ':', @deps, '=', @acts]);
389              
390             # Make note of first constant rule
391 78 100 100     34121 if (!$self->{first} && ! grep ref $_ eq 'Regexp', @tgts) {
392 27         113 my $rules = $self->maker->rules;
393 27         553 $self->{first} = $rules->[-1];
394             }
395             }
396             else {
397             # It'd better be a sequence of perl blocks
398 36 100       163 my $re = %$braces ? join('|', keys %$braces) : "\n";
399 36         681 my @blocks = split /($re)/, $stmt;
400 36         167 foreach my $block (@blocks) {
401 110 100       908 next if $block =~ /^\s*$/; # Ignore whitespace
402 40 100       153 if (defined $braces->{$block}) {
403             # It's a perl block
404 39         183 my $perl = _expand($block, $braces);
405 39 100       151 if ($perl eq '') { # It was a \ at end of line
406 2         3 $stmt_line++;
407 2         5 next;
408             }
409             # Remove the enclosing {}
410 37         404 $perl =~ s/\A \{ (.*) \} \z/$1/xs;
411 37         251 $self->_eval("\@_ = \$self; $perl", $filename,
412             $stmt_line);
413 32         145 $stmt_line += $perl =~ tr/\n//;
414             }
415             else {
416 1         26 $self->_carp("Illegal input: '$block'",
417             $filename, $stmt_line);
418             }
419             }
420             }
421             }
422              
423             # Set-up for next statement
424 325 100       813 $in_braces = 0 if $in_braces < 0;
425 325         645 $stmt = '';
426 325         938 $stmt_line = $lineno+$l+1;
427             }
428             }
429 56 100       203 $self->_croak("Unmatched \{", $filename, $stmt_line) if $in_braces;
430 55         363 return $self->{errors};
431             }
432              
433             # Internal routines
434              
435             # Calls carp with information as to where the problem occurred
436             # Arguments: message, [filename, [lineno]]
437             sub _carp : method {
438 7     7   35 my ($self, $msg, $filename, $lineno) = @_;
439              
440 7 50       36 my @where = ($filename) if defined $filename;
441 7 50       24 push @where, $lineno if $lineno;
442 7 50       53 my $where = @where ? join(', ', @where) . ": " : '';
443 7         17 push @{$self->{errors}}, Carp::shortmess("$where$msg");
  7         1768  
444             }
445              
446             # Calls croak with information as to where the problem occurred
447             # Arguments: message, [filename, [lineno]]
448             sub _croak {
449 8     8   96 my ($self, $msg, $filename, $lineno) = @_;
450              
451 8 100       61 my @where = ($filename) if defined $filename;
452 8 100       47 push @where, $lineno if $lineno;
453 8 100       84 my $where = @where ? join(', ', @where) . ": " : '';
454 8         2526 croak("$where$msg");
455             }
456              
457             # Collapses braces in a string to make evident the nesting
458             # Arguments: string, optional open char, optional close char
459             # Returns: collapsed string, ref. to braces hash to re-constitute it
460             sub _collapse {
461 246     246   650 my ($str, $open, $close) = @_;
462 246 100       684 ($open, $close) = qw({ }) unless defined $close;
463 246         462 my $ord = ord $open;
464 246         552 grep do { $_ = "\Q$_" }, ($open, $close);
  492         1384  
465 246         423 my (%braces, $braces);
466 246         4299 while ($str =~ s/$open([^$open$close]*)$close/ do {
  129         322  
467 129         759 my $s = sprintf "<%x,%d>", $ord, ++$braces;
468 129         776 $braces{$s} = $1;
469 129         984 $s }
470             /seg) { }
471             # Collapse \ at end of lines, too
472 246 100       880 $braces{'<0d>'} = '' if $str =~ s/\\\n/ <0d> /g;
473              
474 246         965 return ($str, \%braces);
475             }
476              
477             # Evaluates a string within the proper package
478             # Arguments: string, filename, line number
479             # Returns: result of eval
480             sub _eval : method {
481 134     134   434 my ($self, $perl, $filename, $stmt_line) = @_;
482              
483 134 50       461 my $ld = defined $filename ? qq(\#line $stmt_line "$filename"\n) : '';
484             my $strict = defined $self->{options}{strict} &&
485 134 100 66     622 $self->{options}{strict} == 0 ? 'no strict;' : '';
486             # Remove minimum indentation of perl block so that HEREIS strings
487             # can be used as part of dependencies or actions
488 134         331 $perl =~ s/^(\t+)/' ' x (8*length($1))/gem;
  19         90  
489 134         535 my @indents = $perl =~ m/^([ ]+)/gm;
490 134 100       437 my $min_indent = @indents ? $indents[0] : '';
491 134 100       302 grep do {$min_indent = $_ if length $_ < length $min_indent}, @indents;
  125         368  
492 134 100       668 $perl =~ s/^$min_indent//gm if $min_indent;
493 134         21772 my @val = eval "${ld}package Slay::Makefile::Eval; $strict $perl";
494 134         9157 chomp $@;
495 134 100       557 $self->_croak($@, $filename, $stmt_line) if $@;
496 129         659 return @val;
497             }
498              
499             # Expands a string where the things in braces have been collapsed
500             # Arguments: string, ref to braces hash, optional open/close chars
501             sub _expand {
502 139     139   450 my ($string, $braces, $open, $close) = @_;
503            
504 139         316 $string =~ s/<0d>//g;
505 139 100       372 return $string unless %$braces;
506 108 100       406 ($open, $close) = qw({ }) unless defined $close;
507 108         637 my $re = join '|', map "\Q$_", keys %$braces;
508 108         2273 while ($string =~ s/($re)/$open$braces->{$1}$close/g) { }
509 108         499 return $string;
510             }
511              
512             # Substitutes global variables in a string
513             # Arguments: string
514             # Returns: substituted string
515             sub _substitute {
516 67     67   182 my ($string) = @_;
517              
518             package Slay::Makefile::Eval;
519 3     3   65 no strict 'refs';
  3         6  
  3         732  
520 67 100       190 $string =~ s/(\$([a-z_]\w*))/defined ${$2} ? ${$2} : $1/gie;
  41         130  
  41         235  
  12         58  
521 67 100       155 $string =~ s/(\$\{([a-z_]\w*)\})/defined ${$2} ? ${$2} : $1/gie;
  2         5  
  2         19  
  1         5  
522              
523 67         230 return $string;
524             }
525              
526             =back
527              
528             =head1 LIMITATIONS
529              
530             The parsing of perl blocks is only semi-smart. In particular,
531             unbalanced braces within comments or strings can cause parsing to end
532             prematurely or not at all. For example,
533              
534             {
535             # This comment has an unbalanced }
536             }
537             {
538             "This string has an unbalanced {";
539             }
540              
541             will not parse correctly. The first block will stop parsing at the
542             end of the comment and the second will continue swallowing text after
543             the end of its closing brace. As long as the total number of {'s
544             exceeds the total number lf }'s, parsing continues. You can always
545             overcome this problem by putting comments in judicious places:
546              
547             {
548             # Compensate with {
549             # This comment has an unbalanced }
550             }
551             {
552             "This string has an unbalanced {"; # Compensate with }
553             }
554              
555             =head1 ACKNOWLEDGEMENTS
556              
557             I want to acknowledge Barrie Slaymaker, who wrote the original
558             Slay::Maker module for CPAN and has been very kind in his support for
559             developing this module.
560              
561             =head1 COPYRIGHT & LICENSE
562              
563             Copyright 2007 Mark Nodine, all rights reserved.
564              
565             This program is free software; you can redistribute it and/or modify it
566             under the same terms as Perl itself.
567              
568             =cut
569              
570             1;