File Coverage

blib/lib/PANT.pm
Criterion Covered Total %
statement 234 300 78.0
branch 58 102 56.8
condition 4 26 15.3
subroutine 36 45 80.0
pod 23 26 88.4
total 355 499 71.1


line stmt bran cond sub pod time code
1             # PANT - Perl version of the ANT/NANT building tools.
2             # Actually not much like them as it doesnt mess with XML currently.
3             # strike that - it now writes XML, but in an HTML kinda way, well XHTML actually.
4             package PANT;
5            
6 7     7   229348 use 5.008;
  7         27  
  7         287  
7 7     7   41 use strict;
  7         15  
  7         251  
8 7     7   38 use warnings;
  7         14  
  7         290  
9 7     7   44 use Carp;
  7         19  
  7         771  
10 7     7   41 use Cwd;
  7         23  
  7         2631  
11 7     7   8666 use File::Copy;
  7         50020  
  7         540  
12 7     7   9259 use File::Copy::Recursive;
  7         33836  
  7         455  
13 7     7   10787 use File::Compare ();
  7         9317  
  7         175  
14 7     7   51 use File::Basename;
  7         17  
  7         1030  
15 7     7   6323 use File::Spec::Functions qw(:ALL);
  7         6630  
  7         2055  
16 7     7   48 use File::Find;
  7         14  
  7         538  
17 7     7   38 use File::Path;
  7         14  
  7         411  
18 7     7   10114 use Getopt::Long;
  7         116874  
  7         54  
19 7     7   10250 use XML::Writer;
  7         154553  
  7         263  
20 7     7   7965 use IO::File;
  7         24693  
  7         1215  
21 7     7   57 use Exporter;
  7         15  
  7         228  
22 7     7   6853 use Digest;
  7         4055  
  7         362  
23 7     7   47 use Config;
  7         17  
  7         30416  
24            
25             our @ISA = qw(Exporter);
26            
27             # Items to export into callers namespace by default. Note: do not export
28             # names by default without a very good reason. Use EXPORT_OK instead.
29             # Do not simply export all your public functions/methods/constants.
30            
31             # This allows declaration use PANT ':all';
32             # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
33             # will save memory.
34             our %EXPORT_TAGS = ( 'all' => [ qw(
35             Phase Task NewerThan Command CopyFile CopyFiles DateStamp FileCompare
36             CopyTree BuildSolution
37             MoveFile MoveFiles MakeTree RmTree Cvs Svn FindPatternInFile
38             UpdateFileVersion StartPant EndPant CallPant RunTests Zip) ] );
39            
40             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
41            
42             our @EXPORT = ( @{ $EXPORT_TAGS{'all'} } );
43            
44            
45             our $VERSION = '0.17';
46            
47             my $dryrun = 0;
48             my ($logvolume, $logdirectory, $logfilename, $logstem, $logsuffix);
49             my $logcount= 1;
50             my $writer;
51            
52             my $this_perl = $^X;
53             if ($^O ne 'VMS') {
54             $this_perl .= $Config{_exe}
55             unless $this_perl =~ m/$Config{_exe}$/i;
56             }
57            
58             =head1 NAME
59            
60             PANT - Perl extension for ANT/NANT like build environments
61            
62             =head1 SYNOPSIS
63            
64             perl buildall.pl -output buildlog
65            
66             use PANT;
67             StartPant();
68             Phase(1, "Update");
69             Task(Command("cvs update"), "Fetch the latest code");
70             Phase(2, "Build");
71             Task(UpdateFileVersion("h/version.h",
72             qr/(#define\s*VERSION\s+)(\d+)/=>q{"$1" . ($2+1)},
73             "Version file updated");
74             Task(Command("make all"), "Built distribution");
75             Phase(3, "Deploy");
76             Task(Command("make distribution"), "Distribution built");
77             if (NewerThan(sources=>["myexe"], targets=>["/usr/bin/myexe"])) {
78             CopyFiles("myexe", "/usr/bin");
79             }
80             EndPant();
81            
82             =head1 ABSTRACT
83            
84             This is a module to help construct automated build environments.
85             The inspiration came from the ANT/NANT build environments which use
86             XML to describe a make like syntax of dependencies. For various
87             reasons none of these were suitable for my purposes, and I suspect
88             that eventually you will end up writing something pretty similar to
89             perl in XML to cater for all the things you want to do. Also a
90             module named PANT was just too good a name to miss!
91            
92             This module draws on some of the ideas in ANT/NANT, and also in the
93             Test::Mode module for ways to do things. This module is therefore a
94             collection of tools to help automate processes, and provide a build
95             log of what happened, so remote builds can be observed.
96            
97             The basic philosophy is that you can probably use make or visual studio
98             or similar to do the heavy building. There is no real need to replicate
99             that. However stuff like checking out of CVS/SVN repositories, updating
100             version numbers, checking it back in, running test harnesses, and similar
101             are things that make is not good at.
102            
103             XML is not a programming language, but you can describe a lot of
104             what you want using it, which is what ANT/NANT basically do. However
105             there is always something you want to do, which can't be described
106             in the current description language. In these cases you can call out
107             to an external routine to do things.
108            
109             However it seems much easier to provide a number of useful
110             subroutines in a scripting language, which help you build
111             things. Then if you need to do something slightly of piste, you have
112             all the power right there.
113            
114             The other thing I want to know about is "did it work" and if it
115             didn't, what went wrong? To this end plenty of logging is required so
116             the build can be tracked. As the build is probably going to be remote,
117             HTML seems the obvious choice to report in, so you can just look at it
118             from a web server.
119            
120             =head1 DESCRIPTION
121            
122             This module provides various useful functions to help in the automated
123             build of a project and to produce a build log. It is still in
124             development, and may well change shape in the light of experience.
125            
126             =head1 EXPORTS
127            
128             =head2 StartPant([title],[style=>stuff])
129            
130             This call should be the first call into the module. It does some
131             intialisation, and parses command line arguments in @ARGV. It takes
132             the following arguments.
133            
134             =over 4
135            
136             =item String
137            
138             The first argument is a string, and is used as the title of the web page if present.
139             If not present it will be called "Build Log".
140            
141             =item style=>stuff
142            
143             This argument if present signals some style data to include. This will
144             be included in a EstyleE tag. This allows you to apply different styles to
145             the generated page.
146            
147             =item stylelink=>href
148            
149             This argument if present directs the inclusion of a style sheet external link.
150            
151             =back
152            
153            
154             Supported command line options are
155            
156             =over 4
157            
158             =item -output file
159            
160             Write the output to the given file.
161            
162             =item -dryrun
163            
164             Simulate a run without actually doing anything.
165            
166             =back
167            
168             =cut
169            
170             sub StartPant {
171 11   100 11 1 13707 my $title = shift || "Build log";
172 11         65 my(%extra) = @_;
173 11         41 my $logname = "";
174 11         111 GetOptions("output=s"=>\$logname,
175             n=>\$dryrun,
176             dryrun=>\$dryrun);
177 11         6299 my $fh;
178 11 50       47 if ($logname) {
179 11 50       150 $fh = new IO::File "$logname", "w" or die "Can't open file $logname: $!";
180            
181             }
182             else {
183 0         0 $logname = "buildlog.html";
184 0 0       0 open $fh, ">&STDOUT" or die "Can't duplicate stdout: $!";
185             }
186 11 100       3408 if (file_name_is_absolute($logname)) {
187 1         18 ($logvolume,$logdirectory,$logfilename) = splitpath( $logname );
188             }
189             else {
190 10         263 ($logvolume,$logdirectory,$logfilename) = splitpath(catfile(getcwd, $logname));
191             }
192 11         276 $logstem = $logfilename;
193 11         119 $logstem =~ s/(\.[^.]+)$//;
194 11         29 $logsuffix = $1;
195 11         240 $writer = XML::Writer->new(NEWLINES=>1, OUTPUT=>$fh);
196 11         8661 $writer->xmlDecl();
197 11         1210 $writer->doctype('html', "-//W3C//DTD XHTML 1.0 Transitional//EN", "http://www.w3.org/TR/xhtml1/DTD/transitional.dtd");
198 11         440 $writer->startTag('html', xmlns=>"http://www/w3/org/TR/xhtml1");
199 11         1043 $writer->startTag('head');
200 11         792 $writer->dataElement('title', $title);
201 11 50       1122 if ($extra{stylelink}) {
202 0         0 $writer->emptyTag('link', href=>$extra{stylelink}, type=>"text/css");
203             }
204 11 50       44 if ($extra{style}) {
205 0         0 $writer->dataElement('style', $extra{style}, type=>"text/css");
206             }
207 11         46 $writer->endTag('head');
208 11         316 $writer->startTag('body');
209             }
210            
211             =head2 EndPant()
212            
213             This function finishes up the run, and should be the last call into
214             the module. It completes the build log in a tidy way.
215            
216             =cut
217            
218             sub EndPant {
219 10 100   10 1 38003 $writer->endTag('ul') if $writer->in_element('ul');
220 10         424 $writer->endTag('body');
221 10         279 $writer->endTag('html');
222 10         279 $writer->end();
223 10         312 undef $writer; # close files and flush
224             }
225            
226            
227             =head2 CallPant(name, options)
228            
229             This function allows you to call a subsidiary pant build. The build
230             will be run and waited for. A reference in the current log will be
231             made to the new log. It is assumed that the subsidiary build is also
232             using PANT as it passes some command line arguments to sort out the
233             logging.
234            
235             Options include
236            
237             =over 4
238            
239             =item directory=>place
240            
241             Change to the given directory to run the subsidiary build. The log
242             path should be modified so it fits.
243            
244             =item logname=>name
245            
246             Name the log file that it will write to this. If this is not given, a
247             name will be made up for you.
248            
249             =back
250            
251             =cut
252            
253             # call a subsidiary build
254             sub CallPant {
255 4     4 1 350 my $build = shift;
256 4         21 my (%args) = @_;
257 4         15 $writer->startTag('li');
258            
259 4         337 $writer->characters("Calling subsidiary build $build.");
260 4 100       95 my $dir = exists $args{directory} ? $args{directory} : ".";
261 4   66     22 my $logthisname = $args{logname} || "$logstem-$logcount$logsuffix";
262 4 100       24 $logthisname .= $logsuffix if ($logthisname !~ /\.[^.]+/);
263 4         118 my $logfile = catpath($logvolume, $logdirectory, $logthisname);
264 4         58 my $relfile = abs2rel($logfile, $dir);
265 4         646 my $rv = Command("$this_perl $build -output $relfile",
266             log=>$logfile, @_);
267 4         11 $logcount ++;
268 4         18 $writer->endTag('li');
269 4         343 return $rv;
270             }
271            
272             =head2 Phase([list])
273            
274             This function is purely for help in dividing up the build log. It
275             inserts a heading into the log allowing you to divide the build up
276             into a variety of parts. You might have a pre-build cvs checkput
277             phase, a build phase, and followed up by a test and deployment phase.
278            
279             The list is used as the contents of the header, and the first element
280             of the list is used as an HTML anchor in case you want to refer to it.
281            
282             =cut
283            
284             # a phase marker, for dividing up output a bit
285             sub Phase {
286 0 0   0 1 0 $writer->endTag('ul') if $writer->in_element('ul');
287 0         0 $writer->startTag('a', name=>$_[0]);
288 0         0 $writer->startTag('h1');
289 0         0 $writer->characters("@_");
290 0         0 $writer->endTag('h1');
291 0         0 $writer->endTag('a');
292 0         0 $writer->startTag('ul');
293             }
294            
295             =head2 DateStamp
296            
297             This function returns a datestamp in a common format. Its is intended
298             for use in logging output, and also in CVS/SVN type retrievals.
299            
300             =cut
301            
302             ## cvs like date/time
303             sub DateStamp {
304 0     0 1 0 my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime();
305 0         0 $year += 1900;
306 0         0 $mon++;
307            
308 0         0 return "$year-$mon-$mday $hour:$min:$sec";
309             }
310            
311            
312             =head2 NewerThan(sources=>[qw(f1 f2*.txt)], targets=>[build], ...)
313            
314             This function provides a make like dependency checker.
315             It has the following arguments,
316            
317             =over 4
318            
319             =item sources
320            
321             A list of wildcard (glob'able) files that are the source.
322            
323             =item treesources
324            
325             A list of wildcard directories that are descended for source files.
326             Currently all files in the tree are considered possibilities.
327            
328             =item targets
329            
330             A list of wildcard (glob'able) files that are the target
331            
332             =back
333            
334             The function will return true if any of the sources are
335             newer than the oldest of the targets.
336            
337             =cut
338            
339             # compares sources and targets
340             # Pick oldest of the targets
341             # newest of the sources.
342             sub NewerThan {
343 2     2 1 14 my (%args) = @_;
344 2 100       14 $writer->startTag('ul') if ! $writer->in_element('ul');
345 2         138 $writer->startTag('li');
346 2         80 my $srcs = "";
347 2 50       7 $srcs .= " the files @{ $args{sources} }" if exists $args{sources};
  2         7  
348 2 50       6 $srcs .= " the directories @{ $args{treesources} }" if exists $args{treesources};
  0         0  
349 2         6 $writer->characters("Are any of $srcs newer than @{ $args{targets} }? ");
  2         10  
350 2         39 my $newestt = time;
351 2         6 my $tfile = "none";
352 2         3 foreach my $glob (@{ $args{targets} }) {
  2         5  
353 2         77 foreach my $sfile (glob $glob) {
354            
355 2         35 my $t = (stat($sfile))[9];
356 2 50       9 if ($t) {
357 2 100       6 $newestt = $t if ($t < $newestt);
358 2         10 $tfile = $sfile;
359             }
360             else {
361 0         0 $writer->dataElement('li', "Warning: $sfile doesn't exist\n");
362 0         0 $newestt = 0;
363             }
364             }
365             }
366 2         73 my $newests = 1;
367 2         7 my $srcfile = "none";
368 2 50       6 if ($newestt > 0) {
369 2         3 GLOB: foreach my $glob (@{ $args{sources} }) {
  2         6  
370 2         29 foreach my $sfile (glob $glob) {
371 2         27 my $t = (stat($sfile))[9];
372 2 50       7 if ($t) {
373 2 50       9 if ($t > $newests) {
374 2         6 $srcfile = $sfile;
375 2         3 $newests = $t
376             }
377 2 100       12 last GLOB if ($newests > $newestt);
378             }
379             else {
380 0         0 carp "$sfile doesn't exist\n";
381 0 0       0 Abort("$sfile doesn't exist\n") if ($args{dieonerror});
382 0         0 $newests = 0;
383             }
384             }
385             }
386             my $wanted = sub {
387 0     0   0 my $t = (stat($_))[9];
388 0 0       0 if ($t > $newestt) {
389 0         0 $srcfile = $_;
390 0         0 $newests = $t;
391             }
392 2         16 };
393 2         3 foreach my $glob (@{ $args{treesources} }) {
  2         12  
394 0         0 foreach my $sfile (glob $glob) {
395 0         0 find($wanted, $sfile);
396             #print "Check tree $sfile\n";
397             }
398             }
399             }
400 2         4 my $rval = $newests > $newestt;
401 2 100       12 $writer->characters($rval ? "Yes" : "No");
402 2         35 $writer->endTag('li');
403             # print "Source $srcfile ", scalar(localtime($newests)), " Dest $tfile ", scalar(localtime($newestt)), " $rval\n";
404 2         43 return $rval;
405             }
406            
407             =head2 Task(result, message)
408            
409             This command evaluates the first argument to see if it is true, and
410             prints the second argument into the log. If the first argument is
411             false, the build will abort.
412            
413             =cut
414            
415             # checks a task succeeded
416             sub Task {
417 2     2 1 46 my $test = shift;
418 2         11 $writer->dataElement('li', "@_\n");
419 2 50       159 Abort("FAILED: ", @_) if ! $test;
420 2         13 return 1;
421             }
422            
423             =head2 Abort(list)
424            
425             This function aborts the build and is called internally when thigns go
426             wrong.
427            
428             =cut
429            
430             # give up and go home
431             sub Abort {
432 0     0 1 0 $writer->dataElement('span', Carp::longmess("@_"),
433             style=>"color:red;font-weight:bold");
434 0         0 EndPant();
435 0         0 confess @_;
436             }
437             =head2 Command(cmd, options)
438            
439             This function runs the given external command, capturing the output
440             for the log, and evaluating the return code to see if it worked.
441            
442             =over 4
443            
444             Currently there is only one option
445            
446             =item directory=>"somewhere"
447            
448             This will cause the command to run in the given directory, rather than
449             being where you happen to be currently.
450            
451             =back
452            
453             =cut
454            
455             # run a command, in a directory maybe
456             sub Command {
457 5     5 0 10 my $cmd = shift;
458 5         19 my (%args) = @_;
459 5         10 my $cdir = ".";
460 5 100       24 if ($args{directory}) {
461 3         19 $cdir = getcwd;
462 3 50       73 chdir($args{directory}) || Abort("Can't change to directory $args{directory}");
463            
464             }
465 5         21 $writer->startTag('li');
466 5         225 $writer->characters("Run $cmd\n");
467 5         170 my $output;
468             my $retval;
469 5 50       16 if ($dryrun) {
470 0         0 $output = "Output of the command $cmd would be here";
471 0         0 $retval = 1;
472             }
473             else {
474 5         20 $writer->startTag('pre');
475 5         185 $cmd .= " 2>&1"; # collect stderr too
476 5 50       45236 if (open(PIPE, "$cmd |")) {
477 5         846392 while(my $line = ) {
478 1         57 $writer->characters($line);
479             }
480 5         571 close(PIPE);
481 5         86 $retval = $? == 0;
482             }
483             else {
484 0         0 $retval = 0;
485             }
486 5         528 $writer->endTag('pre');
487             }
488 5 50       413 $writer->characters("$cmd failed: $!") if ($retval == 0);
489 5 100       32 if ($args{log}) {
490 4         75 my($v,$d, $f) = splitpath($args{log});
491 4         521 my $fulllog = rel2abs($args{log});
492 4         201 my $destlog = catpath($logvolume, $logdirectory, $f);
493 4 50       89 CopyFile($args{log}, $destlog) if($fulllog ne $destlog);
494 4         17 my $reldir = abs2rel($args{log}, catpath($logvolume, $logdirectory, ''));
495            
496 4         783 $writer->dataElement('a', "Log file", href=>$f);
497             }
498 5         1414 $writer->endTag('li');
499 5 50       143 do { chdir($cdir) || Abort("Can't change back to $cdir: $!"); } if ($args{directory});
  3 100       114  
500 5         117 return $retval;
501             }
502             =head2 CopyFiles(source, destdir)
503            
504             This function copies all the files that match the source glob pattern
505             to the given directory. The names will remain the same.
506            
507             =cut
508            
509             # copy over several files into a new directory
510             sub CopyFiles {
511 0     0 0 0 my ($src, $dest) = @_;
512 0 0 0     0 Abort("$dest is not a directory") if (!$dryrun && ! -d $dest);
513 0         0 foreach my $sfile (glob $src) {
514 0         0 my $bname = basename($sfile);
515 0 0       0 return 0 if CopyFile($sfile, "$dest/$bname") == 0;
516             }
517 0         0 return 1;
518            
519             }
520            
521             =head2 CopyTree(source, dest)
522            
523             This function copies an entire tree hierarchy from the source to the
524             destination. It makes use of File::Copy::Recursive routines to do this.
525            
526             =cut
527            
528             # copy over several files into a new directory
529             sub CopyTree {
530 1     1 1 3 my ($src, $dest) = @_;
531 1         8 $writer->dataElement('li', "Copy $src tree to $dest\n");
532 1 50       81 if (!$dryrun) {
533 1         13 my($nfdirs,$ndirs,$depth) = File::Copy::Recursive::rcopy($src, $dest);
534 1         1095 $writer->dataElement('li',
535             "Copied $nfdirs files and directories, $ndirs directories to a depth of $depth");
536            
537 1         153 return $nfdirs;
538             }
539 0         0 return 1;
540             }
541            
542             =head2 CopyFile(source, dest)
543            
544             This function copies an individual file from the source to the
545             destination. It allows for renaming.
546            
547             =cut
548            
549             # copy a file and possibly rename.
550             sub CopyFile {
551 2     2 1 5 my ($src, $dest) = @_;
552 2         12 $writer->dataElement('li', "Copy $src to $dest\n");
553 2 50       152 return 1 if ($dryrun);
554 2 100       16 if( copy($src, $dest) == 0) {
555 1         240 $writer->dataElement('li', "Copy failed: $!\n");
556 1         65 return 0;
557             }
558 1         13087 return 1;
559             }
560            
561            
562             =head2 MoveFiles(source, destdir)
563            
564             This function moves all the files that match the source glob pattern
565             to the given directory. The names will remain the same.
566            
567             =cut
568            
569             # move over several files into a new directory
570             sub MoveFiles {
571 0     0 1 0 my ($src, $dest) = @_;
572 0 0 0     0 Abort("$dest is not a directory") if (!$dryrun && ! -d $dest);
573 0         0 foreach my $sfile (glob $src) {
574 0         0 my $bname = basename($sfile);
575 0 0       0 return 0 if MoveFile($sfile, "$dest/$bname") == 0;
576             }
577 0         0 return 1;
578            
579             }
580            
581             =head2 MoveFile(source, dest)
582            
583             This function moves an individual file from the source to the
584             destination. It allows for renaming.
585            
586             =cut
587            
588             # copy a file and possibly rename.
589             sub MoveFile {
590 1     1 1 3 my ($src, $dest) = @_;
591 1         18 $writer->dataElement('li', "Move $src to $dest\n");
592 1 50       126 return 1 if ($dryrun);
593 1 50       12 if( move($src, $dest) == 0) {
594 0         0 $writer->dataElement('li', "Move failed: $!\n");
595 0         0 return 0;
596             }
597 1         129 return 1;
598             }
599             =head2 UpdateFileVersion(file, patterns)
600            
601             This functions name will probably change. It allows for updating files
602             contents based on the given set of patterns. Some care is needed to
603             get the patterns and the replacements correct. The replacement text is
604             subject to double evaluation.
605            
606             =cut
607            
608             sub UpdateFileVersion {
609 2     2 0 424 my ($file, %patterns) = @_;
610 2 100       14 $writer->startTag('ul') if ! $writer->in_element('ul');
611 2         80 $writer->startTag('li');
612 2         114 $writer->characters("Update file $file\n");
613 2         58 $writer->startTag('ul');
614 2 50       154 open(FILE, $file) || do { $writer->characters("Can't open file $file: $!"); return 0; };
  0         0  
  0         0  
615 2 50       290 open(FILEOUT, ">$file.$$") || do { $writer->characters("Can't open file $file.$$: $!"); return 0; };
  0         0  
  0         0  
616 2         44 while (my $line = ) {
617 8         32 while( my($k, $v) = each %patterns) {
618 12 100       329 if ($line =~ s/$k/$v/ee) {
  3         314  
619 3         5 my $vv;
620 3         183 eval "\$vv = $v";
621 3         545 $writer->dataElement("li","Changed line '$line' '$1' '$2' '$v' $vv\n");
622             }
623             }
624 8         352 print FILEOUT $line;
625             }
626 2         26 close(FILE);
627 2         217 close(FILEOUT);
628 2         11 $writer->endTag('ul');
629 2         54 $writer->endTag('li');
630 2 50       52 return 1 if ($dryrun);
631 2         276 return rename("$file.$$", $file);
632             }
633            
634            
635             =head2 AddOutput(list)
636            
637             This allows additional commentary to be added to the output stream.
638            
639             =cut
640            
641             sub AddOutput {
642 0     0 1 0 $writer->characters("@_");
643             }
644            
645             =head2 AddElement(list)
646            
647             This allows additional constructs to be added to the output, such a
648             href references and so on. It is passed onto XML::Writer::dataElement
649             directly and takes the same syntax.
650            
651             =cut
652            
653             sub AddElement {
654 0     0 1 0 $writer->dataElement(@_);
655             }
656            
657            
658             =head2 RunTests(args)
659            
660             Run the list of perl style test files, and capture the result in the
661             output of the log. The The arguments allow you to specify the tests to
662             run, see PANT::Test for details.
663            
664             =cut
665            
666             sub RunTests {
667 1     1 1 42 require PANT::Test;
668 1         11 my $test = new PANT::Test($writer, dryrun=>$dryrun);
669 1         4 return $test->RunTests(@_);
670             }
671            
672             =head2 Zip(file)
673            
674             This function returns a PANT::Zip object to help construct the given zip file.
675             See PANT::Zip for more details.
676            
677             =cut
678            
679             sub Zip {
680 1     1 1 47 require PANT::Zip;
681 1         11 return new PANT::Zip($writer, @_, dryrun=>$dryrun);
682             }
683            
684             =head2 Cvs()
685            
686             This function returns a PANT::Cvs object to help with running Cvs commands.
687             See PANT::Cvs for more details.
688            
689             =cut
690            
691             sub Cvs {
692 1     1 1 228 require PANT::Cvs;
693 1         10 return new PANT::Cvs($writer, @_, dryrun=>$dryrun);
694             }
695            
696             =head2 Svn()
697            
698             This function returns a PANT::Svn object to help with running Svn commands.
699             See PANT::Svn for more details.
700            
701             =cut
702            
703             sub Svn {
704 1     1 1 264 require PANT::Svn;
705 1         11 return new PANT::Svn($writer, @_, dryrun=>$dryrun);
706             }
707            
708            
709             =head2 FileCompare(F1, F2)
710            
711             This function compares two files using the File::Compare routines to
712             see if their contents are identical.
713            
714             =cut
715            
716             sub FileCompare {
717 1     1 1 3 my($f1, $f2) = @_;
718 1         16 return File::Compare::compare($f1, $f2) == 0;
719             }
720            
721             =head2 MakeTree(dir)
722            
723             Create a given directory, and all required intermediate paths.
724            
725             =cut
726            
727             sub MakeTree {
728 1     1 1 1552 my $dir = shift;
729 1         11 $writer->dataElement('li', "Create directory tree $dir\n");
730 1 50       244 return 1 if ($dryrun);
731 1         66 eval { mkpath($dir) };
  1         438  
732 1 50       5 if ($@) {
733 0         0 $writer->dataElement('li', "Couldn't create directory $dir: $@");
734 0         0 return 0;
735             }
736 1         17 return -d $dir;
737             }
738            
739             =head2 RmTree(dir)
740            
741             This function removes the entire tree starting at the given directory.
742             Obviously be careful!
743            
744             =cut
745            
746             sub RmTree {
747 2     2 1 9 my $dir = shift;
748 2         12 $writer->dataElement('li', "Remove tree $dir\n");
749 2 50       189 return 1 if ($dryrun);
750 2         4023 rmtree($dir);
751 2         36 return ! -d $dir;
752             }
753            
754            
755             =head2 BuildSolution(project, args...)
756            
757             This function attempts to build a visual studio style project.
758             The first argument is the base name of the project, which will be used to
759             derive the F<.SLN> and other files.
760             It has the following parameters,
761            
762             =over 4
763            
764             =item solution=>name
765            
766             The name of the solution file. This can be used to insert a .vcproj file
767             to have a similar effect.
768            
769             =item project=>name
770            
771             The given project in the solution you wish to build.
772            
773             =item buildtype=>type
774            
775             What sort of build you want to do. These are the support targets from
776             visual studio, such as /build (default), /rebuild, /clean, /deploy etc.
777            
778             =item log=>file
779            
780             Where to output the log. The default is the base name with .log appended.
781            
782             =item target=>Release
783            
784             The target build environment, usually Debug or Release.
785            
786             =item devenv=>devenv
787            
788             The name of the devenv binary - which might be a full pathname.
789            
790             =back
791            
792             =cut
793            
794             sub BuildSolution {
795 0     0 1 0 my($sln, %args) = @_;
796 0   0     0 my $slnfile = $args{solution} || "$sln.sln";
797 0   0     0 my $project = $args{project} || $sln;
798 0   0     0 my $buildtype = $args{buildtype} || "/build";
799 0   0     0 my $log = $args{log} || "$sln.log";
800 0   0     0 my $buildtarget = $args{target} || "Release";
801 0   0     0 my $devenv = $args{devenv} || "devenv";
802            
803 0         0 my $cmd = qq{$devenv $slnfile $buildtype "$buildtarget" /project $project /out $log};
804 0         0 return Command($cmd, log=>$log);
805            
806             }
807            
808             =head2 FindPatternInFile(file, pattern)
809            
810             This function searches the given file line by line, until it finds the
811             pattern given, and returns the string matching the first bracketed
812             expression int the regexp. This can be used to
813             find things like file versions.
814            
815             =over 4
816            
817             my $ver = FindPatternInFile("thing.rc", qr/^\s*FILEVERSION\s*(\d+,\d+,\d+,\d+)/);
818            
819             =back
820            
821             =cut
822            
823             sub FindPatternInFile {
824 3     3 1 9 my ($file, $pat) = @_;
825 3 50       118 open(FILE, $file) || return undef;
826 3         44 while (my $line = ) {
827 10 100       60 if ($line =~ $pat) {
828 3         41 close(FILE);
829 3         38 return $1;
830             }
831             }
832 0           close(FILE);
833             }
834            
835             1;
836            
837             __END__