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   242865 use warnings;
  3         47  
  3         108  
4 3     3   17 no warnings qw(void);
  3         6  
  3         84  
5 3     3   15 use strict;
  3         11  
  3         136  
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_51';
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         6  
  3         234  
120 3     3   1860 use Slay::Maker 0.04;
  3         188754  
  3         8796  
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 193832 my ($class, $options) = @_;
134              
135 36         238 my $self = bless {}, $class;
136 36 100       315 $options = {} unless $options;
137              
138 36         768 $self->{maker} = new Slay::Maker({options => $options});
139 36         19142 $self->{options} = $options;
140              
141 36         169 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 716 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 1897546 my $self = shift;
166            
167             $self->_croak('No targets specified and no default target provided')
168 58 100 100     513 if ! @_ && ! $self->{first};
169 57 100       800 $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 708 my ($self, $filename) = @_;
182              
183 62 50       2553 open IN, $filename or croak "Cannot open $filename";
184 62         2811 my $string = join '', ;
185 62         963 close IN;
186 62         575 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 369 my ($self, $string, $filename, $lineno) = @_;
199              
200 62         387 $self->{errors} = [];
201 62   50     577 $lineno ||= 1;
202              
203 62         108 my $in_braces = 0;
204 62         238 my $stmt = '';
205 62         144 my $stmt_line = $lineno;
206              
207 62         169 my $EOL = '(?:\n\r?|\r\n?)';
208 62         1283 my @lines = split /$EOL/o, $string;
209             parse_stmt:
210 62         282 for (my $l=0; $l < @lines; $l++) {
211 489         874 $_ = $lines[$l];
212             # TODO: The following does not check whether braces are in
213             # strings, comments, or are backslash-quoted...
214 489 100       1637 s/^\s*\#.*// if $in_braces == 0; # Delete comments
215 489         1093 my $net_braces = tr/\{// - tr/\}//;
216 489         735 $in_braces += $net_braces;
217             # Append this line to the previous statement
218 489         1138 $stmt .= "$_\n";
219 489 100 100     2086 if ($in_braces <= 0 && ! /\\$/) {
220             # We may have a statement to process
221 331 100       1866 if ($stmt =~ /^\s*$/) {
    100          
222             # Ignore null statement
223             }
224             elsif ($stmt =~ /^\s*(-?)\s*include\s+(?!:)(.*)/) {
225             # include directive
226 27         154 my ($opt, $incfile) = ($1, $2);
227 27         2423 $incfile = eval qq(package Slay::Makefile::Eval; "$incfile");
228 27 100       597 if (! -f $incfile) {
229             # Check if we can build it with rules we already have
230 4         19 eval { $self->{maker}->make($incfile) } ;
  4         69  
231             }
232 27 100       20122 if (-f $incfile) {
    100          
233 25         59 1; # Coverage misses next statement without this
234 25         130 $self->parse($incfile);
235             }
236             elsif (! $opt) {
237 1         13 1; # Coverage misses next statement without this
238 1         39 $self->_croak("Cannot build include file '$incfile'",
239             $filename, $stmt_line);
240             }
241             }
242             else {
243 114         367 my $braces;
244             # Need to collapse matching { } pairs
245 114         643 ($stmt, $braces) = _collapse($stmt);
246 114 100       565 my $re = %$braces ? join('|', keys %$braces) : "\n";
247 114 100       855 if ($stmt =~ /^(?!\s)(.*?)\s*:\s*(.*)/) {
248 78         359 my ($raw_tgts, $raw_deps) = ($1, $2);
249 78         153 my (@tgts, @deps, @acts);
250 78         133 my $rule_line = $stmt_line;
251             # It's a rule
252              
253             # Process the targets
254 78         302 my @raw_tgts = split /\s+/, $raw_tgts;
255 78         196 foreach my $target (@raw_tgts) {
256 82 100       670 if ($target =~ s/^($re)//) {
257             # A perl expression
258 6         52 my $perl = _expand($1, $braces);
259 6 100       35 if ($perl eq '') { # It was a \ at end of line
260 1         2 $rule_line++;
261 1         9 next;
262             }
263 5         33 my @targets = $self->_eval($perl,
264             $filename, $rule_line);
265 5         31 foreach (@targets) {
266 8         38 my $ref = ref $_;
267 8 100 100     72 if ($ref eq 'Regexp' || $ref eq '') {
268 7         12 1; # Coverage misses next stmt without this
269 7         21 push @tgts, $_;
270             }
271             else {
272 1         19 $self->_carp("Illegal return type for target: $ref",
273             $filename, $rule_line);
274             }
275             }
276 5 100       205 $self->_carp("Extraneous input: $target",
277             $filename, $rule_line)
278             if $target !~ /^\s*$/;
279 5         195 $rule_line += $perl =~ tr/\n//;
280             }
281             else {
282             # A string target
283 76 100       369 if ($target =~ /\%/) {
284 33         365 my @const = split /(\%)/, $target;
285 33         101 grep do { $_ = "\Q$_" }, @const;
  202         439  
286 33 100       345 my $qr = 'qr(^' .
287             join('', map($_ eq '\%' ? '(.*)' : $_,
288             @const)) . '$)';
289 33         152 ($target) = $self->_eval($qr, $filename,
290             $rule_line);
291             }
292 76         255 push @tgts, $target;
293             }
294             }
295              
296             # Process the dependencies
297 78         299 my @raw_deps = split /\s+/, $raw_deps;
298 78         250 grep s/\%/\$1/g, @raw_deps; # Handle % in dependencies;
299 78         183 foreach my $dep (@raw_deps) {
300 62 100       458 if ($dep =~ s/^($re)//) {
301             # A perl expression
302 11         52 my $perl = _expand($1, $braces);
303 11 100       42 if ($perl eq '') { # It was a \ at end of line
304 2         7 $rule_line++;
305 2         5 next;
306             }
307 9         50 my ($sub) = $self->_eval("sub { $perl }",
308             $filename, $rule_line);
309 9         22 push @deps, $sub;
310 9 100       80 $self->_carp("Extraneous input: $dep",
311             $filename, $rule_line)
312             if $dep !~ /^\s*$/;
313 9         194 $rule_line += $perl =~ tr/\n//;
314             }
315             else {
316             # A string dependency
317 51         154 push @deps, _substitute($dep);
318             }
319             }
320              
321             # Read the actions
322 78         161 my $act = '';
323 78         135 my $in_braces = 0; # Shadows outer $in_braces
324 78         146 $stmt_line = $lineno+$l+1;
325 78   100     715 while ($l < $#lines && ($lines[++$l] =~ /^\s/ ||
      100        
326             $lines[$l] =~ /^\z/ && $in_braces))
327             {
328 169         370 $_ = $lines[$l];
329 169         362 my $net_braces = tr/\{\[// - tr/\}\]//;
330 169         254 $in_braces += $net_braces;
331 169         580 s/^\t//;
332 169         383 $act .= "$_\n";
333 169 100 66     893 if ($in_braces <= 0 && ! /\\$/) {
334             # We have another action
335 66         187 my ($act1, $braces) = _collapse($act);
336 66 100       315 my $braces_re = %$braces ?
337             join('|', keys %$braces) : "\n";
338 66         164 my ($act2, $brackets) = _collapse($act1, qw([ ]));
339 66 100       318 my $brackets_re = %$brackets ?
340             join('|', keys %$brackets) : "\n";
341 66 100       1215 if ($act2 =~ s/^\s*($braces_re)//) {
    100          
342             # It's a perl block
343 49         174 my $exp = _expand($1, $braces);
344 49         336 my ($sub) =
345             $self->_eval("sub { $exp }",
346             $filename, $stmt_line);
347 49         130 push @acts, $sub;
348             }
349             elsif ($act2 =~ s/^\s*($brackets_re)//) {
350             # It's an anonymous array
351 1         13 my $array = _expand(_expand($1, $brackets,
352             '[', ']'),
353             $braces);
354 1         6 my ($array_p) =
355             $self->_eval("do { $array }",
356             $filename, $stmt_line);
357 1         4 push @acts, $array_p;
358             }
359             else {
360             # It's a command
361 16         60 $act2 = _expand($act2, $brackets, qw([ ]));
362 16         50 chomp ($act2 =
363             _substitute(_expand($act2, $braces)));
364 16         44 $act2 =~ s/^\s*\#.*//;
365             # Allow use of $* for $1
366 16         32 $act2 =~ s/\$\*/\$1/g;
367 16 100       63 push @acts, $act2 if $act2 ne '';
368 16         42 $act2 = ''
369             }
370 66         144 chomp $act2;
371 66 100       523 $self->_carp("Extraneous input: $act2",
372             $filename, $stmt_line)
373             if $act2 !~ /^\s*$/;
374            
375 66         439 $act = '';
376 66         133 $stmt_line = $lineno+$l+1;
377 66         526 $in_braces = 0;
378             }
379             }
380 78 100       208 if ($in_braces) {
381 2         16 1; # Coverage misses next statement without this
382 2         15 $self->_carp("Unmatched '{' or '['",
383             $filename, $stmt_line);
384             }
385 78 100       412 $l-- unless $l == $#lines;
386              
387             # Process the rule
388 78         270 $self->maker->add_rules([@tgts, ':', @deps, '=', @acts]);
389              
390             # Make note of first constant rule
391 78 100 100     33497 if (!$self->{first} && ! grep ref $_ eq 'Regexp', @tgts) {
392 27         101 my $rules = $self->maker->rules;
393 27         571 $self->{first} = $rules->[-1];
394             }
395             }
396             else {
397             # It'd better be a sequence of perl blocks
398 36 100       172 my $re = %$braces ? join('|', keys %$braces) : "\n";
399 36         692 my @blocks = split /($re)/, $stmt;
400 36         170 foreach my $block (@blocks) {
401 110 100       767 next if $block =~ /^\s*$/; # Ignore whitespace
402 40 100       121 if (defined $braces->{$block}) {
403             # It's a perl block
404 39         208 my $perl = _expand($block, $braces);
405 39 100       109 if ($perl eq '') { # It was a \ at end of line
406 2         3 $stmt_line++;
407 2         10 next;
408             }
409             # Remove the enclosing {}
410 37         338 $perl =~ s/\A \{ (.*) \} \z/$1/xs;
411 37         262 $self->_eval("\@_ = \$self; $perl", $filename,
412             $stmt_line);
413 32         138 $stmt_line += $perl =~ tr/\n//;
414             }
415             else {
416 1         18 $self->_carp("Illegal input: '$block'",
417             $filename, $stmt_line);
418             }
419             }
420             }
421             }
422              
423             # Set-up for next statement
424 325 100       784 $in_braces = 0 if $in_braces < 0;
425 325         561 $stmt = '';
426 325         895 $stmt_line = $lineno+$l+1;
427             }
428             }
429 56 100       214 $self->_croak("Unmatched \{", $filename, $stmt_line) if $in_braces;
430 55         392 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   43 my ($self, $msg, $filename, $lineno) = @_;
439              
440 7 50       47 my @where = ($filename) if defined $filename;
441 7 50       39 push @where, $lineno if $lineno;
442 7 50       41 my $where = @where ? join(', ', @where) . ": " : '';
443 7         17 push @{$self->{errors}}, Carp::shortmess("$where$msg");
  7         1739  
444             }
445              
446             # Calls croak with information as to where the problem occurred
447             # Arguments: message, [filename, [lineno]]
448             sub _croak {
449 8     8   67 my ($self, $msg, $filename, $lineno) = @_;
450              
451 8 100       57 my @where = ($filename) if defined $filename;
452 8 100       41 push @where, $lineno if $lineno;
453 8 100       52 my $where = @where ? join(', ', @where) . ": " : '';
454 8         2150 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   603 my ($str, $open, $close) = @_;
462 246 100       867 ($open, $close) = qw({ }) unless defined $close;
463 246         435 my $ord = ord $open;
464 246         469 grep do { $_ = "\Q$_" }, ($open, $close);
  492         1204  
465 246         405 my (%braces, $braces);
466 246         4208 while ($str =~ s/$open([^$open$close]*)$close/ do {
  129         290  
467 129         571 my $s = sprintf "<%x,%d>", $ord, ++$braces;
468 129         651 $braces{$s} = $1;
469 129         984 $s }
470             /seg) { }
471             # Collapse \ at end of lines, too
472 246 100       864 $braces{'<0d>'} = '' if $str =~ s/\\\n/ <0d> /g;
473              
474 246         1082 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   481 my ($self, $perl, $filename, $stmt_line) = @_;
482              
483 134 50       505 my $ld = defined $filename ? qq(\#line $stmt_line "$filename"\n) : '';
484             my $strict = defined $self->{options}{strict} &&
485 134 100 66     563 $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         362 $perl =~ s/^(\t+)/' ' x (8*length($1))/gem;
  19         86  
489 134         507 my @indents = $perl =~ m/^([ ]+)/gm;
490 134 100       385 my $min_indent = @indents ? $indents[0] : '';
491 134 100       310 grep do {$min_indent = $_ if length $_ < length $min_indent}, @indents;
  125         384  
492 134 100       646 $perl =~ s/^$min_indent//gm if $min_indent;
493 134         20916 my @val = eval "${ld}package Slay::Makefile::Eval; $strict $perl";
494 134         8011 chomp $@;
495 134 100       438 $self->_croak($@, $filename, $stmt_line) if $@;
496 129         614 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   499 my ($string, $braces, $open, $close) = @_;
503            
504 139         306 $string =~ s/<0d>//g;
505 139 100       349 return $string unless %$braces;
506 108 100       461 ($open, $close) = qw({ }) unless defined $close;
507 108         591 my $re = join '|', map "\Q$_", keys %$braces;
508 108         2056 while ($string =~ s/($re)/$open$braces->{$1}$close/g) { }
509 108         412 return $string;
510             }
511              
512             # Substitutes global variables in a string
513             # Arguments: string
514             # Returns: substituted string
515             sub _substitute {
516 67     67   170 my ($string) = @_;
517              
518             package Slay::Makefile::Eval;
519 3     3   41 no strict 'refs';
  3         8  
  3         608  
520 67 100       193 $string =~ s/(\$([a-z_]\w*))/defined ${$2} ? ${$2} : $1/gie;
  41         71  
  41         247  
  12         55  
521 67 100       150 $string =~ s/(\$\{([a-z_]\w*)\})/defined ${$2} ? ${$2} : $1/gie;
  2         6  
  2         17  
  1         7  
522              
523 67         193 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;