File Coverage

blib/lib/Test/Smoke/BuildCFG.pm
Criterion Covered Total %
statement 207 241 85.8
branch 66 92 71.7
condition 26 39 66.6
subroutine 32 34 94.1
pod 12 12 100.0
total 343 418 82.0


line stmt bran cond sub pod time code
1             package Test::Smoke::BuildCFG;
2 13     13   2084 use strict;
  13         46  
  13         585  
3              
4             our $VERSION = '0.010';
5              
6 13     13   119 use Cwd;
  13         43  
  13         784  
7 13     13   95 use File::Spec;
  13         36  
  13         417  
8             require File::Path;
9 13     13   97 use Test::Smoke::LogMixin;
  13         38  
  13         823  
10 13     13   2468 use Test::Smoke::Util qw( skip_config );
  13         97  
  13         29575  
11              
12             my %CONFIG = (
13             df_v => 0,
14             df_dfopts => '-Dusedevel',
15             );
16              
17             =head1 NAME
18              
19             Test::Smoke::BuildCFG - OO interface for handling build configurations
20              
21             =head1 SYNOPSIS
22              
23             use Test::Smoke::BuildCFG;
24              
25             my $name = 'perlcurrent.cfg';
26             my $bcfg = Test::Smoke::BuildCFG->new( $name );
27              
28             foreach my $config ( $bcfg->configurations ) {
29             # do somthing with $config
30             }
31              
32             =head1 DESCRIPTION
33              
34             Handle the build configurations
35              
36             =head1 METHODS
37              
38             =head2 Test::Smoke::BuildCFG->new( [$cfgname] )
39              
40             [ Constructor | Public ]
41              
42             Initialise a new object.
43              
44             =cut
45              
46             sub new {
47 8     8 1 5648 my $proto = shift;
48 8 50       33 my $class = ref $proto ? ref $proto : $proto;
49              
50 8         16 my $config = shift;
51              
52 8 100       50 my %args_raw = @_ ? UNIVERSAL::isa( $_[0], 'HASH' ) ? %{ $_[0] } : @_ : ();
  6 100       23  
53              
54             my %args = map {
55 8         34 ( my $key = $_ ) =~ s/^-?(.+)$/lc $1/e;
  7         68  
  7         37  
56 7         38 ( $key => $args_raw{ $_ } );
57             } keys %args_raw;
58              
59             my %fields = map {
60 8 100       25 my $value = exists $args{$_} ? $args{ $_ } : $CONFIG{ "df_$_" };
  16         67  
61 16         56 ( $_ => $value )
62             } qw( v dfopts );
63              
64 8         33 my $self = bless \%fields, $class;
65 8         52 $self->read_parse( $config );
66             }
67              
68             =head2 Test::Smoke::BuildCFG->continue( $logfile[, $cfgname, %options] )
69              
70             [Constructor | public]
71              
72             Initialize a new object without the configurations that have already
73             been fully processed. If *all* configurations have been processed,
74             just pass the equivalent of the C method.
75              
76             =cut
77              
78             sub continue {
79 1     1 1 1125 my $proto = shift;
80 1 50       7 my $class = ref $proto ? ref $proto : $proto;
81              
82 1         3 my $logfile = shift;
83              
84 1         4 my $self = $class->new( @_ );
85 1         3 $self->{_continue} = 1;
86 1 50 33     27 return $self unless $logfile && -f $logfile;
87              
88 1         6 my %seen = __get_smoked_configs( $logfile );
89 1         6 my @not_seen = ();
90 1         6 foreach my $config ( $self->configurations ) {
91 4 100 66     13 push @not_seen, $config unless exists $seen{ "$config" } ||
92             skip_config( $config );
93             }
94 1 50       9 return $self unless @not_seen;
95 1         4 $self->{_list} = \@not_seen;
96 1         4 return $self;
97             }
98              
99             =head2 $bldcfg->verbose
100              
101             [ Getter | Public]
102              
103             Get verbosity.
104              
105             =cut
106              
107 32     32 1 174 sub verbose { $_[0]->{v} }
108              
109             =head2 Test::Smoke::BuildCFG->config( $key[, $value] )
110              
111             [ ClassAccessor | Public ]
112              
113             C is an interface to the package lexical C<%CONFIG>,
114             which holds all the default values for the C arguments.
115              
116             With the special key B this returns a reference
117             to a hash holding all the default values.
118              
119             =cut
120              
121             sub config {
122 18     18 1 34 my $dummy = shift;
123              
124 18         60 my $key = lc shift;
125              
126 18 50       59 if ( $key eq 'all_defaults' ) {
127             my %default = map {
128 0         0 my( $pass_key ) = $_ =~ /^df_(.+)/;
  0         0  
129 0         0 ( $pass_key => $CONFIG{ $_ } );
130             } grep /^df_/ => keys %CONFIG;
131 0         0 return \%default;
132             }
133              
134 18 50       62 return undef unless exists $CONFIG{ "df_$key" };
135              
136 18 100       74 $CONFIG{ "df_$key" } = shift if @_;
137              
138 18         67 return $CONFIG{ "df_$key" };
139             }
140              
141             =head2 $self->read_parse( $cfgname )
142              
143             C reads the build configurations file and parses it.
144              
145             =cut
146              
147             sub read_parse {
148 8     8 1 15 my $self = shift;
149              
150 8         39 $self->_read( @_ );
151 8         47 $self->_parse;
152              
153 8         60 return $self;
154             }
155              
156             =head2 $self->_read( $nameorref )
157              
158             C<_read()> is a private method that handles the reading.
159              
160             =over 4
161              
162             =item B build configurations are in C<$$nameorref>
163              
164             =item B build configurations are in C<@$nameorref>
165              
166             =item B build configurations are read from the filehandle
167              
168             =item B are taken as the filename for the build configurations
169              
170             =back
171              
172             =cut
173              
174             sub _read {
175 8     8   12 my $self = shift;
176 8         18 my( $nameorref ) = @_;
177 8 100       23 $nameorref = '' unless defined $nameorref;
178              
179 8         29 my $vmsg = "";
180 8         34 local *BUILDCFG;
181 8 100       53 if ( ref $nameorref eq 'SCALAR' ) {
    50          
    50          
    50          
182 6         16 $self->{_buildcfg} = $$nameorref;
183 6         10 $vmsg = "internal content";
184             } elsif ( ref $nameorref eq 'ARRAY' ) {
185 0         0 $self->{_buildcfg} = join "", @$nameorref;
186 0         0 $vmsg = "internal content";
187             } elsif ( ref $nameorref eq 'HASH' ) {
188 0         0 $self->{_buildcfg} = undef;
189 0         0 $self->{_list} = $nameorref->{_list};
190 0         0 $vmsg = "continuing smoke";
191             } elsif ( ref $nameorref eq 'GLOB' ) {
192 0         0 *BUILDCFG = *$nameorref;
193 0         0 $self->{_buildcfg} = do { local $/; };
  0         0  
  0         0  
194 0         0 $vmsg = "anonymous filehandle";
195             } else {
196 2 50       8 if ( $nameorref ) {
197 0 0       0 if ( open BUILDCFG, "< $nameorref" ) {
198 0         0 $self->{_buildcfg} = do { local $/; };
  0         0  
  0         0  
199 0         0 close BUILDCFG;
200 0         0 $vmsg = $nameorref;
201             } else {
202 0         0 require Carp;
203 0         0 Carp::carp("Cannot read buildconfigurations ($nameorref): $!");
204 0         0 $self->{_buildcfg} = $self->default_buildcfg();
205 0         0 $vmsg = "internal content";
206             }
207             } else { # Allow intentional default_buildcfg()
208 2         16 $self->{_buildcfg} = $self->default_buildcfg();
209 2         19 $vmsg = "internal content";
210             }
211             }
212 8 50       31 $vmsg .= "[continue]" if $self->{_continue};
213 8         56 $self->log_info("Reading build configurations from %s", $vmsg);
214             }
215              
216             =head2 $self->_parse( )
217              
218             C<_parse()> will split the build configurations file in sections.
219             Sections are ended with a line that begins with an equals-sign ('=').
220              
221             There are two types of section
222              
223             =over
224              
225             =item B
226              
227             =item B
228              
229             A B contains a "target-option". This is a build option
230             that should be in the ccflags variable in the F file
231             (see also L) and starts with a (forward) slash ('/').
232              
233             A B can have only one (1) target-option.
234              
235             =back
236              
237             =cut
238              
239             sub _parse {
240 8     8   15 my $self = shift;
241              
242 8 50       27 return unless defined $self->{_buildcfg}; # || $self->{_list};
243              
244 8         27 $self->{_sections} = [ ];
245 8         76 my @sections = split m/^=.*\n/m, $self->{_buildcfg};
246 8         57 $self->log_debug("Found %d raw-sections", scalar @sections);
247              
248 8         31 foreach my $section ( @sections ) {
249 25         47 chomp $section;
250 25         34 my $index = 0;
251 25         105 my %opts = map { s/^\s+$//; $_ => $index++ }
  56         95  
  56         147  
252             grep !/^#/ => split /\n/, $section, -1;
253             # Skip empty sections
254 25 100 66     188 next if (keys %opts == 0) or (exists $opts{ "" } and keys %opts == 1);
      100        
255              
256 22 100       100 if ( grep m|^/.+/?$| => keys %opts ) { # Policy section
257 9         18 my @targets;
258 9         26 my @lines = keys %opts;
259 9         27 foreach my $line ( @lines ) {
260 27 100       103 next unless $line =~ m|^/(.+?)/?$|;
261              
262 9         33 push @targets, $1;
263 9         29 delete $opts{ $line };
264             }
265 9 50       23 if ( @targets > 1 ) {
266 0         0 require Carp;
267 0         0 Carp::carp( "Multiple policy lines in one section:\n\t",
268             join( "\n\t", @targets ),
269             "\nWill use /$targets[0]/\n" );
270             }
271 9         32 push @{ $self->{_sections} },
272             { policy_target => $targets[0],
273 9         52 args => [ sort {$opts{ $a } <=> $opts{ $b }} keys %opts ] };
  9         69  
274              
275             } else { # Buildopt section
276 13         61 push @{ $self->{_sections} },
277 13         21 [ sort {$opts{ $a } <=> $opts{ $b }} keys %opts ];
  16         103  
278             }
279             }
280             # Make sure we have at least *one* section
281 8 50       21 push @{ $self->{_sections} }, [ "" ] unless @{ $self->{_sections} };
  0         0  
  8         33  
282              
283 8         16 $self->log_debug("Left with %d parsed sections", scalar @{$self->{_sections}});
  8         36  
284 8         27 $self->_serialize;
285 8         20 $self->log_debug("Found %d (unfiltered) configurations", scalar @{$self->{_list}});
  8         48  
286             }
287              
288             =head2 $self->_serialize( )
289              
290             C<_serialize()> creates a list of B
291             objects from the parsed sections.
292              
293             =cut
294              
295             sub _serialize {
296 8     8   55 my $self = shift;
297              
298 8         17 my $list = [ ];
299 8         17 __build_list( $list, $self->{dfopts}, [ ], @{ $self->{_sections} } );
  8         34  
300              
301 8         28 $self->{_list} = $list;
302             }
303              
304             =head2 __build_list( $list, $previous_args, $policy_subst, $this_cfg, @cfgs )
305              
306             Recursive sub, mainly taken from the old C in F
307              
308             =cut
309              
310             sub __build_list {
311 52     52   112 my( $list, $previous_args, $policy_subst, $this_cfg, @cfgs ) = @_;
312              
313 52         59 my $policy_target;
314 52 100       120 if ( ref $this_cfg eq "HASH" ) {
315 33         56 $policy_target = $this_cfg->{policy_target};
316 33         46 $this_cfg = $this_cfg->{args};
317             }
318              
319 52         86 foreach my $conf ( @$this_cfg ) {
320 108         177 my $config_args = $previous_args;
321 108 100       218 $config_args .= " $conf" if length $conf;
322              
323 108         162 my @substitutions = @$policy_subst;
324 108 100       278 push @substitutions, [ $policy_target, $conf ]
325             if defined $policy_target;
326              
327 108 100       187 if ( @cfgs ) {
328 44         130 __build_list( $list, $config_args, \@substitutions, @cfgs );
329 44         90 next;
330             }
331              
332 64         168 push @$list, Test::Smoke::BuildCFG::Config->new(
333             $config_args, @substitutions
334             );
335             }
336             }
337              
338             =head2 $buildcfg->configurations( )
339              
340             Returns the list of configurations (Test::Smoke::BuildCFG::Config objects)
341              
342             =cut
343              
344             sub configurations {
345 7     7 1 3107 my $self = shift;
346              
347 7         15 @{ $self->{_list} };
  7         33  
348             }
349              
350             =head2 $buildcfg->policy_targets( )
351              
352             Returns a list of policytargets from the policy substitution sections
353              
354             =cut
355              
356             sub policy_targets {
357 2     2 1 17 my $self = shift;
358              
359 2 50       12 return unless UNIVERSAL::isa( $self->{_sections}, "ARRAY" );
360              
361 2         7 my @targets;
362 2         3 for my $section ( @{ $self->{_sections} } ) {
  2         12  
363             next unless UNIVERSAL::isa( $section, "HASH" ) &&
364 6 100 66     47 $section->{policy_target};
365 3         9 push @targets, $section->{policy_target};
366             }
367              
368 2         28 return @targets;
369             }
370              
371             =head2 as_string
372              
373             Return the parsed configuration as a string.
374              
375             =cut
376              
377             sub as_string {
378 5     5 1 4199 my $self = shift;
379 5         10 my @sections;
380 5         6 for my $section ( @{ $self->{_sections} } ) {
  5         12  
381 14 100       57 if ( UNIVERSAL::isa( $section, 'ARRAY' ) ) {
    50          
382 8         14 push @sections, $section;
383             } elsif ( UNIVERSAL::isa( $section, 'HASH' ) ) {
384             push @sections, [
385             "/$section->{policy_target}/",
386 6         14 @{ $section->{args} },
  6         19  
387             ];
388             }
389             }
390 5         64 return join "=\n", map join( "\n", @$_, "" ) => @sections;
391             }
392              
393             =head2 source
394              
395             returns the text-source of this instance.
396              
397             =cut
398              
399             sub source {
400 0     0 1 0 my $self = shift;
401              
402 0         0 return $self->{_buildcfg};
403             }
404              
405             =head2 sections
406              
407             returns an ARRAYREF of the sections in this instance.
408              
409             =cut
410              
411             sub sections {
412 0     0 1 0 my $self = shift;
413              
414 0         0 return $self->{_sections};
415             }
416              
417             =head2 __get_smoked_configs( $logfile )
418              
419             Parse the logfile and return a hash(ref) of already processed
420             configurations.
421              
422             =cut
423              
424             sub __get_smoked_configs {
425 1     1   3 my( $logfile ) = @_;
426              
427 1         2 my %conf_done = ( );
428 1         3 local *LOG;
429 1 50       35 if ( open LOG, "< $logfile" ) {
430 1         5 my $conf;
431             # A Configuration is done when we detect a new Configuration:
432             # or the phrase "Finished smoking $patch"
433 1         21 while ( ) {
434 17 100 66     74 s/^Configuration:\s*// || /^Finished smoking/ or next;
435 3 100       16 $conf and $conf_done{ $conf }++;
436 3         5 chomp; $conf = $_;
  3         9  
437             }
438 1         12 close LOG;
439             }
440 1 50       12 return wantarray ? %conf_done : \%conf_done;
441             }
442              
443             =head2 Test::Smoke::BuildCFG->default_buildcfg()
444              
445             This is a constant that returns a textversion of the default
446             configuration.
447              
448             =cut
449              
450             sub default_buildcfg() {
451              
452 2     2 1 30 return <<__EOCONFIG__;
453             # Test::Smoke::BuildCFG->default_buildcfg
454             # Check the documentation for more information
455             == Build all configurations with and without ithreads
456              
457             -Duseithreads
458             == Build with and without 64bitall
459              
460             -Duse64bitall
461             == All configurations with and without -DDEBUGGING
462             /-DDEBUGGING/
463              
464             -DDEBUGGING
465             __EOCONFIG__
466             }
467              
468             =head2 new_configuration( $config )
469              
470             A wrapper around C<< Test::Smoke::BuildCFG::Config->new() >> so the
471             object is accessible from outside this package.
472              
473             =cut
474              
475             sub new_configuration {
476 180     180 1 6186 return Test::Smoke::BuildCFG::Config->new( @_ );
477             }
478              
479             1;
480              
481             package Test::Smoke::BuildCFG::Config;
482              
483             use overload
484 83 100   83   13873 '""' => sub { $_[0]->[0] || "" },
485 13     13   2703 fallback => 1;
  13         2060  
  13         165  
486              
487 13     13   1315 use Text::ParseWords qw( quotewords );
  13         40  
  13         12615  
488              
489             =head1 PACKAGE
490              
491             Test::Smoke::BuildCFG::Config - OO interface for a build confiuration
492              
493             =head1 SYNOPSIS
494              
495             my $bcfg = Test::Smoke::BuildCFG::Config->new( $args, $policy );
496              
497             or
498              
499             my $bcfg = Test::Smoke::BuildCFG::Config->new;
500             $bcfg->args( $args );
501             $bcfg->policy( [ -DDEBUGGING => '-DDEBUGGING' ],
502             [ -DPERL_COPY_ON_WRITE => '' ] );
503              
504             if ( $bcfg->has_arg( '-Duseithreads' ) ) {
505             # do stuff for -Duseithreads
506             }
507              
508             =head1 DESCRIPTION
509              
510             This is a simple object that holds both the build arguments and the
511             policy substitutions. The build arguments are stored as a string and
512             the policy subtitutions are stored as a list of lists. Each substitution is
513             represented as a list with the two elements: the target and its substitute.
514              
515             =head1 METHODS
516              
517             =head2 Test::Smoke::BuildCFG::Config->new( [ $args[, \@policy_substs ]] )
518              
519             Create the new object as an anonymous list.
520              
521             =cut
522              
523             sub new {
524 244     244   467 my $proto = shift;
525 244   33     758 my $class = ref $proto || $proto;
526              
527 244         620 my $self = bless [ undef, [ ], { } ], $class;
528              
529 244 50       855 @_ >= 1 and $self->args( shift );
530 244 100       624 @_ > 0 and $self->policy( @_ );
531              
532 244         634 $self;
533             }
534              
535             =head2 $buildcfg->args( [$args] )
536              
537             Accessor for the build arguments field.
538              
539             =cut
540              
541             sub args {
542 244     244   327 my $self = shift;
543              
544 244 50       506 if ( defined $_[0] ) {
545 244         636 $self->[0] = shift;
546 244         618 $self->_split_args;
547             }
548              
549 244         532 $self->[0];
550             }
551              
552             =head2 $buildcfg->policy( [@substitutes] )
553              
554             Accessor for the policy substitutions.
555              
556             =cut
557              
558             sub policy {
559 80     80   6058 my $self = shift;
560              
561 80 100       154 if ( @_ ) {
562             my @substitutions = @_ == 1 && ref $_[0][0] eq 'ARRAY'
563 64 50 66     274 ? @{ $_[0] } : @_;
  0         0  
564 64         140 $self->[1] = \@substitutions;
565             }
566              
567 80         113 @{ $self->[1] };
  80         148  
568             }
569              
570             =head2 $self->_split_args( )
571              
572             Create a hash with all the build arguments as keys.
573              
574             =cut
575              
576             sub _split_args {
577 244     244   423 my $self = shift;
578              
579 244         507 my $i = 0;
580             $self->[2] = {
581 244         691 map { ( $_ => $i++ ) } quotewords( '\s+', 1, $self->[0] )
  549         23593  
582             };
583             $self->[0] = join( " ", sort {
584             $self->[2]{ $a } <=> $self->[2]{ $b }
585 244   100     685 } keys %{ $self->[2] } ) || "";
586             }
587              
588             =head2 $buildcfg->has_arg( $arg[,...] )
589              
590             Check the build arguments hash for C<$arg>. If you specify more then one
591             the results will be logically ANDed!
592              
593             =cut
594              
595             sub has_arg {
596 74     74   188 my $self = shift;
597              
598 74         103 my $ok = 1;
599 74   100     348 $ok &&= exists $self->[2]{ $_ } foreach @_;
600 74         397 return $ok;
601             }
602              
603             =head2 $buildcfg->any_arg( $arg[,...] )
604              
605             Check the build arguments hash for C<$arg>. If you specify more then one
606             the results will be logically ORed!
607              
608             =cut
609              
610             sub any_arg {
611 1     1   2 my $self = shift;
612              
613 1         3 my $ok = 0;
614 1   66     10 $ok ||= exists $self->[2]{ $_ } foreach @_;
615 1         6 return $ok;
616             }
617              
618             =head2 $buildcfg->args_eq( $args )
619              
620             C takes a string of config arguments and returns true if
621             C<$self> has exactly the same args as the C<$args> has.
622              
623             There is the small matter of default_args (dfopts) kept as a Class
624             variable in L!
625              
626             =cut
627              
628             sub args_eq {
629 17     17   36 my $self = shift;
630 17         25 my $args = shift;
631              
632             my $default_args = join "|", sort {
633 17         49 length($b) <=> length($a)
  0         0  
634             } quotewords( '\s+', 1, Test::Smoke::BuildCFG->config( 'dfopts' ) );
635              
636 26         77 my %copy = map { ( $_ => undef ) }
637 17         824 grep !/$default_args/ => keys %{ $self->[2] };
  17         119  
638 17         57 my @s_args = grep !/$default_args/ => quotewords( '\s+', 1, $args );
639 17         1432 my @left;
640 17         49 while ( my $option = pop @s_args ) {
641 26 50       55 if ( exists $copy{ $option } ) {
642 26         71 delete $copy{ $option };
643             } else {
644 0         0 push @left, $option;
645             }
646             }
647 17 50 33     122 return (@left || keys %copy) ? 0 : 1;
648             }
649              
650             =head2 $config->rm_arg( $arg[,..] )
651              
652             Simply remove the argument(s) from the list and recreate the arguments
653             line.
654              
655             =cut
656              
657             sub rm_arg {
658 170     170   263 my $self = shift;
659              
660 170         323 foreach my $arg ( @_ ) {
661 138 50       444 exists $self->[2]{ $arg } and delete $self->[2]{ $arg };
662             }
663             $self->[0] = join( " ", sort {
664             $self->[2]{ $a } <=> $self->[2]{ $b }
665 170   100     233 } keys %{ $self->[2] } ) || "";
666             }
667              
668             =head2 $config->vms
669              
670             Redo the the commandline switches in a VMSish way.
671              
672             =cut
673              
674             sub vms {
675 1     1   493 my $self = shift;
676              
677             return join( " ", map {
678             tr/"'//d;
679             s/^-//;
680             qq/-"$_"/;
681             } sort {
682             $self->[2]{ $a } <=> $self->[2]{ $b }
683 1   50     3 } keys %{ $self->[2] } ) || "";
684             }
685              
686             1;
687              
688             =head1 SEE ALSO
689              
690             L, L
691              
692             =head1 COPYRIGHT
693              
694             (c) 2002-2003, All rights reserved.
695              
696             * Abe Timmerman
697              
698             This library is free software; you can redistribute it and/or modify
699             it under the same terms as Perl itself.
700              
701             See:
702              
703             =over 4
704              
705             =item * http://www.perl.com/perl/misc/Artistic.html
706              
707             =item * http://www.gnu.org/copyleft/gpl.html
708              
709             =back
710              
711             This program is distributed in the hope that it will be useful,
712             but WITHOUT ANY WARRANTY; without even the implied warranty of
713             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
714              
715             =cut