File Coverage

blib/lib/Dist/CheckConflicts.pm
Criterion Covered Total %
statement 119 120 99.1
branch 34 38 89.4
condition 16 20 80.0
subroutine 17 17 100.0
pod 4 4 100.0
total 190 199 95.4


line stmt bran cond sub pod time code
1             package Dist::CheckConflicts;
2             BEGIN {
3 7     7   10586 $Dist::CheckConflicts::AUTHORITY = 'cpan:DOY';
4             }
5             $Dist::CheckConflicts::VERSION = '0.11';
6 7     7   57 use strict;
  7         17  
  7         189  
7 7     7   35 use warnings;
  7         12  
  7         148  
8 7     7   186 use 5.006;
  7         24  
  7         272  
9             # ABSTRACT: declare version conflicts for your dist
10              
11 7     7   38 use base 'Exporter';
  7         22  
  7         938  
12             our @EXPORT = our @EXPORT_OK = (
13             qw(conflicts check_conflicts calculate_conflicts dist)
14             );
15              
16 7     7   39 use Carp;
  7         16  
  7         626  
17 7     7   6006 use Module::Runtime 0.009 'module_notional_filename', 'require_module';
  7         11788  
  7         53  
18              
19              
20             my %CONFLICTS;
21             my %HAS_CONFLICTS;
22             my %DISTS;
23              
24             sub import {
25 29     29   24104 my $pkg = shift;
26 29         73 my $for = caller;
27              
28 29         40 my ($conflicts, $alsos, $dist);
29 29         696 ($conflicts, @_) = _strip_opt('-conflicts' => @_);
30 29         686 ($alsos, @_) = _strip_opt('-also' => @_);
31 29         68 ($dist, @_) = _strip_opt('-dist' => @_);
32              
33 29 50       39 my %conflicts = %{ $conflicts || {} };
  29         190  
34 29 100       56 for my $also (@{ $alsos || [] }) {
  29         646  
35 13 100       20 eval { require_module($also) } or next;
  13         50  
36 12 100       572 if (!exists $CONFLICTS{$also}) {
37 1         2 $also .= '::Conflicts';
38 1 50       2 eval { require_module($also) } or next;
  1         4  
39             }
40 12 50       44 if (!exists $CONFLICTS{$also}) {
41 0         0 next;
42             }
43 12         54 my %also_confs = $also->conflicts;
44 12         52 for my $also_conf (keys %also_confs) {
45 20 100 100     145 $conflicts{$also_conf} = $also_confs{$also_conf}
46             if !exists $conflicts{$also_conf}
47             || $conflicts{$also_conf} lt $also_confs{$also_conf};
48             }
49             }
50              
51 29         466 $CONFLICTS{$for} = \%conflicts;
52 29   66     122 $DISTS{$for} = $dist || $for;
53              
54 29 100       79 if (grep { $_ eq ':runtime' } @_) {
  2         11  
55 2         7 for my $conflict (keys %conflicts) {
56 16   50     57 $HAS_CONFLICTS{$conflict} ||= [];
57 16         18 push @{ $HAS_CONFLICTS{$conflict} }, $for;
  16         35  
58             }
59              
60             # warn for already loaded things...
61 2         8 for my $conflict (keys %conflicts) {
62 16 100       185 if (exists $INC{module_notional_filename($conflict)}) {
63 8         145 _check_version([$for], $conflict);
64             }
65             }
66              
67             # and warn for subsequently loaded things...
68 21   66     98 @INC = grep {
69 2         22 !(ref($_) eq 'ARRAY' && @$_ > 1 && $_->[1] == \%CONFLICTS)
70             } @INC;
71             unshift @INC, [
72             sub {
73 24     24   2180 my ($sub, $file) = @_;
74              
75 24         104 (my $mod = $file) =~ s{\.pm$}{};
76 24         65 $mod =~ s{/}{::}g;
77 24 50       96 return unless $mod =~ /[\w:]+/;
78              
79 24 100       6882 return unless defined $HAS_CONFLICTS{$mod};
80              
81             {
82 8         9 local $HAS_CONFLICTS{$mod};
  8         16  
83 8         60 require $file;
84             }
85              
86 8         584 _check_version($HAS_CONFLICTS{$mod}, $mod);
87              
88             # the previous require already handled it
89 8         14 my $called;
90             return sub {
91 16 100       139 return 0 if $called;
92 8         12 $_ = "1;";
93 8         8 $called = 1;
94 8         44 return 1;
95 8         287 };
96             },
97 2         17 \%CONFLICTS, # arbitrary but unique, see above
98             ];
99             }
100              
101 29         3402 $pkg->export_to_level(1, @_);
102             }
103              
104             sub _strip_opt {
105 87     87   184 my ($opt, @args) = @_;
106              
107 87         96 my $val;
108 87         210 for my $idx ( 0 .. $#args - 1 ) {
109 80 100 66     407 if (defined $args[$idx] && $args[$idx] eq $opt) {
110 50         104 $val = (splice @args, $idx, 2)[1];
111 50         89 last;
112             }
113             }
114              
115 87         279 return ( $val, @args );
116             }
117              
118             sub _check_version {
119 16     16   28 my ($fors, $mod) = @_;
120              
121 16         30 for my $for (@$fors) {
122 16         30 my $conflict_ver = $CONFLICTS{$for}{$mod};
123 16         17 my $version = do {
124 7     7   6546 no strict 'refs';
  7         19  
  7         4047  
125 16         28 ${ ${ $mod . '::' }{VERSION} };
  16         17  
  16         77  
126             };
127              
128 16 100       148 if ($version le $conflict_ver) {
129 8         75 warn <
130             Conflict detected for $DISTS{$for}:
131             $mod is version $version, but must be greater than version $conflict_ver
132             EOF
133 8         55 return;
134             }
135             }
136             }
137              
138              
139             sub conflicts {
140 38     38 1 107 my $package = shift;
141 38         46 return %{ $CONFLICTS{ $package } };
  38         446  
142             }
143              
144              
145             sub dist {
146 13     13 1 1829 my $package = shift;
147 13         38 return $DISTS{ $package };
148             }
149              
150              
151             sub check_conflicts {
152 9     9 1 2657 my $package = shift;
153 9         27 my $dist = $package->dist;
154 9         26 my @conflicts = $package->calculate_conflicts;
155 9 100       36 return unless @conflicts;
156              
157 5         17 my $err = "Conflicts detected for $dist:\n";
158 5         10 for my $conflict (@conflicts) {
159 9         51 $err .= " $conflict->{package} is version "
160             . "$conflict->{installed}, but must be greater than version "
161             . "$conflict->{required}\n";
162             }
163 5         42 die $err;
164             }
165              
166              
167             sub calculate_conflicts {
168 19     19 1 168 my $package = shift;
169 19         51 my %conflicts = $package->conflicts;
170              
171 19         37 my @ret;
172              
173              
174             CONFLICT:
175 19         46 for my $conflict (keys %conflicts) {
176 55         59 my $success = do {
177 55     1   288 local $SIG{__WARN__} = sub {};
  1         609  
178 55         502 eval { require_module($conflict) };
  55         322  
179             };
180 55         8859 my $error = $@;
181 55         127 my $file = module_notional_filename($conflict);
182 55 100 100     1166 next if not $success and $error =~ /Can't locate \Q$file\E in \@INC/;
183              
184 51 100       115 warn "Warning: $conflict did not compile" if not $success;
185 51 100       665 my $installed = $success ? $conflict->VERSION : 'unknown';
186 51 100 100     476 push @ret, {
187             package => $conflict,
188             installed => $installed,
189             required => $conflicts{$conflict},
190             } if not $success or $installed le $conflicts{$conflict};
191             }
192              
193 19         144 return sort { $a->{package} cmp $b->{package} } @ret;
  8         84  
194             }
195              
196              
197             1;
198              
199             __END__