File Coverage

blib/lib/LCFG/Build/VCS.pm
Criterion Covered Total %
statement 33 103 32.0
branch 0 36 0.0
condition 0 3 0.0
subroutine 11 17 64.7
pod 5 6 83.3
total 49 165 29.7


line stmt bran cond sub pod time code
1             package LCFG::Build::VCS; # -*-perl-*-
2 1     1   662 use strict;
  1         1  
  1         34  
3 1     1   6 use warnings;
  1         2  
  1         50  
4              
5             # $Id: VCS.pm.in 31796 2017-01-19 13:31:51Z squinney@INF.ED.AC.UK $
6             # $Source: /var/cvs/dice/LCFG-Build-VCS/lib/LCFG/Build/VCS.pm.in,v $
7             # $Revision: 31796 $
8             # $HeadURL: https://svn.lcfg.org/svn/source/tags/LCFG-Build-VCS/LCFG_Build_VCS_0_2_3/lib/LCFG/Build/VCS.pm.in $
9             # $Date: 2017-01-19 13:31:51 +0000 (Thu, 19 Jan 2017) $
10              
11             our $VERSION = '0.2.3';
12              
13 1     1   825 use DateTime ();
  1         303671  
  1         34  
14 1     1   576 use File::Copy ();
  1         1676  
  1         20  
15 1     1   4 use File::Path ();
  1         1  
  1         10  
16 1     1   3 use File::Spec ();
  1         1  
  1         9  
17 1     1   730 use File::Temp ();
  1         6407  
  1         20  
18 1     1   5 use IO::File ();
  1         2  
  1         16  
19 1     1   817 use IPC::Run qw(run);
  1         20229  
  1         44  
20              
21 1     1   483 use Moose::Role;
  1         3191  
  1         4  
22 1     1   3898 use Moose::Util::TypeConstraints;
  1         1  
  1         4  
23              
24             subtype 'AbsPath'
25             => as 'Str'
26             => where { File::Spec->file_name_is_absolute($_) }
27             => message { 'Directory must be an absolute path.' };
28              
29             # coerce the input string (which is possibly a relative path) into an
30             # absolute path which does not have a trailing /
31              
32             coerce 'AbsPath'
33             => from 'Str'
34             => via { my $path = File::Spec->file_name_is_absolute($_) ? $_ : File::Spec->rel2abs($_); $path =~ s{/$}{}; $path };
35              
36             requires qw/checkcommitted genchangelog tagversion export export_devel import_project checkout_project/;
37              
38             has 'id' => (
39             is => 'ro',
40             isa => 'Str',
41             required => 1,
42             );
43              
44             has 'module' => (
45             is => 'rw',
46             isa => 'Str',
47             required => 1,
48             );
49              
50             has 'workdir' => (
51             is => 'rw',
52             isa => 'AbsPath',
53             required => 1,
54             coerce => 1,
55             default => q{.},
56             );
57              
58             has 'binpath' => (
59             is => 'rw',
60             isa => 'Str',
61             required => 0,
62             );
63              
64             has 'quiet' => (
65             is => 'rw',
66             isa => 'Bool',
67             default => 0,
68             required => 0,
69             );
70              
71             has 'dryrun' => (
72             is => 'rw',
73             isa => 'Bool',
74             default => 0,
75             required => 0,
76             );
77              
78             has 'logname' => (
79             is => 'rw',
80             isa => 'Str',
81             default => 'ChangeLog',
82             required => 1,
83             );
84              
85             sub build_cmd {
86 0     0 0   my ( $self, @args ) = @_;
87              
88 0           my @cmd = ( $self->binpath, @args );
89              
90 0           return @cmd;
91             }
92              
93             sub run_cmd {
94 0     0 1   my ( $self, @args ) = @_;
95              
96 0           my @cmd = $self->build_cmd(@args);
97              
98 0           my @out;
99 0 0         if ( $self->dryrun ) {
100 0           my $cmd = join q( ), @cmd;
101 0           print "Dry-run: $cmd\n";
102             }
103             else {
104 0           my ( $in, $out, $err );
105              
106 0           my $success = run \@cmd, \$in, \$out, \$err;
107 0 0         if ( !$success ) {
108 0           die "Error whilst running @cmd: $err\n";
109             }
110 0 0         if ($err) {
111 0           warn "$err\n";
112             }
113              
114 0           @out = split /[\r\n]+/, $out;
115             }
116              
117 0           return @out;
118             }
119              
120             sub logfile {
121 0     0 1   my ($self) = @_;
122              
123 0           return File::Spec->catfile( $self->workdir, $self->logname );
124             }
125              
126             sub gen_tag {
127 0     0 1   my ( $self, $version ) = @_;
128              
129             # Build a tag from the name and version (if specified) and then
130             # replace any period or hyphen characters.
131             #
132             # name: lcfg-foo, version: 1.0.1, gives: lcfg_foo_1_0_1
133              
134 0           my $tag;
135 0 0         if ( !defined $version ) {
136 0           $tag = $self->module;
137             }
138             else {
139              
140 0 0         if ( $version eq 'latest' ) {
141 0           $tag = 'latest';
142             }
143             else {
144 0           $tag = join q{_}, $self->module, $version;
145             }
146              
147             }
148              
149 0           $tag =~ s/\./_/g;
150 0           $tag =~ s/\-/_/g;
151              
152 0           return $tag;
153             }
154              
155             sub update_changelog {
156 0     0 1   my ( $self, $version ) = @_;
157              
158 0           my $dir = $self->workdir;
159 0           my $logfile = $self->logfile;
160              
161             # If this is a dry-run we will need to clean up the temporary file
162             # at the end. Otherwise it gets renamed to the logfile.
163              
164 0           my $unlink = 0;
165 0 0         if ( $self->dryrun ) {
166 0           $unlink = 1;
167             }
168              
169 0           my $tmplog = File::Temp->new(
170             UNLINK => $unlink,
171             DIR => $dir,
172             SUFFIX => '.tmp',
173             );
174              
175 0           my $tmpname = $tmplog->filename;
176              
177 0           my $date = DateTime->now->ymd;
178              
179 0           my $id = $self->id;
180              
181 0           print {$tmplog} <<"EOT";
  0            
182             $date $id: new release
183              
184             \t* Release: $version
185              
186             EOT
187              
188 0 0         if ( -f $logfile ) {
189 0 0         my $log = IO::File->new( $logfile, 'r' )
190             or die "Could not open $logfile: $!\n";
191              
192 0           while ( defined( my $line = <$log> ) ) {
193 0           print {$tmplog} $line;
  0            
194             }
195              
196 0           $log->close;
197             }
198              
199             $tmplog->close
200 0 0         or die "Could not close temporary file, $tmpname: $!\n";
201              
202 0 0         if ( !$self->dryrun ) {
203 0 0         rename $tmpname, $logfile
204             or die "Could not rename $tmpname as $logfile: $!\n";
205             }
206              
207 0           return;
208             }
209              
210             sub mirror_file {
211 0     0 1   my ( $self, $workdir, $exportdir, $dirname, $fname ) = @_;
212              
213 0           my $from_dir = File::Spec->catdir( $workdir, $dirname );
214 0           my $to_dir = File::Spec->catdir( $exportdir, $dirname );
215              
216 0 0 0       if ( !$self->dryrun && !-d $to_dir ) {
217 0           eval { File::Path::mkpath($to_dir) };
  0            
218 0 0         if ($@) {
219 0           die "Could not create $to_dir: $@\n";
220             }
221              
222 0           my ($dev, $ino, $mode, $nlink, $uid,
223             $gid, $rdev, $size, $atime, $mtime,
224             $ctime, $blksize, $blocks
225             ) = stat $from_dir;
226              
227 0 0         chmod $mode, $to_dir or die "chmod on $to_dir failed: $!\n";
228              
229             # We don't care about atime/mtime for directories
230             }
231              
232 0           my $from = File::Spec->catfile( $workdir, $dirname, $fname );
233 0           my $to = File::Spec->catfile( $exportdir, $dirname, $fname );
234              
235 0           my ($dev, $ino, $mode, $nlink, $uid,
236             $gid, $rdev, $size, $atime, $mtime,
237             $ctime, $blksize, $blocks
238             ) = stat $from;
239              
240 0 0         if ( $self->dryrun ) {
241 0           print "Dry-run: $from -> $to\n";
242             }
243             else {
244 0 0         File::Copy::syscopy( $from, $to )
245             or die "Copy $from to $to failed: $!\n";
246              
247 0 0         chmod $mode, $to or die "chmod on $to to ($mode) failed: $!\n";
248 0 0         utime $atime, $mtime, $to or die "utime on $to to ($atime, $mtime) failed: $!\n";
249              
250             }
251              
252 0           return;
253             }
254              
255             1;
256             __END__
257              
258             =head1 NAME
259              
260             LCFG::Build::VCS - LCFG version-control infrastructure
261              
262             =head1 VERSION
263              
264             This documentation refers to LCFG::Build::VCS version 0.2.3
265              
266             =head1 SYNOPSIS
267              
268             my $vcs = LCFG::Build::VCS::CVS->new();
269              
270             $vcs->genchangelog();
271              
272             if ( $vcs->checkcommitted() ) {
273             $vcs->tagversion();
274             }
275              
276             =head1 DESCRIPTION
277              
278             This is a suite of tools designed to provide a standardised interface
279             to version-control systems so that the LCFG build tools can deal with
280             project version-control in a high-level abstract fashion. Typically
281             they provide support for procedures such as importing and exporting
282             projects, doing tagged releases, generating the project changelog from
283             the version-control log and checking all changes are committed.
284              
285             This is an interface, you should not attempt to create objects
286             directly using this module. You will need to implement a sub-class,
287             for example L<LCFG::Build::VCS::CVS>. This interface requires certain
288             attributes and methods be specified within any implementing sub-class,
289             see below for details. For complete details you should read the
290             documentation associated with the specific sub-class.
291              
292             More information on the LCFG build tools is available from the website
293             http://www.lcfg.org/doc/buildtools/
294              
295             =head1 ATTRIBUTES
296              
297             =over 4
298              
299             =item module
300              
301             The name of the software package in this repository. This is required
302             and there is no default value.
303              
304             =item workdir
305              
306             The directory in which the version-control system commands should be
307             carried out. This is required and if none is specified then it will
308             default to '.', the current working directory. This must be an
309             absolute path but if you pass in a relative path coercion will
310             automatically occur based on the current working directory.
311              
312             =item binpath
313              
314             The path to the version-control tool. This is required and it is
315             expected that any module which implements this interface will set a
316             suitable default command name.
317              
318             =item quiet
319              
320             This is a boolean value which controls the quietness of the
321             version-control system commands. By default it is false and commands,
322             such as CVS, will print lots of extra stuff to the screen.
323              
324             =item dryrun
325              
326             This is a boolean value which controls whether the commands will
327             actually have a real effect or just print out what would be done. By
328             default it is false.
329              
330             =item logname
331              
332             The name of the logfile to which information should be directed when
333             doing version updates. This is also the name of the logfile to be used
334             if you utilise the automatic changelog generation option. The default
335             file name is 'ChangeLog'.
336              
337             =back
338              
339             =head1 SUBROUTINES/METHODS
340              
341             This module provides a few fully-implemented methods which are likely
342             to be useful for all sub-classes which implement the interface.
343              
344             =over 4
345              
346             =item gen_tag($version)
347              
348             Generate a tag based on the package name and the specified
349             version. Tags are generated from the module name attribute and the
350             version information passed in by replacing any hyphens or dots with
351             underscores and joining the two fields with an underscore. For
352             example, lcfg-foo and 1.0.1 would become lcfg_foo_1_0_1. If no version
353             is specified then just the module name will be used.
354              
355             =item update_changelog($version)
356              
357             This will add a standard-format release tag entry to the top of the
358             change log file.
359              
360             =item mirror_file( $sourcedir, $targetdir, $reldir, $basename )
361              
362             This will copy a file from the source directory to the target
363             directory. The relative path of the file (within the source directory)
364             must be split into the relative directory path and filename. Effort is
365             made to preserve the mode and, in the case of files, atime and
366             mtime. This is used by various modules in the export_devel() method to
367             mirror the project directory into a build directory.
368              
369             =item logfile()
370              
371             This is a convenience method which returns the full path to the
372             logfile based on the workdir and logname attributes.
373              
374             =back
375              
376             As well as the methods above, any class which implements this
377             interface MUST provide methods for:
378              
379             =over 4
380              
381             =item checkcommitted()
382              
383             Test to see if there are any uncommitted files in the project
384             directory. Note this test does not spot files which have not been
385             added to the version-control system. In scalar context the subroutine
386             returns 1 if all files are committed and 0 (zero) otherwise. In list
387             context the subroutine will return this code along with a list of any
388             files which require committing.
389              
390             =item genchangelog()
391              
392             This method will generate a changelog (the name of which is controlled
393             by the logname attribute) from the log kept within the version-control
394             system.
395              
396             =item tagversion($version)
397              
398             This method is used to tag a set of files for a project at a
399             particular version. It will also update the changelog
400             appropriately. The tag name is generated using the I<gen_tag()>
401             method, see below for full details.
402              
403             =item run_cmd(@args)
404              
405             A method used to handle the running of commands for the particular
406             version-control system. This is required for systems like CVS where
407             shell commands have to be executed. Not all modules will need to
408             implement this method as they may well use a proper Perl module API
409             (e.g. subversion).
410              
411             =item export( $version, $dir )
412              
413             Exports the source code for the project tagged at the specified
414             release. The second argument specifies the directory into which the
415             exported project directory will be placed.
416              
417             =item export_devel( $version, $dir )
418              
419             Exports the current development version of the source code for the
420             project (i.e. your working copy). The second argument specifies the
421             directory into which the exported project directory will be placed.
422              
423             =item import_project( $dir, $version, $message )
424              
425             Imports a project source tree into the version-control system.
426              
427             =item checkout_project( $version, $dir )
428              
429             Does a check-out from the version-control system of the project tagged
430             at the specified version. Unlike the export() method this checked-out
431             copy will include the files necessary for the version-control system
432             (e.g. CVS or .svn directories).
433              
434             =head1 DEPENDENCIES
435              
436             This module is L<Moose> powered. It also requires L<DateTime> and L<IPC::Run>.
437              
438             =head1 SEE ALSO
439              
440             L<LCFG::Build::PkgSpec>, L<LCFG::Build::VCS::CVS>, L<LCFG::Build::VCS::None>, L<LCFG::Build::Tools>
441              
442             =head1 PLATFORMS
443              
444             This is the list of platforms on which we have tested this
445             software. We expect this software to work on any Unix-like platform
446             which is supported by Perl.
447              
448             FedoraCore5, FedoraCore6, ScientificLinux5
449              
450             =head1 BUGS AND LIMITATIONS
451              
452             There are no known bugs in this application. Please report any
453             problems to bugs@lcfg.org, feedback and patches are also always very
454             welcome.
455              
456             =head1 AUTHOR
457              
458             Stephen Quinney <squinney@inf.ed.ac.uk>
459              
460             =head1 LICENSE AND COPYRIGHT
461              
462             Copyright (C) 2008 University of Edinburgh. All rights reserved.
463              
464             This library is free software; you can redistribute it and/or modify
465             it under the terms of the GPL, version 2 or later.
466              
467             =cut