File Coverage

blib/lib/App/AutoBuild.pm
Criterion Covered Total %
statement 21 357 5.8
branch 0 172 0.0
condition 0 51 0.0
subroutine 7 28 25.0
pod 4 20 20.0
total 32 628 5.1


line stmt bran cond sub pod time code
1             package App::AutoBuild;
2 1     1   27039 use strict;
  1         3  
  1         33  
3 1     1   5 use warnings;
  1         1  
  1         91  
4              
5             =head1 NAME
6              
7             App::AutoBuild - A perl tool to make it quick and easy to compile a C/C++ project with automatic compilation of dependencies
8              
9             =head1 VERSION
10              
11             Version 0.04
12              
13             =cut
14              
15             our $VERSION = '0.04';
16              
17             =head1 SYNOPSIS
18              
19             Example project layout:
20              
21             build.pl
22              
23             #!/usr/bin/perl
24             use App::AutoBuild qw(build shell_config);
25              
26             build({
27             'cflags'=>[qw(-O2 -Isrc/ -Wall -Wextra), shell_config('sdl-config', '--cflags')],
28             'ldflags'=>[qw(-lSDL_image -lm), shell_config('sdl-config', '--libs')],
29             'programs'=>{
30             # each output executable goes here. you can list several.
31             'main'=>'src/main.c',
32             },
33             });
34              
35             src/main.c
36              
37             #include "stuff.h"
38              
39             int main(int argc, char** argv)
40             {
41             stuff_function();
42             return 0;
43             }
44              
45             src/stuff.c
46              
47             #include "stuff.h"
48              
49             void stuff_function()
50             {
51             // hai
52             }
53              
54             src/stuff.h
55              
56             #pragma once
57              
58             void stuff_function();
59              
60             Note you don't need to put stuff.c into your build.pl-- it Just Works(tm).
61              
62             An even shorter example-- instead of a build.pl:
63              
64             build.sh
65              
66             #!/bin/sh
67              
68             export CC="clang"
69             export CFLAGS="-std=c99 -pedantic -Wall -Wextra -O3"
70              
71             perl -MApp::AutoBuild -e 'build("main.c");' -- $@
72              
73             =head1 COMMAND LINE
74              
75             usage: ./build.pl [-h|--help] [-v] [clean]
76              
77             -h, --help: dump help
78              
79             -v: increase verbosity (-vv or more -v's)
80             0 (default) only show compile/link actions, in shortened form
81             1 show compile/link actions with full command lines, and at the end a time summary (also shows App::AutoBuild overhead)
82             2 shows debugging for job numbers (not useful really yet)
83              
84             -d: increase debugosity (-dd or more -d's)
85             0 (default) nothing!
86             1 show which dependencies caused a recompile
87             2 show stat() calls
88              
89             -q: be more quiet
90              
91             --cc=(compiler path): pick a compiler other than gcc
92              
93             --program=(program): don't compile all targets, just this one
94              
95             clean: unlink output files/meta file (.autobuild_meta)
96              
97             =head1 DESCRIPTION
98              
99             After writing a makefile for my 30th C project, I decided this was dumb and it (the computer) should figure out which object files should be linked in or recompiled. The idea behind this module is you create a build.pl that uses App::AutoBuild and calls build() with cflags and ldflags, an output binary filename, and a C file to start with (usually the C file with main()).
100              
101             App::AutoBuild will figure out all the object files your C file depends on. A list of included header files (.h) will be computed by GCC and remembered in a cache. At build time, stat() is called on each header file included by your .c file. If any have a different mtime, the .c file will be recompiled. If you include a .h file that has a corresponding .c file, this process repeats and the output object code will be linked into your final binary automatically.
102              
103             This tool isn't supposed to be a make replacement-- there are plenty of those, and at least one great one already in Perl. The idea is that the build system should know enough about the source code to do what you want for you. This replaces all the functionality of a makefile for a standard C project with the added bonus of having it only link in the objects that are actually used in each output target.
104              
105             =head1 CAVEATS
106              
107             For this to work properly, you must have a scheme and follow it. Every .c/.cpp file must have an .h/.hpp file that matches (with the exception of the .c/.cpp file with main()). For now, the .c and .h files must be in the same directory (but this may be fixed in the future).
108              
109             If you have a .h file and an unrelated .c file with the same name (as in, headers.h and headers.c) in the same folder, the .c file will be compiled and linked in automatically. If this doesn't work well for you, put the .h files without .c files into a different folder (i.e. "include/") or something.
110              
111             A .autobuild_meta file is created in the current directory so it can remember modification times and dependency lists of files. This will definitely be configurable in a future version!
112              
113             =head1 SUBROUTINES/METHODS
114              
115             These are exported by default.
116              
117             =head2 build()
118              
119             Pass this function a hashref of parameters for how to build your project. Keys are as follows:
120              
121             =head3 cflags
122              
123             An arrayref of cflags
124              
125             =head3 ldflags
126              
127             An arrayref of ldflags
128              
129             =head3 programs
130              
131             A hashref with binaries as keys, and start-point C files as values.
132              
133             =head3 rules
134              
135             An arrayref of rule hashrefs. See L.
136              
137             =head2 shell_config()
138              
139             This is a helper function that takes a shell command + args (as an array) to pass to system() and splits the STDOUT into an array by whitespace.
140              
141             I do all this as arrays because the CFLAGS/LDFLAGS can be added or removed per-file with rules.
142              
143             =head1 RULES
144              
145             Rules let you have custom build options per-file. For now it only supports adding/removing cflags for a given .c file, or adding/removing ldflags for a given output binary.
146              
147             {'file'=>'contrib/GLee/GLee.c', 'del_cflags'=>['-pedantic']},
148             {'file'=>'configtest', 'add_ldflags'=>['-lyaml'], 'del_ldflags'=>['-lGL', '-lGLU', shell_config('sdl-config', '--libs')]},
149              
150             These definitely need some more work!
151              
152             =head1 AUTHOR
153              
154             Joel Jensen, C<< >>
155              
156             =head1 BUGS/TODO
157              
158             Please email me if it doesn't work! I've only tested with GCC and clang.
159              
160             Job paralellizing would be sweet. Patches welcome.
161              
162             =head1 LICENSE AND COPYRIGHT
163              
164             Copyright 2010 Joel Jensen.
165              
166             This program is free software; you can redistribute it and/or modify it
167             under the terms of either: the GNU General Public License as published
168             by the Free Software Foundation; or the Artistic License.
169              
170             See http://dev.perl.org/licenses/ for more information.
171              
172             =cut
173              
174 1     1   822 use Time::HiRes qw(time);
  1         1743  
  1         4  
175 1     1   1249 use Storable qw(store retrieve);
  1         3486  
  1         102  
176 1     1   10 use Digest::MD5();
  1         2  
  1         21  
177 1     1   1648 use autodie qw(system);
  1         20419  
  1         7  
178              
179 1     1   20380 use Exporter qw(import);
  1         2  
  1         4502  
180             our @EXPORT = qw(build shell_config);
181              
182             my $meta_file = '.autobuild_meta';
183              
184             my $next_jid = 0;
185             my $system_runtime = 0;
186              
187             sub build
188             {
189 0     0 1   my $opts = shift;
190              
191 0 0         if(ref($opts) eq '')
192             {
193 0           my $bin = $opts;
194 0           $bin =~ s/\.cp?p?$//;
195 0           $opts = {'programs'=>{$bin=>$opts}};
196             }
197              
198 0           my $start = time;
199              
200 0           my $ab = App::AutoBuild->new();
201 0           $ab->args(\@ARGV);
202              
203 0           my @cflags;
204             my @ldflags;
205              
206 0 0         push @cflags, split(/\s+/, $ENV{'CFLAGS'}) if($ENV{'CFLAGS'});
207 0 0         push @ldflags, split(/\s+/, $ENV{'LDFLAGS'}) if($ENV{'LDFLAGS'});
208              
209 0 0         push @cflags, @{$opts->{'cflags'}} if($opts->{'cflags'});
  0            
210 0 0         push @ldflags, @{$opts->{'ldflags'}} if($opts->{'ldflags'});
  0            
211              
212 0           $ab->cflags(\@cflags);
213 0           $ab->ldflags(\@ldflags);
214              
215 0 0         if($ENV{'CC'})
216             {
217 0           $ab->{'cc'} = $ENV{'CC'};
218             }
219              
220 0           for(qw(cc osuffix))
221             {
222 0 0         $ab->{$_} = $opts->{$_} if($opts->{$_});
223             }
224              
225 0   0       $ab->{'rules'} = $opts->{'rules'} || []; # HACK for now until I decide the right way to do this
226              
227 0           for my $bin (keys %{ $opts->{'programs'} })
  0            
228             {
229 0 0 0       if($ab->{'clean'} || !$ab->{'default'} || $ab->{'default'}{$bin})
      0        
230             {
231 0           $ab->program($opts->{'programs'}{$bin}, $bin);
232             }
233             }
234              
235 0           my $moar = 1;
236              
237 0           while($moar)
238             {
239             #$ab->debug_jobs();
240             #;
241 0           $moar = $ab->run();
242             };
243              
244 0           my $end = time;
245              
246 0 0         if($ab->{'verbose'} > 0)
247             {
248 0           printf(
249             "AutoBuild.pm runtime: %.3fs\n".
250             " system() runtime: %.3fs\n".
251             " overhead: %.3fs\n",
252             $end - $start,
253             $system_runtime,
254             ($end - $start) - $system_runtime);
255             }
256              
257 0           return;
258             }
259              
260             sub shell_config
261             {
262 0     0 1   my @cmdline = @_;
263              
264             # TODO use system() for this
265              
266 0           my $run = join(' ', @cmdline);
267 0           my $txt = `$run`;
268 0           chomp($txt);
269              
270 0           return split(/\s+/, $txt);
271             }
272              
273             sub new
274             {
275 0     0 0   my($class) = @_;
276 0   0       $class = ref($class) || $class;
277 0           my $self = bless({}, $class);
278              
279 0 0         if(-e $meta_file)
280             {
281 0           $self->{'meta'} = retrieve($meta_file);
282             }else
283             {
284 0           $self->{'meta'} = {};
285             }
286              
287             # some defaults
288 0           $self->{'verbose'} = 0;
289 0           $self->{'debug'} = 0;
290 0           $self->{'cc'} = 'gcc';
291 0           $self->{'cpp'} = 'g++';
292 0           $self->{'osuffix'} = 'o';
293              
294 0           $self->{'jobs'} = [];
295 0           $self->{'job_index'} = {};
296              
297 0           return($self);
298             }
299              
300             sub DESTROY
301             {
302 0     0     my($self) = @_;
303 0 0         if($self->{'clean'})
304             {
305 0           $self->unlink_file($meta_file);
306             }else
307             {
308 0           store($self->{'meta'}, $meta_file);
309             }
310 0           return;
311             }
312              
313             sub args
314             {
315 0     0 0   my($self, $argv) = @_;
316              
317 0           for(@$argv)
318             {
319 0 0 0       if($_ =~ m/^-(v+)$/)
    0          
    0          
    0          
    0          
    0          
    0          
320             {
321 0           $self->{'verbose'} += length($1);
322             }
323             elsif($_ =~ m/^-(d+)$/)
324             {
325 0           $self->{'debug'} += length($1);
326             }
327             elsif($_ eq '-q')
328             {
329 0           $self->{'quiet'} = 1;
330             }
331             elsif($_ eq 'clean')
332             {
333 0           $self->{'clean'} = 1;
334             }
335             elsif($_ =~ m/^--cc=(.+)$/)
336             {
337 0           $self->{'cc'} = $1;
338             }
339             elsif($_ =~ m/^--program=(.+)$/)
340             {
341 0           $self->{'default'}{$1} = 1;
342             }
343             elsif($_ eq '-h' || $_ eq '--help')
344             {
345 0           print <<"zap";
346             usage: $0 [-h|--help] [-v] [clean]
347              
348             -h, --help: dump help
349              
350             -v: increase verbosity (-vv or more -v's)
351             0 (default) only show compile/link actions, in shortened form
352             1 show compile/link actions with full command lines, and at the end a time summary (also shows App::AutoBuild overhead)
353             2 shows debugging for job numbers (not useful really yet)
354              
355             -d: increase debugosity (-dd or more -d's)
356             0 (default) nothing!
357             1 show which dependencies caused a recompile
358             2 show stat() calls
359              
360             clean: unlink output files/meta file ($meta_file)
361             zap
362 0           exit(1);
363             }
364             else
365             {
366 0           warn "ignoring unknown commandline option $_";
367             }
368             }
369             }
370              
371             sub cflags
372             {
373 0     0 1   my($self, $v) = @_;
374 0 0         if($v)
375             {
376 0           $self->{'cflags'} = $v;
377             }
378 0           return $self->{'cflags'};
379             }
380             sub ldflags
381             {
382 0     0 1   my($self, $v) = @_;
383 0 0         if($v)
384             {
385 0           $self->{'ldflags'} = $v;
386             }
387 0           return $self->{'ldflags'};
388             }
389              
390              
391             sub program
392             {
393 0     0 0   my($self, $cfile, $out) = @_;
394              
395 0           my %job = (
396             'out'=>$out,
397             'task'=>'ld',
398             );
399              
400 0           my $jid = $self->add_job(\%job);
401              
402 0           $job{'needs'} = [$self->add_build_job($cfile, $jid)];
403             }
404              
405             sub add_build_job
406             {
407 0     0 0   my($self, $cfile) = @_;
408              
409 0           my $cpp = 0;
410 0 0         if(substr($cfile, -4, 4) eq '.cpp')
411             {
412 0           $self->{'cpp_ld'} = 1;
413 0           $cpp = 1;
414             }
415             # otherwise assume C instead of C++
416              
417 0           my $ofile = replace_ext($cfile, $self->{'osuffix'});
418 0           $ofile =~ s/([^\/]+)$/\.$1/;
419              
420 0           my $job_key = $ofile; # add CFLAGS to this when that's customizable per binary
421 0 0         if(my $existing_jid = $self->{'job_index'}{$job_key})
422             {
423 0           return $existing_jid; # dont add a job
424             }
425              
426 0           my %job = (
427             'cfile'=>$cfile,
428             'out'=>$ofile,
429             'task'=>'cc',
430             'cpp'=>$cpp,
431             );
432              
433 0           my $jid = $self->add_job(\%job);
434 0           $self->{'job_index'}{$job_key} = $jid;
435 0           return $jid;
436             }
437              
438             sub add_job
439             {
440 0     0 0   my($self, $job) = @_;
441 0           $self->{'jobs'}[$next_jid] = $job;
442 0           return $next_jid++;
443             }
444              
445             sub needs_recurse
446             {
447 0     0 0   my($self, $jid) = @_;
448              
449 0           my @r = ($jid);
450 0           my %dups = ($jid=>1);
451 0           my %recursed;
452              
453 0           my $more = 1;
454              
455 0           while($more)
456             {
457 0           $more = 0;
458 0           for my $i (@r)
459             {
460 0 0         next if($recursed{$i});
461 0           $recursed{$i} = 1;
462 0           my $n = $self->{'jobs'}[$i]{'needs'};
463 0 0         next unless($n);
464 0           for my $id (@$n)
465             {
466 0 0         if(!$dups{$id})
467             {
468 0           push @r, $id;
469 0           $dups{$id} = 1;
470 0           $more = 1;
471             }
472             }
473             }
474             }
475              
476 0           shift @r; # take off our initial job id
477              
478 0           return \@r;
479             }
480              
481             sub debug_jobs
482             {
483 0     0 0   my($self) = @_;
484              
485 0           print "\n";
486 0           for(my $i = 0; $i < $next_jid; $i++)
487             {
488 0           my $job = $self->{'jobs'}[$i];
489 0           print " job $i: ".$job->{'task'}.' '.$job->{'out'};
490              
491 0 0 0       if($job->{'needs'} && scalar @{ $job->{'needs'} })
  0            
492             {
493 0           print " (needs ".join(', ', @{ $job->{'needs'} }).")";
  0            
494             }
495              
496 0 0         if($job->{'done'})
497             {
498 0           print " DONE";
499             }
500              
501 0           print "\n";
502             }
503             }
504              
505             sub run
506             {
507 0     0 0   my($self) = @_;
508              
509 0           my @can;
510              
511 0           for(my $i = 0; $i < $next_jid; $i++)
512             {
513 0           my $job = $self->{'jobs'}[$i];
514 0 0         if($job->{'done'})
515             {
516 0           next;
517             }
518              
519 0           my $met = 1;
520              
521 0           for my $ni (@{ $self->needs_recurse($i) })
  0            
522             {
523 0 0         if(!$self->{'jobs'}[$ni]{'done'})
524             {
525 0           $met = 0;
526             }
527             }
528              
529 0 0         if($met)
530             {
531 0           push @can, $i;
532             }
533             }
534              
535 0           my $r = 0;
536              
537 0           for my $i (@can)
538             {
539 0 0         if($self->{'verbose'} > 1)
540             {
541 0           print "executing job $i\n";
542             }
543              
544 0 0         if($self->exec_job($i))
545             {
546 0           $r = 1;
547             }
548             }
549              
550 0           return $r;
551             }
552              
553             sub exec_job
554             {
555 0     0 0   my($self, $jid) = @_;
556 0           my $job = $self->{'jobs'}[$jid];
557              
558 0 0         if($job->{'task'} eq 'cc')
559             {
560 0           my $ofile = $job->{'out'};
561 0           my $cfile = $job->{'cfile'};
562 0           my $depfile = $cfile.'.deps';
563              
564 0           my $headers = $self->{'meta'}{$cfile}{'headers'};
565              
566 0           my @cflags = @{ $self->{'cflags'} };
  0            
567 0           for my $rule (@{ $self->{'rules'} })
  0            
568             {
569 0           my $fm = $rule->{'filematch'};
570 0 0 0       if(($rule->{'file'} && ($rule->{'file'} eq $cfile || $rule->{'file'} eq $ofile)) ||
      0        
      0        
      0        
      0        
571             ($fm && ($cfile =~ m/$fm/ || $ofile =~ m/$fm/)))
572             {
573 0           my $add = $rule->{'add_cflags'};
574 0           my $del = $rule->{'del_cflags'};
575 0 0         if($add)
576             {
577 0           for my $f (@$add)
578             {
579 0 0         if(!grep { $_ eq $f } @cflags)
  0            
580             {
581 0           push @cflags, $f;
582             }
583             }
584             }
585 0 0         if($del)
586             {
587 0           @cflags = grep { my $f = $_; ! grep { $f eq $_ } @$del } @cflags;
  0            
  0            
  0            
588             }
589             }
590             }
591              
592 0 0         my $exec = $job->{'cpp'} ? $self->{'cpp'} : $self->{'cc'};
593              
594 0           my @gcc = grep { $_ } (
  0            
595             $exec,
596             @cflags,
597             '-MF', $depfile, '-MMD',
598             '-c', $cfile,
599             '-o', $ofile,
600             );
601 0           my $exec_str = join(' ', @gcc);
602              
603 0           my @check = ($ofile);
604 0           for(@$headers)
605             {
606 0 0         next if(substr($_, 0, 1) eq '/');
607 0           push @check, $_;
608             }
609              
610 0           my $changed = 1;
611              
612 0 0         if($self->{'meta'}{$ofile})
613             {
614 0 0 0       if($self->{'meta'}{$ofile}{'exec'} && $self->{'meta'}{$ofile}{'exec'} eq $exec_str)
    0          
615             {
616 0           $changed = 0;
617 0 0         if($self->{'debug'} > 2)
618             {
619 0           print "cc $ofile has the same GCC options\n";
620             }
621             }elsif($self->{'debug'})
622             {
623 0           print "cc $ofile has changed GCC options\n";
624             }
625             }
626              
627 0           for my $f (@check)
628             {
629 0 0         if($self->file_changed($f))
630             {
631 0           $changed = 1;
632 0 0         if($self->{'debug'})
633             {
634 0           print "cc $cfile has changed dep $f\n";
635             }
636             }else
637             {
638 0 0         if($self->{'debug'} > 2)
639             {
640 0           print "cc $cfile no changed dep $f\n";
641             }
642             }
643             }
644              
645 0 0         if($self->{'clean'})
    0          
646             {
647 0           $self->unlink_file($ofile);
648             }
649             elsif($changed)
650             {
651              
652 0 0         if($self->{'verbose'})
    0          
653             {
654 0           print $exec_str."\n";
655             }elsif(!$self->{'quiet'})
656             {
657 0 0         print $job->{'cpp'} ? 'cc++ ' : 'cc ';
658 0           print "$cfile..\n";
659             }
660              
661 0           my $start = time;
662 0           system(@gcc);
663 0           $system_runtime += (time - $start);
664              
665 0           $headers = slurp_depfile($depfile);
666 0           $self->{'meta'}{$cfile}{'headers'} = $headers;
667              
668             # remember the new md5sum/mtime
669 0 0         if($self->file_update($ofile))
670             {
671 0           $job->{'updated'} = 1;
672             }
673 0           $self->{'meta'}{$ofile}{'exec'} = $exec_str;
674              
675 0           for(@$headers)
676             {
677 0 0         next if(substr($_, 0, 1) eq '/');
678 0           $self->file_changed($_);
679             }
680              
681             }
682              
683 0           my @needs;
684              
685 0           for my $hfile (@$headers)
686             {
687 0 0         next if(substr($hfile, 0, 1) eq '/');
688             # look for a matching c file
689 0           my $c = replace_ext($hfile, 'c');
690             # look for a matching cpp file
691 0 0         $c = replace_ext($hfile, 'cpp') if(!-e $c);
692 0 0         next unless(-e $c);
693              
694 0           push @needs, $self->add_build_job($c);
695             }
696              
697 0           $job->{'needs'} = \@needs;
698 0           $job->{'done'} = 1;
699              
700 0           return 1;
701             }
702              
703 0 0         if($job->{'task'} eq 'ld')
704             {
705 0           my $out = $job->{'out'};
706              
707 0           my $any_changed = $self->file_changed($out);
708              
709 0           my $needs = $self->needs_recurse($jid);
710 0           my @ofiles;
711 0           for my $n (@{ $self->needs_recurse($jid) })
  0            
712             {
713 0           my $oj = $self->{'jobs'}[$n];
714 0           push @ofiles, $oj->{'out'};
715 0 0         if($oj->{'updated'})
716             {
717 0           $any_changed = 1;
718 0 0         if($self->{'debug'})
719             {
720 0           print "ld $out has changed dep ".$oj->{'out'}."\n";
721             }
722             }
723             }
724              
725 0           my @ldflags = @{ $self->{'ldflags'} };
  0            
726 0           for my $rule (@{ $self->{'rules'} })
  0            
727             {
728 0 0 0       if($rule->{'file'} && $rule->{'file'} eq $out)
729             {
730 0           my $add = $rule->{'add_ldflags'};
731 0           my $del = $rule->{'del_ldflags'};
732 0 0         if($add)
733             {
734 0           for my $f (@$add)
735             {
736 0 0         if(!grep { $_ eq $f } @ldflags)
  0            
737             {
738 0           push @ldflags, $f;
739             }
740             }
741             }
742 0 0         if($del)
743             {
744 0           @ldflags = grep { my $f = $_; ! grep { $f eq $_ } @$del } @ldflags;
  0            
  0            
  0            
745             }
746             }
747             }
748              
749 0 0         my $exec = $self->{'cpp_ld'} ? $self->{'cpp'} : $self->{'cc'};
750 0           my @gcc = grep { $_ } (
  0            
751             $exec,
752             @ldflags,
753             @ofiles,
754             '-o', $out,
755             );
756 0           my $exec_str = join(' ', @gcc);
757              
758 0 0 0       if(!$self->{'meta'}{$out} || !$self->{'meta'}{$out}{'exec'} || $self->{'meta'}{$out}{'exec'} ne $exec_str)
      0        
759             {
760 0           $any_changed = 1;
761 0 0         if($self->{'debug'})
762             {
763 0           print "cc $out has changed GCC options\n";
764             }
765             }else
766             {
767 0 0         if($self->{'debug'} > 2)
768             {
769 0           print "cc $out has the same GCC options\n";
770             }
771             }
772              
773 0 0         if($self->{'clean'})
    0          
774             {
775 0           $self->unlink_file($out);
776             }
777             elsif($any_changed)
778             {
779 0 0         if($self->{'verbose'})
    0          
780             {
781 0           print $exec_str."\n";
782             }elsif(!$self->{'quiet'})
783             {
784 0 0         print $self->{'cpp_ld'} ? "ld++ " : "ld ";
785 0           print "$out..\n";
786             }
787              
788 0           my $start = time;
789 0           system(@gcc);
790 0           $system_runtime += (time - $start);
791              
792 0           $self->file_update($out);
793 0           $self->{'meta'}{$out}{'exec'} = $exec_str;
794             }else
795             {
796 0 0         if($self->{'verbose'})
797             {
798 0           print "Already up to date: $out\n";
799             }
800             }
801              
802 0           $job->{'done'} = 1;
803 0           return 1;
804             }
805              
806 0   0       die "bad task type: ".($job->{'task'} // 'undef');
807             }
808              
809             sub slurp_depfile
810             {
811 0     0 0   my($depfile) = @_;
812              
813 0 0         open(my $dat, '<', $depfile) || die $!;
814 0           my $slurp = do { local $/ = undef; <$dat> };
  0            
  0            
815 0 0         close($dat) || die $!;
816              
817 0           unlink($depfile);
818              
819 0           $slurp =~ s/^.+:\s+//;
820 0           $slurp =~ s/\\\n/ /gsm;
821              
822 0           my @incs = grep { length } split(/\s+/, $slurp);
  0            
823              
824 0           return \@incs;
825             }
826              
827             sub replace_ext
828             {
829 0     0 0   my($in, $ext) = @_;
830 0           $in =~ s/\.[^.]{1,3}$//;
831 0           $in .= '.'.$ext;
832 0           return $in;
833             }
834              
835             sub file_changed
836             {
837 0     0 0   my($self, $file) = @_;
838              
839 0 0         if(defined $self->{'changed_this_run'}{$file})
840             {
841 0           return $self->{'changed_this_run'}{$file};
842             }
843              
844 0 0         if(-e $file)
845             {
846 0           my $mtime = $self->mtime($file);
847             #my $md5 = $self->md5($file);
848              
849 0 0 0       if($self->{'meta'}{$file} && $self->{'meta'}{$file}{'mtime'} && $self->{'meta'}{$file}{'mtime'} == $mtime)
      0        
850             {
851 0           $self->{'changed_this_run'}{$file} = 0;
852 0           return 0;
853             }
854              
855             #if($self->{'meta'}{$file} && $self->{'meta'}{$file}{'md5'} && $self->{'meta'}{$file}{'md5'} eq $md5)
856             #{
857             # $self->{'changed_this_run'}{$file} = 0;
858             # return 0;
859             #}
860              
861 0           $self->{'meta'}{$file}{'mtime'} = $mtime;
862             #$self->{'meta'}{$file}{'md5'} = $md5;
863             }
864              
865 0           $self->{'changed_this_run'}{$file} = 1;
866 0           return 1;
867             }
868             sub file_update
869             {
870 0     0 0   my($self, $file) = @_;
871              
872 0           delete $self->{'mtime_this_run'}{$file};
873 0           delete $self->{'md5_this_run'}{$file};
874 0           delete $self->{'changed_this_run'}{$file};
875 0           return $self->file_changed($file);
876             }
877              
878             sub mtime
879             {
880 0     0 0   my($self, $file) = @_;
881 0 0         if($self->{'mtime_this_run'}{$file})
882             {
883 0           return $self->{'mtime_this_run'}{$file};
884             }
885 0 0         if($self->{'debug'} > 1)
886             {
887 0           print "stat('$file');\n";
888             }
889 0           my @s = stat($file);
890 0           $self->{'mtime_this_run'}{$file} = $s[9];
891 0           return $s[9];
892             }
893              
894             sub md5
895             {
896 0     0 0   my($self, $file) = @_;
897 0 0         if($self->{'md5_this_run'}{$file})
898             {
899 0           return $self->{'md5_this_run'}{$file};
900             }
901 0 0         if($self->{'debug'} > 1)
902             {
903 0           print " md5('$file');\n";
904             }
905 0 0         open(my $dat, '<', $file) || die "cannot open file for md5: $!";
906 0           my $md5 = Digest::MD5->new();
907 0           $md5->addfile($dat);
908 0           close($dat);
909 0           my $sum = $md5->hexdigest();
910 0           $self->{'md5_this_run'}{$file} = $sum;
911 0           return $sum;
912             }
913              
914             sub unlink_file
915             {
916 0     0 0   my($self, $file) = @_;
917              
918 0 0         if(-e $file)
919             {
920 0 0         if($self->{'verbose'})
921             {
922 0           print "unlink('$file');\n";
923             }
924 0 0         unlink($file) || die "could not unlink $file: $!";
925             }
926             }
927              
928             1;