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   1968 use strict;
  13         42  
  13         562  
3              
4             our $VERSION = '0.010';
5              
6 13     13   94 use Cwd;
  13         27  
  13         687  
7 13     13   91 use File::Spec;
  13         43  
  13         393  
8             require File::Path;
9 13     13   72 use Test::Smoke::LogMixin;
  13         30  
  13         792  
10 13     13   2354 use Test::Smoke::Util qw( skip_config );
  13         71  
  13         30155  
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 4694 my $proto = shift;
48 8 50       33 my $class = ref $proto ? ref $proto : $proto;
49              
50 8         18 my $config = shift;
51              
52 8 100       54 my %args_raw = @_ ? UNIVERSAL::isa( $_[0], 'HASH' ) ? %{ $_[0] } : @_ : ();
  6 100       24  
53              
54             my %args = map {
55 8         32 ( my $key = $_ ) =~ s/^-?(.+)$/lc $1/e;
  7         72  
  7         34  
56 7         41 ( $key => $args_raw{ $_ } );
57             } keys %args_raw;
58              
59             my %fields = map {
60 8 100       23 my $value = exists $args{$_} ? $args{ $_ } : $CONFIG{ "df_$_" };
  16         72  
61 16         60 ( $_ => $value )
62             } qw( v dfopts );
63              
64 8         31 my $self = bless \%fields, $class;
65 8         32 $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 1061 my $proto = shift;
80 1 50       5 my $class = ref $proto ? ref $proto : $proto;
81              
82 1         2 my $logfile = shift;
83              
84 1         4 my $self = $class->new( @_ );
85 1         4 $self->{_continue} = 1;
86 1 50 33     26 return $self unless $logfile && -f $logfile;
87              
88 1         8 my %seen = __get_smoked_configs( $logfile );
89 1         16 my @not_seen = ();
90 1         5 foreach my $config ( $self->configurations ) {
91 4 100 66     9 push @not_seen, $config unless exists $seen{ "$config" } ||
92             skip_config( $config );
93             }
94 1 50       3 return $self unless @not_seen;
95 1         6 $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 156 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 29 my $dummy = shift;
123              
124 18         40 my $key = lc shift;
125              
126 18 50       67 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       58 return undef unless exists $CONFIG{ "df_$key" };
135              
136 18 100       67 $CONFIG{ "df_$key" } = shift if @_;
137              
138 18         90 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 20 my $self = shift;
149              
150 8         31 $self->_read( @_ );
151 8         45 $self->_parse;
152              
153 8         50 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   15 my $self = shift;
176 8         22 my( $nameorref ) = @_;
177 8 100       35 $nameorref = '' unless defined $nameorref;
178              
179 8         24 my $vmsg = "";
180 8         28 local *BUILDCFG;
181 8 100       65 if ( ref $nameorref eq 'SCALAR' ) {
    50          
    50          
    50          
182 6         15 $self->{_buildcfg} = $$nameorref;
183 6         11 $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       21 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         42 $self->{_buildcfg} = $self->default_buildcfg();
209 2         13 $vmsg = "internal content";
210             }
211             }
212 8 50       22 $vmsg .= "[continue]" if $self->{_continue};
213 8         36 $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   16 my $self = shift;
241              
242 8 50       27 return unless defined $self->{_buildcfg}; # || $self->{_list};
243              
244 8         21 $self->{_sections} = [ ];
245 8         70 my @sections = split m/^=.*\n/m, $self->{_buildcfg};
246 8         43 $self->log_debug("Found %d raw-sections", scalar @sections);
247              
248 8         28 foreach my $section ( @sections ) {
249 25         46 chomp $section;
250 25         34 my $index = 0;
251 25         126 my %opts = map { s/^\s+$//; $_ => $index++ }
  56         103  
  56         133  
252             grep !/^#/ => split /\n/, $section, -1;
253             # Skip empty sections
254 25 100 66     190 next if (keys %opts == 0) or (exists $opts{ "" } and keys %opts == 1);
      100        
255              
256 22 100       95 if ( grep m|^/.+/?$| => keys %opts ) { # Policy section
257 9         17 my @targets;
258 9         25 my @lines = keys %opts;
259 9         19 foreach my $line ( @lines ) {
260 27 100       81 next unless $line =~ m|^/(.+?)/?$|;
261              
262 9         30 push @targets, $1;
263 9         21 delete $opts{ $line };
264             }
265 9 50       27 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         41 push @{ $self->{_sections} },
272             { policy_target => $targets[0],
273 9         44 args => [ sort {$opts{ $a } <=> $opts{ $b }} keys %opts ] };
  9         69  
274              
275             } else { # Buildopt section
276 13         70 push @{ $self->{_sections} },
277 13         21 [ sort {$opts{ $a } <=> $opts{ $b }} keys %opts ];
  16         80  
278             }
279             }
280             # Make sure we have at least *one* section
281 8 50       20 push @{ $self->{_sections} }, [ "" ] unless @{ $self->{_sections} };
  0         0  
  8         24  
282              
283 8         16 $self->log_debug("Left with %d parsed sections", scalar @{$self->{_sections}});
  8         31  
284 8         32 $self->_serialize;
285 8         12 $self->log_debug("Found %d (unfiltered) configurations", scalar @{$self->{_list}});
  8         33  
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   50 my $self = shift;
297              
298 8         18 my $list = [ ];
299 8         34 __build_list( $list, $self->{dfopts}, [ ], @{ $self->{_sections} } );
  8         27  
300              
301 8         27 $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   113 my( $list, $previous_args, $policy_subst, $this_cfg, @cfgs ) = @_;
312              
313 52         82 my $policy_target;
314 52 100       125 if ( ref $this_cfg eq "HASH" ) {
315 33         53 $policy_target = $this_cfg->{policy_target};
316 33         44 $this_cfg = $this_cfg->{args};
317             }
318              
319 52         87 foreach my $conf ( @$this_cfg ) {
320 108         161 my $config_args = $previous_args;
321 108 100       215 $config_args .= " $conf" if length $conf;
322              
323 108         170 my @substitutions = @$policy_subst;
324 108 100       283 push @substitutions, [ $policy_target, $conf ]
325             if defined $policy_target;
326              
327 108 100       188 if ( @cfgs ) {
328 44         128 __build_list( $list, $config_args, \@substitutions, @cfgs );
329 44         84 next;
330             }
331              
332 64         509 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 2779 my $self = shift;
346              
347 7         14 @{ $self->{_list} };
  7         27  
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 14 my $self = shift;
358              
359 2 50       13 return unless UNIVERSAL::isa( $self->{_sections}, "ARRAY" );
360              
361 2         8 my @targets;
362 2         7 for my $section ( @{ $self->{_sections} } ) {
  2         16  
363             next unless UNIVERSAL::isa( $section, "HASH" ) &&
364 6 100 66     36 $section->{policy_target};
365 3         20 push @targets, $section->{policy_target};
366             }
367              
368 2         53 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 3742 my $self = shift;
379 5         11 my @sections;
380 5         7 for my $section ( @{ $self->{_sections} } ) {
  5         13  
381 14 100       48 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         15 @{ $section->{args} },
  6         19  
387             ];
388             }
389             }
390 5         53 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         3 my %conf_done = ( );
428 1         6 local *LOG;
429 1 50       38 if ( open LOG, "< $logfile" ) {
430 1         4 my $conf;
431             # A Configuration is done when we detect a new Configuration:
432             # or the phrase "Finished smoking $patch"
433 1         28 while ( ) {
434 17 100 66     75 s/^Configuration:\s*// || /^Finished smoking/ or next;
435 3 100       11 $conf and $conf_done{ $conf }++;
436 3         5 chomp; $conf = $_;
  3         19  
437             }
438 1         13 close LOG;
439             }
440 1 50       15 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 23 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 5584 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   11803 '""' => sub { $_[0]->[0] || "" },
485 13     13   2519 fallback => 1;
  13         2042  
  13         158  
486              
487 13     13   1416 use Text::ParseWords qw( quotewords );
  13         39  
  13         12628  
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   432 my $proto = shift;
525 244   33     1006 my $class = ref $proto || $proto;
526              
527 244         691 my $self = bless [ undef, [ ], { } ], $class;
528              
529 244 50       850 @_ >= 1 and $self->args( shift );
530 244 100       615 @_ > 0 and $self->policy( @_ );
531              
532 244         660 $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   397 my $self = shift;
543              
544 244 50       498 if ( defined $_[0] ) {
545 244         621 $self->[0] = shift;
546 244         512 $self->_split_args;
547             }
548              
549 244         516 $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   4398 my $self = shift;
560              
561 80 100       161 if ( @_ ) {
562             my @substitutions = @_ == 1 && ref $_[0][0] eq 'ARRAY'
563 64 50 66     278 ? @{ $_[0] } : @_;
  0         0  
564 64         134 $self->[1] = \@substitutions;
565             }
566              
567 80         109 @{ $self->[1] };
  80         140  
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   378 my $self = shift;
578              
579 244         381 my $i = 0;
580             $self->[2] = {
581 244         775 map { ( $_ => $i++ ) } quotewords( '\s+', 1, $self->[0] )
  549         24246  
582             };
583             $self->[0] = join( " ", sort {
584             $self->[2]{ $a } <=> $self->[2]{ $b }
585 244   100     710 } 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   178 my $self = shift;
597              
598 74         107 my $ok = 1;
599 74   100     352 $ok &&= exists $self->[2]{ $_ } foreach @_;
600 74         406 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   3 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   32 my $self = shift;
630 17         28 my $args = shift;
631              
632             my $default_args = join "|", sort {
633 17         58 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         891 grep !/$default_args/ => keys %{ $self->[2] };
  17         172  
638 17         53 my @s_args = grep !/$default_args/ => quotewords( '\s+', 1, $args );
639 17         1453 my @left;
640 17         51 while ( my $option = pop @s_args ) {
641 26 50       70 if ( exists $copy{ $option } ) {
642 26         73 delete $copy{ $option };
643             } else {
644 0         0 push @left, $option;
645             }
646             }
647 17 50 33     132 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   301 my $self = shift;
659              
660 170         348 foreach my $arg ( @_ ) {
661 138 50       470 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     238 } 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   422 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