File Coverage

blib/lib/Module/Mask/Deps.pm
Criterion Covered Total %
statement 105 107 98.1
branch 32 34 94.1
condition 20 21 95.2
subroutine 22 22 100.0
pod 4 4 100.0
total 183 188 97.3


line stmt bran cond sub pod time code
1             package Module::Mask::Deps;
2              
3 3     3   154784 use strict;
  3         8  
  3         115  
4 3     3   17 use warnings;
  3         6  
  3         191  
5              
6             our $VERSION = '0.07';
7              
8             =head1 NAME
9              
10             Module::Mask::Deps - Mask modules not listed as dependencies
11              
12             =head1 SYNOPSIS
13              
14             Cause your test suite to blow up if you require a module not listed as a
15             requirement in C or C.
16              
17             perl Build.PL
18             HARNESS_PERL_SWITCHES=-MModule::Mask::Deps ./Build test
19              
20             Or use directly in your testing code:
21              
22             use Module::Mask::Deps;
23              
24             BEGIN {
25             # die if an unlisted module is used
26             use_ok('My::Module');
27             }
28              
29             # turn off masking (at compile time)
30             no Module::Mask::Deps;
31              
32             # .. or at run-time
33             unimport Module::Mask::Deps;
34              
35             Or use lexically:
36              
37             require Module::Mask::Deps;
38              
39             {
40             my $mask = new Module::Mask::Deps;
41              
42             # Non-dependencies masked until end-of-scope.
43              
44             # ...
45             }
46              
47             require Arbitrary::Module;
48              
49             =head1 DESCRIPTION
50              
51             This module aims to help module developers keep track of their dependencies by
52             only allowing modules to be loaded if they are in core or are listed as
53             dependencies.
54              
55             It uses L and L to build its list of
56             declared dependant modules.
57              
58             =cut
59              
60 3     3   13336 use Module::CoreList;
  3         123786  
  3         45  
61 3     3   5277 use Module::Mask;
  3         13693  
  3         23  
62 3     3   265 use Parse::CPAN::Meta;
  3         6825  
  3         148  
63              
64             # Need to load these early in case we mask them later
65 3     3   37 use CPAN::Meta::YAML;
  3         22859  
  3         233  
66 3     3   35 use JSON::PP;
  3         19113  
  3         275  
67              
68 3         3282 use File::Spec::Functions qw(
69             file_name_is_absolute
70             updir
71             splitdir
72             abs2rel
73 3     3   23 );
  3         6  
74              
75             our @ISA = qw( Module::Mask::Inverted );
76              
77             =head1 METHODS
78              
79             =head2 import
80              
81             use Module::Mask::Deps;
82              
83             import Module::Mask::Deps;
84              
85             Causes a L object to be created as
86             C<$Module::Mask::Deps::Mask>. This means that when called with C the mask
87             object is is in scope at compile time and therefore should affect all
88             subsequent C and C statements in the program.
89              
90             =cut
91              
92             our $Mask;
93              
94             sub import {
95 5     5   20968 my $class = shift;
96              
97 5 100       25 if ($Mask) {
98 1         5 $Mask->set_mask;
99             }
100             else {
101 4         31 $Mask = $class->new;
102             }
103             }
104              
105             =head2 unimport
106              
107             unimport Module::Mask::Deps
108              
109             no Module::Mask::Deps;
110              
111             Stops the mask from working until import is called again. See clear_mask in
112             L
113              
114             Note that C occurs at compile time and is not lexical
115             in the same way as C and are.
116              
117             =cut
118              
119             sub unimport {
120 2 100   2   3657 $Mask->clear_mask if $Mask;
121             }
122              
123             =head2 new
124              
125             $obj = $class->new()
126              
127             Returns a new C object. See L for details
128             about how modules are masked.
129              
130             =cut
131              
132             sub new {
133 4     4 1 12 my $class = shift;
134              
135 4         22 my @deps = $class->get_deps;
136              
137 4         176 return $class->SUPER::new(@deps);
138             }
139              
140             =head2 set_mask
141              
142             $obj = $obj->set_mask()
143              
144             Overloaded from L to place the mask object after any relative
145             paths at the beginning of @INC.
146              
147             Typically, in a testing environment, local paths are unshifted into @INC by
148             C, C or command-line switches. We don't want the mask to
149             affect those paths.
150              
151             Also, relative paths passed to require will not be masked.
152              
153             # Will check @INC but won't be masked
154             require 't/my_script.pl';
155              
156             # Won't even check @INC
157             require './t/my_script.pl';
158              
159             =cut
160              
161             sub set_mask {
162 5     5 1 33105 my $self = shift;
163              
164 5         40 $self->SUPER::set_mask();
165             # now the mask should be at the start of @INC
166              
167             # This is less code, but it's not as clear.
168             # Might even be less efficient.
169             # for (my $i = 1; $self->_rel_path($INC[$i]); $i++) {
170             # unshift @INC, splice @INC, $i, 1;
171             # }
172              
173             # count how many relative paths follow the mask object
174 5         234 my $count = 0;
175 5         34 for my $entry (@INC[1 .. $#INC]) {
176 17 100       47 if ($self->_rel_path($entry)) {
177 12         561 $count++;
178             }
179             else {
180 5         721 last;
181             }
182             }
183              
184             # move relative entries in front of the mask object
185 5         28 unshift @INC, splice @INC, 1, $count;
186              
187 5         15 return $self;
188             }
189              
190             sub _rel_path {
191 17     17   29 my ($self, $entry) = @_;
192            
193 17   100     64 return !file_name_is_absolute($entry)
194             || (splitdir(abs2rel($entry)))[0] ne updir;
195             }
196              
197             # prevent sub-dependencies from being masked
198             sub Module::Mask::Deps::INC {
199 12     12 1 123176 my ($self, $module) = @_;
200              
201 12 100       56 if ($self->is_masked($module)) {
202 9         296 my ($call_pack, $call_file) = caller;
203              
204 9 100       32 if ($self->_is_listed($call_pack)) {
    100          
205             # we've explicitly whitelisted the calling package,
206             # don't mask its dependencies
207              
208             # also add this module to the whitelist
209 4         167 $self->mask_modules($module);
210              
211 4         758 return;
212             }
213             elsif (-f $module) {
214             # $module must be a local, relative path.
215             # Absolute paths don't check @INC
216             # It will be loaded as long as . is in @INC
217 1         772 return;
218             }
219             else {
220             # Maybe we're being called from a package defined inside a module
221             # file
222 4         844 my %inc_lookup = reverse %INC;
223              
224             # This won't work unless the module is loaded from the filesystem
225 4         28 my $call_mod = $inc_lookup{$call_file};
226              
227 4 100 100     40 if ($call_mod and $self->_is_listed($call_mod)) {
228             # Add the sub-package to the whitelist so we don't need to
229             # re-check next time
230 2         64 $self->mask_modules($call_pack, $module);
231              
232 2         573 return;
233             }
234             }
235             }
236              
237 5         172 return $self->SUPER::INC($module);
238             }
239              
240             =head2 get_deps
241              
242             @deps = $class->get_deps()
243              
244             Returns current dependencies as defined in either C or C,
245             checked in that order. This is used internally by import and new, so there's
246             no need to call it directly.
247              
248             It returns all explicitly defined dependencies, plus all core dependencies for
249             the appropriate version of Perl, i.e. either the minimum version specified in
250             the meta file or the currently running version.
251              
252             The sections of the metadata checked for dependencies are:
253              
254             =over
255              
256             =item requires
257              
258             =item test_requires
259              
260             =item build_requires
261              
262             =back
263              
264             =cut
265              
266             sub get_deps {
267 6     6 1 2044 my $class = shift;
268              
269 6         10 my @errors;
270              
271 6         24 for my $file ($class->_meta_files) {
272             # Ignore errors reading the file
273 9 100       15 if (my $meta = eval { Parse::CPAN::Meta->load_file($file) }) {
  9 50       86  
274 6         30554 my %deps = $class->_meta_reqs($meta);
275              
276 6   66     47 my $perl_version = delete $deps{perl} || $];
277              
278 6         43 return $class->_merge_core($perl_version, keys %deps);
279             }
280             elsif ($@) {
281 3         298 push @errors, $@;
282             }
283             }
284              
285 0         0 local $" = "\n";
286 0         0 die "$class: Couldn't find dependencies\n@errors\n";
287             }
288              
289             sub _meta_files {
290 6     6   41 return qw( META.json META.yml );
291             }
292              
293             # Extract requirements from metadata.
294             # There are two formats for this, try all combinations and return as a hash
295             sub _meta_reqs {
296 6     6   13 my ($class, $meta) = @_;
297              
298 6         106 my @top_keys = grep /(?:_|^)requires$/, keys %$meta;
299              
300             return (
301 9         36 ( map %$_, @$meta{ @top_keys } ),
302 6 100       58 ( map %{ $_->{requires} }, values %{ $meta->{prereqs} || {} } ),
  6         72  
303             );
304             }
305              
306             # convenience function to return unique deps for a given perl version and
307             # dependency list
308             sub _merge_core {
309 6     6   22 my ($class, $version, @deps) = @_;
310 6         26 my @core = $class->_get_core($version);
311 6         86 my %seen;
312              
313 6         17 return grep { !$seen{$_}++ } (@core, @deps);
  1915         4776  
314             }
315              
316             # Find core modules for the given perl version
317             sub _get_core {
318 165     165   107617 my $class = shift;
319 165         297 my $perl_version = shift;
320 165         229 my @core;
321              
322 165         438 @core = $class->_core_for_version($perl_version);
323              
324 165 100       1358217 return @core if @core;
325              
326             # Nothing found,
327             # Maybe $perl_version needs reformatting..
328              
329 43 100       110 if (my $clean = $class->_clean_version($perl_version)) {
330 42         101 @core = $class->_core_for_version($clean);
331             }
332              
333 43 100       132310 return @core if @core;
334              
335             # still nothing..
336              
337 5         33 die "$class: Couldn't find core modules for perl $perl_version\n";
338             }
339              
340             # wrap %Module::CoreList::version
341             sub _core_for_version {
342 207     207   363 my $class = shift;
343 207         291 my $perl_version = shift;
344              
345 3     3   20 use Carp qw( confess );
  3         8  
  3         1034  
346 207 50       542 confess "undef perl version" unless defined $perl_version;
347              
348 207 100       591 if (exists $Module::CoreList::version{$perl_version}) {
349 160         209 return keys %{$Module::CoreList::version{$perl_version}};
  160         1051  
350             }
351             else {
352 47         156 return;
353             }
354             }
355              
356             # try to transform perl version numbers into the type used in Module::CoreList
357             sub _clean_version {
358 43     43   78 my ($class, $version) = @_;
359 43         93 $version =~ tr/0-9._//dc;
360 43         220 my ($major, @minors) = split(/[._]/, $version);
361              
362             # we don't want trailing zeros
363 43   100     341 pop @minors while @minors && $minors[-1] == 0;
364              
365 43         85 @minors = map { sprintf('%03d', $_) } @minors;
  61         315  
366              
367 43 100 100     406 if (@minors > 1 && $major == 5 && $minors[0] >= 3 && $minors[0] < 6) {
      100        
      100        
368             # Between 5.3 and 5.6, the second minor version is 2 digits
369             # 5.3.7 => 5.00307
370             # 5.5.3 => 5.00503
371              
372 9         74 $minors[1] =~ s/^0//;
373             }
374              
375 43         83 local $" = '';
376              
377 43 100       272 return @minors ? "$major.@minors" : $major;
378             }
379              
380             1;
381              
382             __END__