File Coverage

blib/lib/ChainMake.pm
Criterion Covered Total %
statement 200 257 77.8
branch 87 132 65.9
condition 38 60 63.3
subroutine 20 23 86.9
pod 9 9 100.0
total 354 481 73.6


line stmt bran cond sub pod time code
1             package ChainMake;
2              
3 5     5   43501 use strict;
  5         22  
  5         320  
4 5     5   34 use warnings;
  5         14  
  5         438  
5 5     5   12429 use Data::Dumper;
  5         165914  
  5         486  
6 5     5   24598 use Clone qw(clone);
  5         53782  
  5         548  
7 5     5   62 use Fcntl (qw/:flock O_RDONLY O_CREAT/); # import LOCK_* constants
  5         33  
  5         1166  
8 5     5   31679 use Tie::File;
  5         395140  
  5         10135  
9              
10             our $VERSION = '0.9.0';
11              
12             our %DEFAULTS = (
13             verbose => 1,
14             silent => 0,
15             timestamps_file => '.chainstamps',
16             symbols => [qr/\$t_name/,qr/\$t_base/,qr/\$t_ext/],
17             );
18              
19             our %TARGETTYPE_PARAMS=(
20             requirements => sub { (ref shift eq 'ARRAY') },
21             insistent => sub {
22             my $p=shift;
23             return (($p == 0) || ($p == 1));
24             },
25             handler => sub { (ref shift eq 'CODE') },
26             timestamps => sub {
27             my $p=shift;
28             return ( (ref $p eq 'ARRAY') || ($p eq 'once') );
29             },
30             );
31              
32             sub new {
33 6     6 1 29 my $proto=shift;
34 6         20 my %args=@_;
35 6   33     77 my $self = bless {
36             targettypes => [],
37             }, ref($proto) || $proto;
38 6         37 $self->configure(%args);
39 6         29 return $self;
40             }
41              
42             sub configure {
43 12     12 1 56 my ($self,%args)=@_;
44             $self->{$_}=
45             defined($args{$_}) ? $args{$_} :
46             defined($self->{$_}) ? $self->{$_} :
47             $DEFAULTS{$_}
48 12 100       256 foreach (keys %DEFAULTS);
    100          
49 12         66 return 1;
50             }
51              
52             sub targets {
53             # add one target_type
54 30     30 1 136 my ($self,$targets,%target_t)=@_;
55 30 100       161 $targets=[$targets] unless (ref $targets eq 'ARRAY');
56              
57             # check if some parameters are given at all
58 30 100 66     52 unless ((@{$targets} > 0) && (keys %target_t)) {
  30         252  
59 1         21 $self->_diag(0,"at least one targetname and some parameters please\n");
60 1         6 return 0 ;
61             }
62             # only params from %TARGETTYPE_PARAMS allowed
63 29         96 for (keys %target_t) {
64 57 100       165 unless (defined $TARGETTYPE_PARAMS{$_}) {
65 1         7 $self->_diag(0,"unknown parameter $_\n");
66 1         7 return 0;
67             }
68             # perform pseudo type check
69 56 100       85 unless ( &{$TARGETTYPE_PARAMS{$_}}($target_t{$_}) ) {
  56         172  
70 4         15 $self->_diag(0,"illegal value in parameter $_\n");
71 4         24 return 0;
72             }
73             }
74             # extra necessities
75 24 100 100     176 unless (defined($target_t{requirements}) || defined($target_t{handler})) {
76 2         9 $self->_diag(0,"at least requirements or handler must be supplied\n");
77 2         12 return 0;
78             }
79 22 100 100     166 if (defined($target_t{timestamps}) && !defined($target_t{handler})) {
80 1         11 $self->_diag(0,"timestamps cannot be supplied without handler\n");
81 1         24 return 0;
82             }
83              
84 21         154 $target_t{targets}=$targets;
85 21         122 push (@{$self->{targettypes}},\%target_t);
  21         147  
86             }
87              
88             sub chainmake {
89             # returns 0 oder youngest
90             # 0 means failure
91 188     188 1 1118 my ($self,$t_name)=@_;
92 188 50       479 unless ($t_name) {
93 0         0 $self->_diag(0,"Usage: $0 Target\nType '$0 help' for more info\n\n");
94 0         0 return 0;
95             }
96              
97             # Target "instanziieren", d.h. Targetnamen ($t_name etc.) anwenden
98 188         583 my $target=clone($self->_match_target($t_name));
99 188 100       774 unless($target) {
100 11 50       28 if ($t_name eq 'help') {
101 0         0 print "Available targets\n-----------------\n".$self->available_targets();
102             }
103             else {
104 11         49 $self->_diag(0,"Don't know how to make $t_name. Maybe a typo?\n");
105             }
106 11         119 return 0;
107             }
108             # split target name into base and extension, handmade
109 177         328 my $t_base=$t_name;
110 177         258 my $t_ext='';
111 177 100       457 if ($t_name =~ /^(.+)\.([^\.]+)$/) {
112 4         14 $t_base = $1;
113 4         11 $t_ext = $2;
114             }
115             # apply symbols in timestamps and requirements
116 177 100       695 for (
    100          
117 101         342 (ref $target->{timestamps} eq 'ARRAY') ? @{$target->{timestamps}} : (),
  97         257  
118             (ref $target->{requirements} eq 'ARRAY') ? @{$target->{requirements}} : (),
119             ){
120 208         897 s/$self->{symbols}->[0]/$t_name/g;
121 208         634 s/$self->{symbols}->[1]/$t_base/g;
122 208         819 s/$self->{symbols}->[2]/$t_ext/g;
123             }
124              
125             # muss Handler ausführen / kann Handler wegen fehlender Req nicht ausführen
126 177         301 my ($must_make,$cannot_make);
127              
128             # Rausfinden, wie alt das älteste File von timestamps ist (=> $oldest)
129             # und ob vielleicht sogar eines fehlt (=> $must_make=1)
130             # Generelles Designproblem ist Auflösung des Timestamps=1s (Fat32: 2s)
131 0         0 my $oldest;
132 177 100 100     1569 if ((defined $target->{timestamps}) &&
  101 100 66     404  
      66        
133             (ref $target->{timestamps} eq 'ARRAY') &&
134             (@{$target->{timestamps}} > 0)
135             ) {
136 101         436 (my $yy,$oldest,my $missing)=$self->_check_file_timestamps($target->{timestamps});
137 101 100       280 if ($missing) {
138 25         42 $must_make=1;
139 25         55 undef $oldest;
140             }
141             }
142             elsif ((defined $target->{timestamps}) &&
143             ($target->{timestamps} eq 'once')) {
144 66         282 my $ts=$self->_get_timestamp($t_name);
145 66 100       189 if ($ts) {
146 28         53 $oldest=$ts;
147             }
148             else {
149 38         83 $must_make=1;
150             }
151             }
152             # timestamps gibts nicht / unverständlich
153             else {
154 10         17 $must_make=1;
155             }
156            
157             # Alle Requirements daraufhin prüfen (d.h. chainmake() darauf ausführen),
158             # ob eines der Requirements jünger als unser ältestes timestamps-File ($oldest) ist
159 177         244 my $youngest_req;
160 177 100       601 if (ref $target->{requirements} eq 'ARRAY') {
161 97         582 $youngest_req=$self->_check_requirements($target->{requirements},$target->{insistent},$target->{parallel});
162 97 50       298 if ($youngest_req) {
163             #print "$t_name - y: $youngest_req, o: $oldest ".(($youngest_req > $oldest) ? "younger (must make)\n": "older\n");
164 97 100 100     458 $must_make=1 if ($oldest && ($youngest_req > $oldest));
165             }
166             else {
167 0         0 $cannot_make=1;
168             }
169             }
170            
171             # From here on we potentially return from the method
172             # to avoid too deeply nested if if ifs.
173            
174             # Irgendwas nicht erfolgreich?
175 177 50       398 if ($cannot_make) {
176 0         0 $self->_diag(2,"Cannot make '$t_name' due to missing requirements\n");
177 0         0 return 0;
178             }
179            
180             # Muss nix machen
181 177 100       501 unless ($must_make) {
182 88         898 $self->_diag(2,"Nothing to do for target '$t_name'.\n");
183 88 100 66     424 if ((ref $target->{timestamps} eq 'ARRAY') && (@{$target->{timestamps}} > 0)) {
  64 50       241  
184 64         210 (my $youngest,my $ol,my $missing)=$self->_check_file_timestamps($target->{timestamps});
185 64 50       183 if ($missing) {
186 0         0 $self->_diag(0,"This should not happen. Timestamps file '$missing' is still missing\n");
187 0         0 return 0;
188             }
189 64         454 return $youngest;
190             }
191             # auto timestamps
192             elsif ($target->{timestamps} eq 'once') {
193 24         276 return $oldest; # hat sich nicht geändert
194             }
195             # timestamps gibts nicht / unverständlich
196             else {
197 0         0 return 1;
198             }
199             }
200            
201             # Kein Handler?
202 89 100       1219 unless (ref $target->{handler} eq 'CODE') {
203 2         10 $self->_diag(2,"Nothing else to do for target '$t_name'\n");
204 2         24 return 1;
205             }
206            
207             # Handler ausführen
208             # und dann rausfinden,
209             # wie jung das jüngste File von timestamps jetzt ist
210 87         411 $self->_diag(2,"\nMaking target $t_name\n");
211 87   100     567 my $success=&{$target->{handler}}($t_name, $t_base, $t_ext, $youngest_req || undef, $oldest || undef);
  87   100     360  
212              
213 87         5006614 my $youngest;
214 87 100       217 if ($success) {
215 83         169 my $make_time=time;
216 5     5   86 no warnings;
  5         12  
  5         824  
217 83 100 66     538 if ((ref $target->{timestamps} eq 'ARRAY') && (@{$target->{timestamps}} > 0)) {
  35 100       152  
218 35         112 ($youngest,my $ol,my $missing)=$self->_check_file_timestamps($target->{timestamps});
219 35 50       112 if ($missing) {
220 0         0 $self->_diag(0,"Timestamps file '$missing' is still missing. Looks like an error in your target handler\n");
221 0         0 $youngest=0;
222             }
223             }
224             elsif ($target->{timestamps} eq 'once') {
225 40         154 $self->_write_timestamp($t_name => $make_time);
226 40         3700 $youngest=$make_time;
227             }
228             else {
229             # timestamps gibts nicht / unverständlich
230 8         21 $youngest=$make_time;
231             }
232 5     5   30 use warnings;
  5         12  
  5         26853  
233             }
234             else {
235             # make nicht erfolgreich
236 4         421 $self->_diag(2,"Making $t_name was not successfull\n");
237             # evtl. vorhandene timestamps files löschen
238 4 100 66     19 if ((ref $target->{timestamps} eq 'ARRAY') && (@{$target->{timestamps}} > 0)) {
  2         10  
239 2         4 for my $timestamps (@{$target->{timestamps}}) {
  2         5  
240 2 50       23 if (-e $timestamps) {
241 2         9 $self->_diag(2,"Removing timestamps file $timestamps\n");
242 2 50       120 unlink $timestamps or $self->_diag(0,"Removing timestamps file $timestamps was not successfull\n");
243             }
244             }
245             }
246             }
247            
248 87   100     1161 return $youngest || 0;
249             }
250              
251             sub execute_system {
252 0     0 1 0 my ($self,%cmd)=@_;
253 0         0 my $cmd;
254 0 0       0 if ($^O =~ /MSWin32/) {
255 0   0     0 $cmd=$cmd{Windows} || $cmd{All};
256             }
257             else {
258 0   0     0 $cmd=$cmd{Linux} || $cmd{All};
259             }# there are no other OS in the world so far
260              
261 0         0 $self->_diag(1,"> $cmd\n");
262 0         0 system($cmd);
263 0 0       0 if ($? == -1) {
    0          
264 0         0 $self->_diag(0,"failed to execute: $!\n");
265             }
266             elsif ($? & 127) {
267 0 0       0 $self->_diag(0,sprintf "child died with signal %d, %s coredump\n",
268             ($? & 127), ($? & 128) ? 'with' : 'without');
269             }
270             else {
271 0         0 my $value=$? >> 8;
272 0         0 return ($value == 0);
273             }
274 0         0 return undef;
275             }
276              
277             sub execute_perl {
278 0     0 1 0 my ($self,$cmd)=@_;
279 0         0 print "> $cmd\n";
280 0         0 system("$^X $cmd");
281 0 0       0 if ($? == -1) {
    0          
282 0         0 $self->_diag(0,"failed to execute: $!\n");
283             }
284             elsif ($? & 127) {
285 0 0       0 $self->_diag(0,sprintf "child died with signal %d, %s coredump\n",
286             ($? & 127), ($? & 128) ? 'with' : 'without');
287             }
288             else {
289 0         0 my $value=$? >> 8;
290 0         0 return ($value == 0);
291             }
292 0         0 return undef;
293             }
294              
295             sub available_targets {
296 0     0 1 0 my $self=shift;
297 0         0 my $list;
298 0         0 for (@{$self->{targettypes}}) {
  0         0  
299 0         0 my @targets=@{$_->{targets}};
  0         0  
300 0         0 my $col=0;
301 0         0 while (@targets) {
302 0         0 $list.=sprintf "%-30.30s", shift @targets;
303 0 0       0 $list.="\n" if $col++==3;
304 0         0 $col%=3;
305             }
306 0         0 $list.="\n";
307             }
308 0         0 return $list;
309             }
310              
311             sub _check_requirements {
312             # Alle Requirements checken (d.h. make darauf ausführen),
313             # und Timestamp des jüngsten zurückgeben.
314             # serieller Modus
315 97     97   497 my ($self,$req,$insistent,$parallel)=@_;
316 97         197 my ($cannot_make,$cannot_make_name)=(0,'');
317 97         122 my $youngest;
318             REQUIREMENTS:
319 97         174 for my $dep (@$req) {
320 107         135 my $age;
321             # ist es der Name eines Targets?
322 107 50       240 if ($self->_match_target($dep)) {
    0          
323 107         350 $age=$self->chainmake($dep);
324 107 50       338 unless ($age) {
325 0         0 $self->_diag(1,"Requirement '$dep' failed.\n");
326 0         0 $cannot_make=1;
327 0         0 $cannot_make_name=$dep;
328 0 0       0 last REQUIREMENTS unless ($insistent);
329             }
330             }
331             # oder der Name einer Datei?
332             elsif (-e $dep) {
333 0         0 $age=(stat($dep))[9];
334             }
335             # Requirement nicht auffindbar
336             else {
337 0         0 $self->_diag(1,"Missing requirement '$dep'.\n");
338 0         0 $cannot_make=1;
339 0         0 $cannot_make_name=$dep;
340 0 0       0 last REQUIREMENTS unless ($insistent);
341             }
342              
343             # ist dieses Requirement jünger als das bisher Jüngste?
344 107 100 66     468 if (!($youngest) || (($age) && ($age > $youngest))) {
      66        
345 100         506 $youngest=$age;
346             }
347             }
348 97 50       352 return ($cannot_make ? 0 : $youngest);# $cannot_make_name kann er auch noch returnen
349             }
350              
351             sub _match_target {
352 295     295   515 my ($self,$t_name)=@_;
353 295         489 for my $t (@{$self->{targettypes}}) {
  295         741  
354 706         873 for my $name (@{$t->{targets}}) {
  706         1558  
355 866         1021 my $match;
356 866 100       1654 if (ref($name) eq 'Regexp') {
357 149         648 $match=$t_name =~ $name;
358             }
359             else {
360 717         1125 $match=$t_name eq $name;
361             }
362 866 100       21488 if ($match) {
363 284         4517 return $t;
364             }
365             }
366             }
367 11         74 return undef;
368             }
369            
370             sub _check_file_timestamps {
371 200     200   311 my ($self,$ver)=@_;
372 200         261 my ($oldest,$youngest,$missing);
373 200         238 for my $timestamps (@{$ver}) {
  200         493  
374 200 100       2931 if (-e $timestamps) {
375 175         2269 my $mtime = (stat($timestamps))[9];
376 175 50 33     735 $youngest=$mtime unless (($youngest) && ($youngest >= $mtime));
377 175 50 33     739 $oldest=$mtime unless (($oldest) && ($oldest <= $mtime));
378             }
379             else {
380 25         88 $missing=$timestamps;
381             }
382             }
383 200         820 return ($oldest,$youngest,$missing);
384             }
385              
386             sub _get_timestamp {
387 66     66   135 my ($self,$target)=@_;
388 66         82 my $ts;
389 66 50       1638 my $tie=tie(my @array, 'Tie::File', $self->{timestamps_file}, memory => 0, mode => O_RDONLY | O_CREAT ) or die "Kann Datei $$self{timestamps_file} nicht zum Lesen verbinden";
390 66         12696 $tie->flock(LOCK_SH);
391 66         5789 for (@array) {
392 189         13199 my ($t,$v)=split "\t";
393 189 100       19847 if ($t eq $target) {
394 28         43 $ts=$v;
395 28         77 last;
396             }
397             }
398 66         1701 undef $tie;
399 66         335 untie @array;
400            
401 66         4052 return $ts;
402             }
403              
404             sub _write_timestamp {
405 40     40   91 my ($self,$target,$val) = @_;
406              
407 40 50       332 my $tie=tie(my @array, 'Tie::File', $self->{timestamps_file}, memory => 0 ) or die "Kann Datei $$self{timestamps_file} nicht zum Lesen verbinden";
408 40         49864 $tie->flock(LOCK_EX);
409              
410 40         5494 for my $n (0 .. $#array) {
411 117         13328 my ($t,$v) = split "\t", $array[$n];
412 117 100       18662 next unless $t eq $target;
413 4         21 splice @array, $n, 1;
414 4         229724 last;
415             }
416 40         20236 push(@array,"$target\t$val");
417 40         23341 undef $tie;
418 40         211 untie @array;
419             }
420              
421             sub delete_timestamp {
422 15     15 1 50 my ($self,$target) = @_;
423 15         39 my $ret=0;
424              
425 15 50       218 my $tie=tie(my @array, 'Tie::File', $self->{timestamps_file}, memory => 0 ) or die "Kann Datei $$self{timestamps_file} nicht zum Lesen verbinden";
426 15         3402 $tie->flock(LOCK_EX);
427              
428 15         617 for my $n (0 .. $#array) {
429 16         1380 my ($t,$v) = split "\t", $array[$n];
430 16 100       1313 if ($t eq $target) {
431 11         107 splice(@array, $n, 1);
432 11         41898 $ret=1;
433 11         28 last;
434             }
435             }
436            
437 15         337 undef $tie;
438 15         88 untie @array;
439 15         747 return $ret;
440             }
441              
442             sub unlink_timestamps {
443 15     15 1 33 my $self=shift;
444 15         175112 unlink $self->{timestamps_file};
445 15         100 return 1;
446             }
447              
448             sub _diag {
449 203     203   1237 my ($self,$type,$msg)=@_;
450 203 100       40667 if ($type == 0) { # error
    50          
    50          
451 20 50       98 print $msg unless ($self->{silent})}
452             elsif ($type == 1) { # progress
453 0 0       0 print $msg unless ($self->{silent});
454             }
455             elsif ($type == 2) { # verbose
456 183 50 33     1615 print $msg if ($self->{verbose} && !($self->{silent}));
457             }
458             }
459              
460             1;
461              
462             __END__