File Coverage

blib/lib/TinyMake.pm
Criterion Covered Total %
statement 6 103 5.8
branch 0 38 0.0
condition 0 6 0.0
subroutine 2 13 15.3
pod 5 9 55.5
total 13 169 7.6


line stmt bran cond sub pod time code
1             package TinyMake;
2             our $VERSION = '0.06';
3            
4             =head1 NAME
5            
6             TinyMake - A minimalist build language, similar in purpose to make and ant.
7            
8             =head1 SYNOPSIS
9            
10             use TinyMake ':all';
11            
12             # a file statement without a rule is like a symbolic target
13             file all => ["codeGen", "compile", "dataLoad", "test"];
14            
15             file codeGen => ["database.spec"], sub {
16             # generate code here
17             sh "touch $target";
18             } ;
19            
20             file compile => ["codeGen"], sub {
21             # compile code here
22             sh "touch $target";
23             } ;
24            
25             file dataLoad => ["codeGen"], sub {
26             # load data here
27             sh "touch $target"
28             } ;
29            
30             file test => ["compile", "dataLoad"], sub {
31             # test code here
32             sh "touch $target";
33             } ;
34             # a file statement without prerequisites will be executed
35             # if the target doesn't exist.
36             file clean => sub {
37             # perform cleanup here
38             sh "rm compile codeGen dataLoad test"
39             } ;
40            
41             make @ARGV
42            
43             =cut
44            
45 1     1   20109 use strict;
  1         2  
  1         34  
46 1     1   6 use File::Find ;
  1         3  
  1         1410  
47            
48             require Exporter;
49             our @ISA = ("Exporter");
50             our @EXPORT_OK = qw(file make show group $target @changed @sources sh filetree);
51             our %EXPORT_TAGS = (all => \@EXPORT_OK,);
52             #---------------------------------------------------------------------------
53             #
54             # Exported variables (for use in rules)
55             #
56             #===========================================================================
57             our @changed = ();
58             our @sources = ();
59             our $target = undef;
60            
61             #---------------------------------------------------------------------------
62             #
63             # Private variables
64             #
65             #===========================================================================
66            
67             # prerequisites grouped by target (target => [prerequisites])
68             #
69             my %prerequisites_for_target = ();
70            
71            
72             # (target => $coderef)
73             #
74             my %command_for_target = ();
75            
76            
77             # (target => "A descriptive comment for how target gets built")
78             #
79             my %comment_for_target = ();
80            
81            
82             # The default target is always the first target declared using either
83             # file or group
84             #
85             my $first = undef;
86            
87             #---------------------------------------------------------------------------
88             #
89             # walk a tree in post-order return a list of walked nodes
90             #
91             #===========================================================================
92             sub postorderwalk {
93            
94 0     0 0   my ($node,$children,$store,$visited) = @_;
95            
96 0 0         return if (grep { $_ eq $node} @$visited);
  0            
97            
98 0           push @$visited, $node;
99            
100 0           my @kids = $children->($node);
101            
102 0           foreach (@kids){
103            
104 0           postorderwalk($_,$children,$store,$visited) ;
105            
106             }
107            
108 0           push @$store, $node;
109 0           @$store;
110             }
111             #---------------------------------------------------------------------------
112             #
113             # traverse the dependency tree post-orderly and return the list of targets
114             #
115             #===========================================================================
116             sub depends {
117            
118 0     0 0   my ($t,$store,$visited) = @_;
119            
120             my $children = sub {
121            
122 0     0     my ($node) = @_;
123            
124 0 0         if (exists $prerequisites_for_target{$node}) {
125            
126 0           return @{$prerequisites_for_target{$node}} ;
  0            
127            
128             }
129 0           return ();
130            
131 0           };
132            
133 0           postorderwalk ($t, $children, $store,$visited);
134             }
135             #---------------------------------------------------------------------------
136             #
137             # Show Dependency tree: Prints out a nested list of target and dependencies
138             #
139             #===========================================================================
140             sub show (@_){
141            
142 0     0 0   my ($node,$lvl) = @_;
143            
144 0 0         if (!(defined $node)){
145 0           $node = $first;
146             }
147            
148 0 0         if (!(defined $lvl)){
149 0           $lvl = 0;
150             }
151            
152 0 0         if (exists $command_for_target{$node}){
153 0           print " " x $lvl . "*$node";
154            
155             }else{
156 0           print " " x $lvl . "$node";
157            
158             }
159            
160 0 0         if (exists $comment_for_target{$node}){
161 0           print " - $comment_for_target{$node}";
162             }
163            
164 0           print "\n";
165            
166 0           $lvl+=1; # increase indentation
167            
168 0 0         if (exists $prerequisites_for_target{$node}){
169            
170 0           my @children = @{$prerequisites_for_target{$node}};
  0            
171            
172 0           foreach (@children){
173 0           show ($_, $lvl);
174             }
175            
176             }
177 0           $lvl-=1;
178             }
179             #---------------------------------------------------------------------------
180             #
181             # Execute shell command: prints the shell command, the executes it
182             #
183             #===========================================================================
184             sub sh (@){
185            
186 0     0 1   print "@_\n";
187            
188 0           return qx(@_);
189            
190             }
191             #---------------------------------------------------------------------------
192             #
193             # Add a new explicit target :
194             #
195             #===========================================================================
196             sub file {
197            
198 0     0 1   my ($t,@params) = @_;
199 0 0         $first = $t unless defined $first;
200            
201 0 0         if (@params){
202            
203 0           foreach (0..$#params){
204            
205 0 0         if (ref $params[$_] eq "CODE"){
206 0           $command_for_target{$t} = $params[$_];
207 0           next;
208             }
209            
210 0 0         if (ref $params[$_] eq "ARRAY"){
211 0           $prerequisites_for_target{$t} = $params[$_];
212 0           next;
213             }
214            
215 0 0 0       if (ref $params[$_] eq "" and $params[$_] ne ""){
216 0           $comment_for_target{$t} = $params[$_];
217 0           next;
218             }
219            
220             }
221            
222             }else{
223            
224             # TinyMake cannot read minds yet.
225 0           $prerequisites_for_target{$t} = [];
226            
227             }
228            
229             }
230             #---------------------------------------------------------------------------
231             #
232             # Add a new group of explicit targets
233             #
234             #===========================================================================
235             sub group {
236            
237 0     0 1   my ($t,$href,$coderef,@rest) = @_;
238            
239 0           foreach (keys %$href) {
240            
241 0           file $_ => $href->{$_}, $coderef, @rest;
242            
243             }
244            
245 0           file $t => [keys %$href];
246            
247             }
248             #---------------------------------------------------------------------------
249             #
250             # Return the lastmodified time for each source file
251             #
252             #===========================================================================
253             {
254             my %cachedsourcetimes = ();
255            
256             sub sourcetimes {
257             # map {$_ => -M $_} @_;
258 0     0 0   my %result = ();
259            
260 0           foreach my $source (@_){
261            
262 0           my $value = undef;
263             #
264             # if the source is not itself a target then
265             # cache its modification time. THis assumes
266             # that such sources will not be modified as a side effect
267             # of any production rule during execution!!!!
268             #
269 0 0 0       if (exists $command_for_target{$source} or
270             exists $prerequisites_for_target{$source} ){
271            
272             # the source file is also mentioned as a target
273             #
274 0           my @filestats = stat $source;
275 0           $value = $filestats[9];
276            
277             }else{
278            
279             # the sourcefile is not mentioned as a target
280             # (it is pure)
281             #
282 0 0         if (!exists $cachedsourcetimes{$source}){
283            
284 0           my @filestats = stat $source;
285 0           $cachedsourcetimes{$source} = $filestats[9];
286            
287             }
288 0           $value = $cachedsourcetimes{$source};
289            
290             }
291 0           $result{$source} = $value;
292            
293             }
294 0           %result;
295            
296             }
297            
298             }
299             #---------------------------------------------------------------------------
300             #
301             # build the production script.
302             #
303             #===========================================================================
304             sub make {
305            
306 0     0 1   my @result = ();
307 0 0         @_ = ($first) unless (@_);
308 0           my $exec = 0;
309            
310 0           foreach (@_){
311            
312 0           my @files = depends $_,[],[];
313 0           my @files_i_can_build = grep {exists $command_for_target{$_} } @files;
  0            
314            
315 0           foreach (@files_i_can_build){
316            
317 0           $target = $_;
318 0           @changed = @sources = ();
319            
320 0 0         if (exists $prerequisites_for_target{$target}){
321            
322 0           @sources = @{$prerequisites_for_target{$target}} ;
  0            
323 0           @changed = @sources;
324            
325             }
326            
327 0           $exec = 1;
328            
329 0 0         if (-e $target){
330            
331 0           my @filestats = stat $target;
332 0           my $targettime = $filestats[9];
333 0           my %sourcetimes = sourcetimes @sources;
334 0           $exec = @changed = grep { $sourcetimes{$_} > $targettime } @sources;
  0            
335            
336             }
337            
338 0 0         if ($exec){
339            
340 0           $command_for_target{$target}->() ;
341 0           push @result, $target;
342            
343             }
344            
345             }
346 0 0         print "'$_' is up to date\n" unless ($exec);
347             }
348 0           @result;
349             }
350            
351             #---------------------------------------------------------------------------
352             #
353             # Get a recursive listing of files in a given directory
354             #
355             #===========================================================================
356             sub filetree {
357            
358 0     0 1   my @found = ();
359 0     0     File::Find::find sub{ push @found, $File::Find::name }, @_;
  0            
360 0           return @found;
361            
362             }
363            
364             1;
365            
366             __END__