File Coverage

blib/lib/LCFG/Build/Utils.pm
Criterion Covered Total %
statement 30 166 18.0
branch 0 56 0.0
condition 0 19 0.0
subroutine 10 20 50.0
pod 7 9 77.7
total 47 270 17.4


line stmt bran cond sub pod time code
1             package LCFG::Build::Utils; # -*-cperl-*-
2 1     1   1136 use strict;
  1         2  
  1         41  
3 1     1   5 use warnings;
  1         3  
  1         56  
4              
5             # $Id: Utils.pm.in 12955 2010-07-20 13:13:37Z squinney@INF.ED.AC.UK $
6             # $Source: /var/cvs/dice/LCFG-Build-Tools/lib/LCFG/Build/Utils.pm.in,v $
7             # $Revision: 12955 $
8             # $HeadURL: https://svn.lcfg.org/svn/source/tags/LCFG-Build-Tools/LCFG_Build_Tools_0_4_0/lib/LCFG/Build/Utils.pm.in $
9             # $Date: 2010-07-20 14:13:37 +0100 (Tue, 20 Jul 2010) $
10              
11             our $VERSION = '0.4.0';
12              
13 1     1   18 use Cwd ();
  1         2  
  1         15  
14 1     1   5 use File::Basename ();
  1         2  
  1         14  
15 1     1   5 use File::Find ();
  1         2  
  1         42  
16 1     1   6 use File::Spec ();
  1         2  
  1         14  
17 1     1   1507 use File::Temp ();
  1         19837  
  1         22  
18 1     1   726 use IO::File ();
  1         846  
  1         21  
19 1     1   140582 use Module::Pluggable search_path => [ 'LCFG::Build::Utils' ];
  1         14446  
  1         13  
20              
21 1     1   102 use constant NOT_FOUND => -1;
  1         2  
  1         2334  
22              
23             {
24             my $datadir = exists $ENV{LCFG_BUILD_TMPLDIR}
25             ? $ENV{LCFG_BUILD_TMPLDIR}
26             : '/usr/share/lcfgbuild';
27              
28             sub datadir {
29 0 0   0 0   if ( defined $_[0] ) {
30 0           $datadir = $_[0];
31             }
32 0           return $datadir;
33             }
34             }
35              
36             { # Caching going on here, make variables only readable by this method.
37              
38             my ( $lcfgcfg, $mapping );
39              
40             require YAML::Syck;
41              
42             sub load_configs {
43              
44 0     0 0   my $datadir = datadir();
45              
46 0           my $lcfg_file = File::Spec->catfile( $datadir, 'lcfg_config.yml' );
47 0           my $map_file = File::Spec->catfile( $datadir, 'mapping_config.yml' );
48              
49 0   0       $lcfgcfg ||= YAML::Syck::LoadFile($lcfg_file);
50 0   0       $mapping ||= YAML::Syck::LoadFile($map_file);
51              
52 0           return ( $lcfgcfg, $mapping );
53             }
54             }
55              
56             sub translate_macro {
57 0     0 1   my ( $spec, $macro, $extra ) = @_;
58              
59 0           my ( $lcfgcfg, $mapping ) = load_configs();
60              
61 0           my $output;
62 0 0 0       if ( exists $mapping->{$macro} ) {
    0          
    0          
63 0           my $attr = $mapping->{$macro};
64 0           $output = $spec->$attr;
65              
66 0 0         if ( !defined $output ) {
    0          
67 0           $output = q{};
68             }
69             elsif ( ref $output eq 'ARRAY' ) {
70 0           $output = join q{, }, @{$output};
  0            
71             }
72              
73             }
74             elsif ( exists $lcfgcfg->{$macro} ) {
75 0           $output = $lcfgcfg->{$macro}{value};
76             }
77             elsif ( defined $extra && exists $extra->{$macro} ) {
78 0           $output = $extra->{$macro};
79             }
80             else {
81 0           warn "Unknown macro $macro\n";
82 0           return;
83             }
84              
85 0           return $output;
86              
87             }
88              
89             sub translate_string {
90 0     0 1   my ( $spec, $string, $style, $extra ) = @_;
91              
92 0   0       $style ||= 'autoconf';
93              
94 0           my ( $start_mark, $end_mark );
95 0 0         if ( $style eq 'cmake' ) { # ${FOO}
96 0           $start_mark = quotemeta(q(${));
97 0           $end_mark = quotemeta(q(}));
98             }
99             else { # @FOO@
100 0           $start_mark = quotemeta(q(@));
101 0           $end_mark = quotemeta(q(@));
102             }
103              
104 0           my @macros = (
105             $string =~ m{$start_mark
106             (\w+) # The macro name
107             $end_mark}gx
108             );
109              
110             # unique-ify
111 0           my %macros = map { $_ => 1 } @macros;
  0            
112 0           @macros = keys %macros;
113              
114 0           for my $macro (@macros) {
115 0           my $value = translate_macro( $spec, $macro, $extra );
116 0 0         if ( defined $value ) {
117 0           $string =~ s{$start_mark
118             \Q$macro\E
119             $end_mark}{$value}gx;
120             }
121             }
122              
123 0           return $string;
124             }
125              
126             sub translate_file {
127 0     0 1   my ( $spec, $in, $out, $extra ) = @_;
128              
129 0 0         my $fh = IO::File->new( $in, 'r' )
130             or die "Could not open $in: $!\n";
131              
132 0           my $outdir = ( File::Basename::fileparse($out) )[1];
133              
134 0           my $tmp = File::Temp->new(
135             TEMPLATE => 'tempXXXX',
136             DIR => $outdir,
137             UNLINK => 0,
138             );
139              
140 0           while ( defined( my $line = $fh->getline ) ) {
141 0           my $out = translate_string( $spec, $line, 'autoconf', $extra );
142 0           print {$tmp} $out;
  0            
143             }
144              
145 0           my $tmpname = $tmp->filename;
146              
147 0 0         $tmp->close or die "Could not close temporary file $tmpname: $!\n";
148              
149 0 0         rename $tmpname, $out
150             or die "Could not move temporary file $tmpname to $out: $!\n";
151              
152 0           my ( $dev, $ino, $mode, $nlink, $uid,
153             $gid, $rdev, $size, $atime, $mtime,
154             $ctime, $blksize, $blocks ) = stat $in;
155              
156             # Attempt to make the output files look the same as the input files
157              
158 0 0         chmod $mode, $out or warn "chmod on $out to ($mode) failed: $!\n";
159 0 0         utime $atime, $mtime, $out or warn "utime on $out to ($atime, $mtime) failed: $!\n";
160              
161 0           return;
162             }
163              
164             sub find_trans_files {
165 0     0 1   my ( $basedir, @translate ) = @_;
166              
167 0           require File::Find::Rule;
168              
169 0           my %found;
170              
171 0           for my $trans (@translate) {
172 0           my $searchdir = $basedir;
173 0           my $match = $trans;
174              
175             # Matches are *ALWAYS* expressed using a Unix-style
176             # path-separator. At this point if we spot a separator we need
177             # to handle splitting that string and putting it back together
178             # correctly for the current platform.
179              
180 0 0         if ( index( $trans, q{/} ) != NOT_FOUND ) {
181 0           my @parts = split /\//, $trans;
182              
183 0           $match = pop @parts;
184 0           $searchdir = File::Spec->catdir( $basedir, @parts );
185             }
186              
187 0           my @files;
188 0 0         if ( index( $match, q(*) ) != NOT_FOUND ) {
189              
190 0           @files = File::Find::Rule->file()->name($match)->in($searchdir);
191              
192             }
193             else {
194 0           my $file = File::Spec->catfile( $searchdir, $match );
195 0 0         if ( -f $file ) {
196 0           push @files, $file;
197             }
198             else {
199 0           warn "Could not find $file\n";
200             }
201             }
202              
203             # Remove the suffix if it is one of the supported special
204             # cases, i.e. .cin or .in
205              
206 0           my $suffix;
207 0 0         if ( $match =~ m/(\.c?in)$/ ) {
208 0           $suffix = $1;
209             }
210              
211 0           for my $file (@files) {
212 0           $file = File::Spec->abs2rel( $file, $basedir );
213              
214 0 0         if ( defined $suffix ) {
215 0           my ( $name, $path )
216             = File::Basename::fileparse( $file, $suffix );
217 0           $found{$file} = File::Spec->catfile( $path, $name );
218             }
219             else {
220 0           $found{$file} = $file;
221             }
222             }
223              
224             }
225              
226 0           return %found;
227             }
228              
229             sub find_and_translate {
230 0     0 1   my ( $spec, $dir, $remove_after ) = @_;
231              
232 0           my @translate = $spec->translate;
233              
234 0           my %trans_files = find_trans_files( $dir, @translate );
235              
236 0           for my $in ( keys %trans_files ) {
237 0           my $out = $trans_files{$in};
238              
239 0           $in = File::Spec->catfile( $dir, $in );
240 0           $out = File::Spec->catfile( $dir, $out );
241              
242 0           translate_file( $spec, $in, $out );
243              
244 0 0 0       if ( $remove_after && $in ne $out ) {
245 0           unlink $in;
246             }
247             }
248              
249 0           return;
250             }
251              
252             sub generate_cmake {
253 0     0 1   my ( $spec, $dir, $force ) = @_;
254              
255 0   0       $dir ||= q{.};
256              
257 0           my ( $lcfgcfg, $mapping ) = load_configs();
258              
259 0           require Template;
260              
261 0           my $datadir = datadir();
262 0           my $tmpldir = File::Spec->catdir( $datadir, 'templates' );
263              
264 0 0         my $tt = Template->new(
265             { INCLUDE_PATH => $tmpldir,
266             POST_CHOMP => 1,
267             PRE_CHOMP => 1,
268             }
269             ) or die $Template::ERROR . "\n";
270              
271 0           my $args = {
272             spec => $spec,
273             lcfgcfg => $lcfgcfg,
274             mapping => $mapping
275             };
276              
277             # We allow the user to write their own CMakeLists.txt unless an
278             # override is forced.
279              
280 0           my $cmake_file = File::Spec->catfile( $dir, 'CMakeLists.txt' );
281 0 0 0       if ( $force || !-e $cmake_file ) {
282 0 0         $tt->process( 'cmake.tt', $args, $cmake_file )
283             or die $tt->error() . "\n";
284             }
285              
286 0           my @translate = $spec->translate;
287              
288 0           my %trans_files = find_trans_files( $dir, @translate );
289              
290 0           $args->{trans} = {%trans_files};
291              
292 0           my $lcfg_cmake_file = File::Spec->catfile( $dir, 'lcfg.cmake' );
293              
294 0 0         $tt->process( 'lcfg.cmake.tt', $args, $lcfg_cmake_file )
295             or die $tt->error() . "\n";
296              
297 0           return;
298             }
299              
300             sub generate_srctar {
301 0     0 1   my ( $tarname, $srcdir, $resultsdir ) = @_;
302              
303 0 0         if ( !defined $srcdir ) {
304 0           $srcdir = q{.};
305             }
306 0 0         if ( !File::Spec->file_name_is_absolute( $srcdir ) ) {
307 0           $srcdir = File::Spec->rel2abs( $srcdir );
308             }
309              
310 0 0         if ( !defined $resultsdir ) {
311 0           $resultsdir = q{.};
312             }
313 0 0         if ( !File::Spec->file_name_is_absolute( $resultsdir ) ) {
314 0           $resultsdir = File::Spec->rel2abs( $resultsdir );
315             }
316              
317 0           my @parent_dirs = File::Spec->splitdir($srcdir);
318 0           pop @parent_dirs;
319 0           my $parent_dir = File::Spec->catdir(@parent_dirs);
320              
321 0           my $prev_dir = Cwd::getcwd(); # will need to go back to this later
322              
323 0 0         chdir $parent_dir or die "Could not cd to $parent_dir: $!\n";
324              
325 0           require Archive::Tar;
326 0           require IO::Zlib;
327              
328 0           my $tar = Archive::Tar->new();
329              
330 0           $tar->setcwd($parent_dir);
331              
332             File::Find::find(
333             { wanted => sub {
334 0     0     my $name
335             = File::Spec->abs2rel( $File::Find::name, $parent_dir );
336 0           $tar->add_files($name);
337             },
338 0           no_chdir => 1,
339             },
340             $srcdir
341             );
342              
343 0           my $tarfile = File::Spec->catfile( $resultsdir, $tarname );
344              
345 0           $tar->write( $tarfile, 1 );
346              
347 0           chdir $prev_dir;
348              
349 0           return $tarfile;
350             }
351              
352             1;
353             __END__
354              
355             =head1 NAME
356              
357             LCFG::Build::Utils - LCFG software building utilities
358              
359             =head1 VERSION
360              
361             This documentation refers to LCFG::Build::Utils version 0.4.0
362              
363             =head1 SYNOPSIS
364              
365             my $dir = q{.};
366              
367             my $spec = LCFG::Build::PkgSpec->new_from_metafile("$dir/lcfg.yml");
368              
369             LCFG::Build::Utils::find_and_translate( $spec, $dir );
370              
371             LCFG::Build::Utils::generate_cmake( $spec, $dir );
372              
373             =head1 DESCRIPTION
374              
375             This module provides a suite of utilities to help in building packages
376             from LCFG projects, particularly LCFG components. The methods are
377             mostly used by tools which implement the LCFG::Build::Tool base class
378             (e.g. LCFG::Build::Tool::Pack) but typically they are designed to be
379             generic enough to be used elsewhere.
380              
381             =head1 SUBROUTINES/METHODS
382              
383             =over 4
384              
385             =item translate_macro( $spec, $macro, $extra )
386              
387             This is used by all other translator subroutines in this module to do
388             the actual work of replacing macros with their values.
389              
390             This takes a macro name and translates it using the information stored
391             in the LCFG build metadata package specification. You can also provide
392             a reference to an extra hash of keys and values to be searched. You
393             should note that this subroutine takes a macro I<name> and not the
394             complete macro, i.e. it is C<FOO> not C<@FOO@>. An unknown macro will
395             result in the subroutine returning undef. To help in this case a
396             warning will be printed to STDERR.
397              
398             For backwards compatibility, if the macro is not found in the package
399             specification it will also look in the LCFG default package install
400             locations list (see C</usr/share/lcfgbuild/lcfg_config.yml> for
401             details). It is STRONGLY RECOMMENDED that you do NOT rely on this
402             location list as it will inconvenience downstream users who want to
403             repackage for different Operating Systems.
404              
405             For backwards compatibility the macros are looked up in a mapping list
406             which supports the old names. Any macros not beginning with the
407             C<LCFG_> sub-string should be considered deprecated as they will be
408             phased out in a future release. See
409             C</usr/share/lcfgbuild/mapping_config.yml> for a full list of
410             supported macro mappings.
411              
412             =item translate_string( $spec, $string, $style, $extra )
413              
414             This takes a string and finds all the macros embedded within and gets
415             them translated. Two styles of macros are supported C<cmake> which is
416             like C<${FOO}> and the standard autoconf style which is
417             C<@FOO@>. Specifying anything other than C<cmake> will result in
418             autoconf style substitutions. You can provide a reference to an extra
419             hash which will be passed through to translate_macro().
420              
421             Note that an unknown macro will NOT be removed from the string, a warning
422             is printed to STDERR when this occurs.
423              
424             =item translate_file( $spec, $in, $out, $extra )
425              
426             This takes the names for the input file and output file and,
427             optionally, a reference to a hash of extra key/values to be passed
428             through to translate_macro(). All macros in the file will be
429             translated as described above, currently only autoconf-style macros
430             are supported.
431              
432             =item find_trans_files( $dir, @translate )
433              
434             This takes the name of a directory in which to search and a list of
435             files and file globs to match. A hash of the files found is
436             returned. Note that the paths will all be relative to the specified
437             directory and not absolute.
438              
439             Unless you search for files with a .cin or .in suffix, which are
440             treated specially, the keys and values will be the same. In the
441             special cases the keys will be the path to the input files and the
442             values will be the file names with the .in or .cin file extension
443             removed.
444              
445             If you specify a particular file name and it is missing a warning will
446             be printed but this is not a fatal error.
447              
448             =item find_and_translate( $spec, $dir, $remove )
449              
450             This is a wrapper which combines find_trans_files() with the
451             application of the translate_file() subroutine on each file discovered
452             in the project directory. If you set the C<remove> parameter to a true
453             value then, if the input and output file names do not match, the input
454             files will be removed after the translation has occurred. This can be
455             useful if you want to generate a pristine tar file which does not
456             contain *.cin files.
457              
458             =item generate_cmake( $spec, $dir, $force )
459              
460             This generates all the necessary CMake files for building an LCFG
461             project. This will create a simple C<CMakeLists.txt> file unless you
462             need extra power in which case you can write your own and that will be
463             used instead. You can set the force variable to a true value to
464             overwrite any existing CMake file, if you wish. A second file is
465             created, named C<lcfg.cmake>, which contains all the necessary
466             functions and macros for building an LCFG project.
467              
468             =item $tarfile = generate_srctar( $tarname, $srcdir, $resultsdir )
469              
470             Takes the name of a tarfile to generate and packages up everything in
471             the specified source directory. The generated tar file will be placed
472             into the results directory. If either of the the source or results
473             directories are not specified then the current working directory will
474             be used. This returns the full path to the generated tar file.
475              
476             =back
477              
478             =head1 CONFIGURATION AND ENVIRONMENT
479              
480             Some of the routines use template files, by default it is assumed that
481             the standard template directory is C</usr/share/lcfgbuild/templates>.
482             You can override this using the LCFG_BUILD_TMPLDIR environment
483             variable. If you have done a local (i.e. non-root) install of this
484             module then this will almost certainly be necessary.
485              
486             =head1 DEPENDENCIES
487              
488             This module uses a number of other Perl modules. For generating
489             compressed tar files you will need L<Archive::Tar> and
490             L<IO::Zlib>. For generation of the CMake files you will need the Perl
491             Template Toolkit. For macro translation you will need
492             L<YAML::Syck>. For finding the translation files you will need
493             L<File::Find::Rule>.
494              
495             Although not a requirement for this module, in most cases if you want
496             to build the resulting software you will need CMake, version 2.6.0 or
497             greater, installed on your build machine.
498              
499             =head1 PLATFORMS
500              
501             This is the list of platforms on which we have tested this
502             software. We expect this software to work on any Unix-like platform
503             which is supported by Perl.
504              
505             Fedora12, Fedora13, ScientificLinux5, ScientificLinux6, MacOSX7
506              
507             =head1 BUGS AND LIMITATIONS
508              
509             There are no known bugs in this application. Please report any
510             problems to bugs@lcfg.org, feedback and patches are also always very
511             welcome.
512              
513             =head1 AUTHOR
514              
515             Stephen Quinney <squinney@inf.ed.ac.uk>
516              
517             =head1 LICENSE AND COPYRIGHT
518              
519             Copyright (C) 2008 University of Edinburgh. All rights reserved.
520              
521             This library is free software; you can redistribute it and/or modify
522             it under the terms of the GPL, version 2 or later.
523              
524             =cut
525