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   251265 use warnings;
  3         25  
  3         107  
4 3     3   30 no warnings qw(void);
  3         5  
  3         82  
5 3     3   16 use strict;
  3         5  
  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.14';
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   14 use Carp;
  3         6  
  3         236  
120 3     3   2008 use Slay::Maker 0.04;
  3         198015  
  3         9636  
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 214284 my ($class, $options) = @_;
134              
135 36         325 my $self = bless {}, $class;
136 36 100       208 $options = {} unless $options;
137              
138 36         879 $self->{maker} = new Slay::Maker({options => $options});
139 36         20205 $self->{options} = $options;
140              
141 36         157 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 694 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 2097727 my $self = shift;
166            
167             $self->_croak('No targets specified and no default target provided')
168 58 100 100     455 if ! @_ && ! $self->{first};
169 57 100       826 $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 721 my ($self, $filename) = @_;
182              
183 62 50       2622 open IN, $filename or croak "Cannot open $filename";
184 62         3015 my $string = join '', ;
185 62         948 close IN;
186 62         537 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 377 my ($self, $string, $filename, $lineno) = @_;
199              
200 62         337 $self->{errors} = [];
201 62   50     605 $lineno ||= 1;
202              
203 62         127 my $in_braces = 0;
204 62         240 my $stmt = '';
205 62         163 my $stmt_line = $lineno;
206              
207 62         259 my $EOL = '(?:\n\r?|\r\n?)';
208 62         1447 my @lines = split /$EOL/o, $string;
209             parse_stmt:
210 62         285 for (my $l=0; $l < @lines; $l++) {
211 489         945 $_ = $lines[$l];
212             # TODO: The following does not check whether braces are in
213             # strings, comments, or are backslash-quoted...
214 489 100       1547 s/^\s*\#.*// if $in_braces == 0; # Delete comments
215 489         1035 my $net_braces = tr/\{// - tr/\}//;
216 489         750 $in_braces += $net_braces;
217             # Append this line to the previous statement
218 489         1106 $stmt .= "$_\n";
219 489 100 100     1897 if ($in_braces <= 0 && ! /\\$/) {
220             # We may have a statement to process
221 331 100       1845 if ($stmt =~ /^\s*$/) {
    100          
222             # Ignore null statement
223             }
224             elsif ($stmt =~ /^\s*(-?)\s*include\s+(?!:)(.*)/) {
225             # include directive
226 27         318 my ($opt, $incfile) = ($1, $2);
227 27         2510 $incfile = eval qq(package Slay::Makefile::Eval; "$incfile");
228 27 100       693 if (! -f $incfile) {
229             # Check if we can build it with rules we already have
230 4         40 eval { $self->{maker}->make($incfile) } ;
  4         92  
231             }
232 27 100       22268 if (-f $incfile) {
    100          
233 25         61 1; # Coverage misses next statement without this
234 25         250 $self->parse($incfile);
235             }
236             elsif (! $opt) {
237 1         14 1; # Coverage misses next statement without this
238 1         32 $self->_croak("Cannot build include file '$incfile'",
239             $filename, $stmt_line);
240             }
241             }
242             else {
243 114         280 my $braces;
244             # Need to collapse matching { } pairs
245 114         386 ($stmt, $braces) = _collapse($stmt);
246 114 100       781 my $re = %$braces ? join('|', keys %$braces) : "\n";
247 114 100       839 if ($stmt =~ /^(?!\s)(.*?)\s*:\s*(.*)/) {
248 78         359 my ($raw_tgts, $raw_deps) = ($1, $2);
249 78         152 my (@tgts, @deps, @acts);
250 78         132 my $rule_line = $stmt_line;
251             # It's a rule
252              
253             # Process the targets
254 78         361 my @raw_tgts = split /\s+/, $raw_tgts;
255 78         217 foreach my $target (@raw_tgts) {
256 82 100       748 if ($target =~ s/^($re)//) {
257             # A perl expression
258 6         46 my $perl = _expand($1, $braces);
259 6 100       22 if ($perl eq '') { # It was a \ at end of line
260 1         2 $rule_line++;
261 1         9 next;
262             }
263 5         39 my @targets = $self->_eval($perl,
264             $filename, $rule_line);
265 5         34 foreach (@targets) {
266 8         28 my $ref = ref $_;
267 8 100 100     76 if ($ref eq 'Regexp' || $ref eq '') {
268 7         27 1; # Coverage misses next stmt without this
269 7         34 push @tgts, $_;
270             }
271             else {
272 1         18 $self->_carp("Illegal return type for target: $ref",
273             $filename, $rule_line);
274             }
275             }
276 5 100       255 $self->_carp("Extraneous input: $target",
277             $filename, $rule_line)
278             if $target !~ /^\s*$/;
279 5         208 $rule_line += $perl =~ tr/\n//;
280             }
281             else {
282             # A string target
283 76 100       286 if ($target =~ /\%/) {
284 33         367 my @const = split /(\%)/, $target;
285 33         105 grep do { $_ = "\Q$_" }, @const;
  202         447  
286 33 100       372 my $qr = 'qr(^' .
287             join('', map($_ eq '\%' ? '(.*)' : $_,
288             @const)) . '$)';
289 33         149 ($target) = $self->_eval($qr, $filename,
290             $rule_line);
291             }
292 76         263 push @tgts, $target;
293             }
294             }
295              
296             # Process the dependencies
297 78         515 my @raw_deps = split /\s+/, $raw_deps;
298 78         323 grep s/\%/\$1/g, @raw_deps; # Handle % in dependencies;
299 78         187 foreach my $dep (@raw_deps) {
300 62 100       514 if ($dep =~ s/^($re)//) {
301             # A perl expression
302 11         51 my $perl = _expand($1, $braces);
303 11 100       40 if ($perl eq '') { # It was a \ at end of line
304 2         8 $rule_line++;
305 2         13 next;
306             }
307 9         56 my ($sub) = $self->_eval("sub { $perl }",
308             $filename, $rule_line);
309 9         44 push @deps, $sub;
310 9 100       85 $self->_carp("Extraneous input: $dep",
311             $filename, $rule_line)
312             if $dep !~ /^\s*$/;
313 9         198 $rule_line += $perl =~ tr/\n//;
314             }
315             else {
316             # A string dependency
317 51         146 push @deps, _substitute($dep);
318             }
319             }
320              
321             # Read the actions
322 78         174 my $act = '';
323 78         154 my $in_braces = 0; # Shadows outer $in_braces
324 78         129 $stmt_line = $lineno+$l+1;
325 78   100     697 while ($l < $#lines && ($lines[++$l] =~ /^\s/ ||
      100        
326             $lines[$l] =~ /^\z/ && $in_braces))
327             {
328 169         455 $_ = $lines[$l];
329 169         345 my $net_braces = tr/\{\[// - tr/\}\]//;
330 169         243 $in_braces += $net_braces;
331 169         492 s/^\t//;
332 169         384 $act .= "$_\n";
333 169 100 66     814 if ($in_braces <= 0 && ! /\\$/) {
334             # We have another action
335 66         166 my ($act1, $braces) = _collapse($act);
336 66 100       322 my $braces_re = %$braces ?
337             join('|', keys %$braces) : "\n";
338 66         170 my ($act2, $brackets) = _collapse($act1, qw([ ]));
339 66 100       234 my $brackets_re = %$brackets ?
340             join('|', keys %$brackets) : "\n";
341 66 100       1282 if ($act2 =~ s/^\s*($braces_re)//) {
    100          
342             # It's a perl block
343 49         189 my $exp = _expand($1, $braces);
344 49         401 my ($sub) =
345             $self->_eval("sub { $exp }",
346             $filename, $stmt_line);
347 49         136 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         17 my ($array_p) =
355             $self->_eval("do { $array }",
356             $filename, $stmt_line);
357 1         7 push @acts, $array_p;
358             }
359             else {
360             # It's a command
361 16         81 $act2 = _expand($act2, $brackets, qw([ ]));
362 16         44 chomp ($act2 =
363             _substitute(_expand($act2, $braces)));
364 16         58 $act2 =~ s/^\s*\#.*//;
365             # Allow use of $* for $1
366 16         35 $act2 =~ s/\$\*/\$1/g;
367 16 100       57 push @acts, $act2 if $act2 ne '';
368 16         25 $act2 = ''
369             }
370 66         154 chomp $act2;
371 66 100       405 $self->_carp("Extraneous input: $act2",
372             $filename, $stmt_line)
373             if $act2 !~ /^\s*$/;
374            
375 66         485 $act = '';
376 66         152 $stmt_line = $lineno+$l+1;
377 66         513 $in_braces = 0;
378             }
379             }
380 78 100       206 if ($in_braces) {
381 2         10 1; # Coverage misses next statement without this
382 2         21 $self->_carp("Unmatched '{' or '['",
383             $filename, $stmt_line);
384             }
385 78 100       387 $l-- unless $l == $#lines;
386              
387             # Process the rule
388 78         236 $self->maker->add_rules([@tgts, ':', @deps, '=', @acts]);
389              
390             # Make note of first constant rule
391 78 100 100     33441 if (!$self->{first} && ! grep ref $_ eq 'Regexp', @tgts) {
392 27         104 my $rules = $self->maker->rules;
393 27         508 $self->{first} = $rules->[-1];
394             }
395             }
396             else {
397             # It'd better be a sequence of perl blocks
398 36 100       158 my $re = %$braces ? join('|', keys %$braces) : "\n";
399 36         709 my @blocks = split /($re)/, $stmt;
400 36         151 foreach my $block (@blocks) {
401 110 100       894 next if $block =~ /^\s*$/; # Ignore whitespace
402 40 100       155 if (defined $braces->{$block}) {
403             # It's a perl block
404 39         252 my $perl = _expand($block, $braces);
405 39 100       138 if ($perl eq '') { # It was a \ at end of line
406 2         13 $stmt_line++;
407 2         11 next;
408             }
409             # Remove the enclosing {}
410 37         380 $perl =~ s/\A \{ (.*) \} \z/$1/xs;
411 37         331 $self->_eval("\@_ = \$self; $perl", $filename,
412             $stmt_line);
413 32         177 $stmt_line += $perl =~ tr/\n//;
414             }
415             else {
416 1         22 $self->_carp("Illegal input: '$block'",
417             $filename, $stmt_line);
418             }
419             }
420             }
421             }
422              
423             # Set-up for next statement
424 325 100       841 $in_braces = 0 if $in_braces < 0;
425 325         669 $stmt = '';
426 325         927 $stmt_line = $lineno+$l+1;
427             }
428             }
429 56 100       190 $self->_croak("Unmatched \{", $filename, $stmt_line) if $in_braces;
430 55         347 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   42 my ($self, $msg, $filename, $lineno) = @_;
439              
440 7 50       39 my @where = ($filename) if defined $filename;
441 7 50       25 push @where, $lineno if $lineno;
442 7 50       37 my $where = @where ? join(', ', @where) . ": " : '';
443 7         15 push @{$self->{errors}}, Carp::shortmess("$where$msg");
  7         1765  
444             }
445              
446             # Calls croak with information as to where the problem occurred
447             # Arguments: message, [filename, [lineno]]
448             sub _croak {
449 8     8   92 my ($self, $msg, $filename, $lineno) = @_;
450              
451 8 100       63 my @where = ($filename) if defined $filename;
452 8 100       97 push @where, $lineno if $lineno;
453 8 100       71 my $where = @where ? join(', ', @where) . ": " : '';
454 8         2672 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   596 my ($str, $open, $close) = @_;
462 246 100       805 ($open, $close) = qw({ }) unless defined $close;
463 246         421 my $ord = ord $open;
464 246         518 grep do { $_ = "\Q$_" }, ($open, $close);
  492         1250  
465 246         435 my (%braces, $braces);
466 246         4266 while ($str =~ s/$open([^$open$close]*)$close/ do {
  129         297  
467 129         628 my $s = sprintf "<%x,%d>", $ord, ++$braces;
468 129         643 $braces{$s} = $1;
469 129         977 $s }
470             /seg) { }
471             # Collapse \ at end of lines, too
472 246 100       1354 $braces{'<0d>'} = '' if $str =~ s/\\\n/ <0d> /g;
473              
474 246         1045 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   504 my ($self, $perl, $filename, $stmt_line) = @_;
482              
483 134 50       515 my $ld = defined $filename ? qq(\#line $stmt_line "$filename"\n) : '';
484             my $strict = defined $self->{options}{strict} &&
485 134 100 66     580 $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         306 $perl =~ s/^(\t+)/' ' x (8*length($1))/gem;
  19         109  
489 134         513 my @indents = $perl =~ m/^([ ]+)/gm;
490 134 100       355 my $min_indent = @indents ? $indents[0] : '';
491 134 100       339 grep do {$min_indent = $_ if length $_ < length $min_indent}, @indents;
  125         463  
492 134 100       720 $perl =~ s/^$min_indent//gm if $min_indent;
493 134         21277 my @val = eval "${ld}package Slay::Makefile::Eval; $strict $perl";
494 134         8832 chomp $@;
495 134 100       502 $self->_croak($@, $filename, $stmt_line) if $@;
496 129         572 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   490 my ($string, $braces, $open, $close) = @_;
503            
504 139         298 $string =~ s/<0d>//g;
505 139 100       383 return $string unless %$braces;
506 108 100       439 ($open, $close) = qw({ }) unless defined $close;
507 108         608 my $re = join '|', map "\Q$_", keys %$braces;
508 108         2181 while ($string =~ s/($re)/$open$braces->{$1}$close/g) { }
509 108         442 return $string;
510             }
511              
512             # Substitutes global variables in a string
513             # Arguments: string
514             # Returns: substituted string
515             sub _substitute {
516 67     67   137 my ($string) = @_;
517              
518             package Slay::Makefile::Eval;
519 3     3   38 no strict 'refs';
  3         6  
  3         657  
520 67 100       213 $string =~ s/(\$([a-z_]\w*))/defined ${$2} ? ${$2} : $1/gie;
  41         68  
  41         256  
  12         69  
521 67 100       156 $string =~ s/(\$\{([a-z_]\w*)\})/defined ${$2} ? ${$2} : $1/gie;
  2         5  
  2         12  
  1         5  
522              
523 67         206 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;