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   1840 use strict;
  13         27  
  13         492  
3              
4             our $VERSION = '0.010';
5              
6 13     13   86 use Cwd;
  13         24  
  13         641  
7 13     13   65 use File::Spec;
  13         38  
  13         320  
8             require File::Path;
9 13     13   64 use Test::Smoke::LogMixin;
  13         30  
  13         632  
10 13     13   1854 use Test::Smoke::Util qw( skip_config );
  13         63  
  13         23456  
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 4458 my $proto = shift;
48 8 50       23 my $class = ref $proto ? ref $proto : $proto;
49              
50 8         17 my $config = shift;
51              
52 8 100       39 my %args_raw = @_ ? UNIVERSAL::isa( $_[0], 'HASH' ) ? %{ $_[0] } : @_ : ();
  6 100       19  
53              
54             my %args = map {
55 8         20 ( my $key = $_ ) =~ s/^-?(.+)$/lc $1/e;
  7         52  
  7         27  
56 7         32 ( $key => $args_raw{ $_ } );
57             } keys %args_raw;
58              
59             my %fields = map {
60 8 100       24 my $value = exists $args{$_} ? $args{ $_ } : $CONFIG{ "df_$_" };
  16         54  
61 16         47 ( $_ => $value )
62             } qw( v dfopts );
63              
64 8         19 my $self = bless \%fields, $class;
65 8         29 $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 872 my $proto = shift;
80 1 50       4 my $class = ref $proto ? ref $proto : $proto;
81              
82 1         2 my $logfile = shift;
83              
84 1         3 my $self = $class->new( @_ );
85 1         2 $self->{_continue} = 1;
86 1 50 33     21 return $self unless $logfile && -f $logfile;
87              
88 1         5 my %seen = __get_smoked_configs( $logfile );
89 1         3 my @not_seen = ();
90 1         3 foreach my $config ( $self->configurations ) {
91 4 100 66     7 push @not_seen, $config unless exists $seen{ "$config" } ||
92             skip_config( $config );
93             }
94 1 50       3 return $self unless @not_seen;
95 1         5 $self->{_list} = \@not_seen;
96 1         3 return $self;
97             }
98              
99             =head2 $bldcfg->verbose
100              
101             [ Getter | Public]
102              
103             Get verbosity.
104              
105             =cut
106              
107 32     32 1 123 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         35 my $key = lc shift;
125              
126 18 50       44 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       52 return undef unless exists $CONFIG{ "df_$key" };
135              
136 18 100       45 $CONFIG{ "df_$key" } = shift if @_;
137              
138 18         61 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 16 my $self = shift;
149              
150 8         38 $self->_read( @_ );
151 8         29 $self->_parse;
152              
153 8         37 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         16 my( $nameorref ) = @_;
177 8 100       28 $nameorref = '' unless defined $nameorref;
178              
179 8         17 my $vmsg = "";
180 8         21 local *BUILDCFG;
181 8 100       43 if ( ref $nameorref eq 'SCALAR' ) {
    50          
    50          
    50          
182 6         13 $self->{_buildcfg} = $$nameorref;
183 6         7 $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       12 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         14 $self->{_buildcfg} = $self->default_buildcfg();
209 2         16 $vmsg = "internal content";
210             }
211             }
212 8 50       26 $vmsg .= "[continue]" if $self->{_continue};
213 8         29 $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   11 my $self = shift;
241              
242 8 50       18 return unless defined $self->{_buildcfg}; # || $self->{_list};
243              
244 8         14 $self->{_sections} = [ ];
245 8         55 my @sections = split m/^=.*\n/m, $self->{_buildcfg};
246 8         30 $self->log_debug("Found %d raw-sections", scalar @sections);
247              
248 8         17 foreach my $section ( @sections ) {
249 25         35 chomp $section;
250 25         27 my $index = 0;
251 25         93 my %opts = map { s/^\s+$//; $_ => $index++ }
  56         73  
  56         114  
252             grep !/^#/ => split /\n/, $section, -1;
253             # Skip empty sections
254 25 100 66     147 next if (keys %opts == 0) or (exists $opts{ "" } and keys %opts == 1);
      100        
255              
256 22 100       76 if ( grep m|^/.+/?$| => keys %opts ) { # Policy section
257 9         17 my @targets;
258 9         17 my @lines = keys %opts;
259 9         15 foreach my $line ( @lines ) {
260 27 100       75 next unless $line =~ m|^/(.+?)/?$|;
261              
262 9         27 push @targets, $1;
263 9         16 delete $opts{ $line };
264             }
265 9 50       24 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         27 push @{ $self->{_sections} },
272             { policy_target => $targets[0],
273 9         13 args => [ sort {$opts{ $a } <=> $opts{ $b }} keys %opts ] };
  9         48  
274              
275             } else { # Buildopt section
276 13         47 push @{ $self->{_sections} },
277 13         15 [ sort {$opts{ $a } <=> $opts{ $b }} keys %opts ];
  17         61  
278             }
279             }
280             # Make sure we have at least *one* section
281 8 50       12 push @{ $self->{_sections} }, [ "" ] unless @{ $self->{_sections} };
  0         0  
  8         24  
282              
283 8         11 $self->log_debug("Left with %d parsed sections", scalar @{$self->{_sections}});
  8         27  
284 8         22 $self->_serialize;
285 8         13 $self->log_debug("Found %d (unfiltered) configurations", scalar @{$self->{_list}});
  8         23  
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   35 my $self = shift;
297              
298 8         13 my $list = [ ];
299 8         15 __build_list( $list, $self->{dfopts}, [ ], @{ $self->{_sections} } );
  8         24  
300              
301 8         23 $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   91 my( $list, $previous_args, $policy_subst, $this_cfg, @cfgs ) = @_;
312              
313 52         52 my $policy_target;
314 52 100       90 if ( ref $this_cfg eq "HASH" ) {
315 33         44 $policy_target = $this_cfg->{policy_target};
316 33         39 $this_cfg = $this_cfg->{args};
317             }
318              
319 52         68 foreach my $conf ( @$this_cfg ) {
320 108         134 my $config_args = $previous_args;
321 108 100       189 $config_args .= " $conf" if length $conf;
322              
323 108         132 my @substitutions = @$policy_subst;
324 108 100       224 push @substitutions, [ $policy_target, $conf ]
325             if defined $policy_target;
326              
327 108 100       174 if ( @cfgs ) {
328 44         102 __build_list( $list, $config_args, \@substitutions, @cfgs );
329 44         69 next;
330             }
331              
332 64         131 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 2500 my $self = shift;
346              
347 7         12 @{ $self->{_list} };
  7         19  
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 8 my $self = shift;
358              
359 2 50       13 return unless UNIVERSAL::isa( $self->{_sections}, "ARRAY" );
360              
361 2         4 my @targets;
362 2         4 for my $section ( @{ $self->{_sections} } ) {
  2         11  
363             next unless UNIVERSAL::isa( $section, "HASH" ) &&
364 6 100 66     38 $section->{policy_target};
365 3         9 push @targets, $section->{policy_target};
366             }
367              
368 2         23 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 3452 my $self = shift;
379 5         6 my @sections;
380 5         7 for my $section ( @{ $self->{_sections} } ) {
  5         9  
381 14 100       37 if ( UNIVERSAL::isa( $section, 'ARRAY' ) ) {
    50          
382 8         12 push @sections, $section;
383             } elsif ( UNIVERSAL::isa( $section, 'HASH' ) ) {
384             push @sections, [
385             "/$section->{policy_target}/",
386 6         11 @{ $section->{args} },
  6         17  
387             ];
388             }
389             }
390 5         42 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         2 local *LOG;
429 1 50       29 if ( open LOG, "< $logfile" ) {
430 1         3 my $conf;
431             # A Configuration is done when we detect a new Configuration:
432             # or the phrase "Finished smoking $patch"
433 1         16 while ( ) {
434 17 100 66     64 s/^Configuration:\s*// || /^Finished smoking/ or next;
435 3 100       7 $conf and $conf_done{ $conf }++;
436 3         5 chomp; $conf = $_;
  3         7  
437             }
438 1         10 close LOG;
439             }
440 1 50       11 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 15 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 5185 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   11577 '""' => sub { $_[0]->[0] || "" },
485 13     13   2250 fallback => 1;
  13         1686  
  13         146  
486              
487 13     13   1025 use Text::ParseWords qw( quotewords );
  13         26  
  13         9815  
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   407 my $proto = shift;
525 244   33     865 my $class = ref $proto || $proto;
526              
527 244         642 my $self = bless [ undef, [ ], { } ], $class;
528              
529 244 50       827 @_ >= 1 and $self->args( shift );
530 244 100       540 @_ > 0 and $self->policy( @_ );
531              
532 244         597 $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   292 my $self = shift;
543              
544 244 50       422 if ( defined $_[0] ) {
545 244         551 $self->[0] = shift;
546 244         569 $self->_split_args;
547             }
548              
549 244         480 $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   4887 my $self = shift;
560              
561 80 100       118 if ( @_ ) {
562             my @substitutions = @_ == 1 && ref $_[0][0] eq 'ARRAY'
563 64 50 66     224 ? @{ $_[0] } : @_;
  0         0  
564 64         104 $self->[1] = \@substitutions;
565             }
566              
567 80         93 @{ $self->[1] };
  80         129  
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   346 my $self = shift;
578              
579 244         411 my $i = 0;
580             $self->[2] = {
581 244         613 map { ( $_ => $i++ ) } quotewords( '\s+', 1, $self->[0] )
  549         19423  
582             };
583             $self->[0] = join( " ", sort {
584             $self->[2]{ $a } <=> $self->[2]{ $b }
585 244   100     686 } 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   145 my $self = shift;
597              
598 74         81 my $ok = 1;
599 74   100     280 $ok &&= exists $self->[2]{ $_ } foreach @_;
600 74         274 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         2 my $ok = 0;
614 1   66     8 $ok ||= exists $self->[2]{ $_ } foreach @_;
615 1         4 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   26 my $self = shift;
630 17         22 my $args = shift;
631              
632             my $default_args = join "|", sort {
633 17         39 length($b) <=> length($a)
  0         0  
634             } quotewords( '\s+', 1, Test::Smoke::BuildCFG->config( 'dfopts' ) );
635              
636 26         57 my %copy = map { ( $_ => undef ) }
637 17         683 grep !/$default_args/ => keys %{ $self->[2] };
  17         103  
638 17         40 my @s_args = grep !/$default_args/ => quotewords( '\s+', 1, $args );
639 17         1196 my @left;
640 17         37 while ( my $option = pop @s_args ) {
641 26 50       42 if ( exists $copy{ $option } ) {
642 26         59 delete $copy{ $option };
643             } else {
644 0         0 push @left, $option;
645             }
646             }
647 17 50 33     98 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   240 my $self = shift;
659              
660 170         496 foreach my $arg ( @_ ) {
661 138 50       473 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     246 } 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   393 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     2 } 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