File Coverage

blib/lib/Distribution/Metadata.pm
Criterion Covered Total %
statement 44 331 13.2
branch 0 84 0.0
condition 0 42 0.0
subroutine 15 43 34.8
pod 14 14 100.0
total 73 514 14.2


line stmt bran cond sub pod time code
1             package Distribution::Metadata v1.0.0;
2 1     1   136831 use v5.24;
  1         4  
3 1     1   6 use warnings;
  1         2  
  1         74  
4 1     1   5 use experimental qw(lexical_subs signatures);
  1         2  
  1         9  
5              
6             our $TRIAL = 0;
7              
8 1     1   1706 use CPAN::DistnameInfo;
  1         1463  
  1         46  
9 1     1   600 use CPAN::Meta;
  1         39001  
  1         45  
10 1     1   11 use Config ();
  1         3  
  1         19  
11 1     1   5 use Cwd ();
  1         3  
  1         20  
12 1     1   692 use ExtUtils::Packlist;
  1         2193  
  1         47  
13 1     1   9 use File::Basename ();
  1         2  
  1         23  
14 1     1   6 use File::Find ();
  1         2  
  1         19  
15 1     1   4 use File::Spec;
  1         2  
  1         18  
16 1     1   973 use JSON ();
  1         18444  
  1         42  
17 1     1   658 use Module::Metadata;
  1         10885  
  1         70  
18              
19 1     1   9 use constant DEBUG => $ENV{PERL_DISTRIBUTION_METADATA_DEBUG};
  1         3  
  1         5426  
20              
21             my $SEP = qr{/|\\}; # path separater
22             my $ARCHNAME = $Config::Config{archname};
23              
24             our $CACHE;
25              
26 0     0 1   sub new_from_file ($class, $file, %option) {
  0            
  0            
  0            
  0            
27 0           $class->_new(%option, _module => {file => $file});
28             }
29              
30 0     0 1   sub new_from_module ($class, $module, %option) {
  0            
  0            
  0            
  0            
31 0           $class->_new(%option, _module => {name => $module});
32             }
33              
34 0     0     sub _new ($class, %option) {
  0            
  0            
  0            
35 0           my $module = $option{_module};
36 0   0       my $inc = $option{inc} || \@INC;
37 0           $inc = $class->_abs_path($inc);
38 0 0         $inc = $class->_fill_archlib($inc) if $option{fill_archlib};
39             my $metadata = $module->{file}
40             ? Module::Metadata->new_from_file($module->{file}, inc => $inc)
41 0 0         : Module::Metadata->new_from_module($module->{name}, inc => $inc);
42              
43 0           my $self = bless {}, $class;
44 0 0         return $self unless $metadata;
45              
46 0           $module->{file} = $metadata->filename;
47 0           $module->{name} = $metadata->name;
48 0           $module->{version} = $metadata->version;
49              
50 0           my ($packlist, $files) = $class->_find_packlist($module->{file}, $inc);
51 0 0         if ($packlist) {
52 0           $self->{packlist} = $packlist;
53 0           $self->{files} = $files;
54             } else {
55 0           return $self;
56             }
57              
58 0           my ($main_module, $lib) = $self->_guess_main_module($packlist);
59 0 0         if ($main_module) {
60 0           $self->{main_module} = $main_module;
61 0 0         if ($main_module eq "perl") {
62 0           $self->{main_module_version} = $^V;
63 0           $self->{main_module_file} = $^X;
64 0           $self->{dist} = "perl";
65 0           my $version = "" . $^V;
66 0           $version =~ s/v//;
67 0           $self->{distvname} = "perl-$version";
68 0           $self->{version} = $version;
69 0           return $self;
70             }
71             } else {
72 0           return $self;
73             }
74              
75 0           my $archlib = File::Spec->catdir($lib, $ARCHNAME);
76 0           my $main_metadata = Module::Metadata->new_from_module(
77             $main_module, inc => [$archlib, $lib]
78             );
79              
80 0           my ($find_module, $find_version);
81 0 0         if ($main_metadata) {
82 0           $self->{main_module_version} = $main_metadata->version;
83 0           $self->{main_module_file} = $main_metadata->filename;
84 0           $find_module = $main_metadata->name;
85 0           $find_version = $main_metadata->version;
86             } else {
87 0           $find_module = $module->{name};
88 0           $find_version = $module->{version};
89             }
90              
91 0           my ($meta_directory, $install_json, $install_json_hash, $mymeta_json) = $class->_find_meta(
92             $main_module, $find_module, $find_version,
93             File::Spec->catdir($archlib, ".meta")
94             );
95 0           $self->{meta_directory} = $meta_directory;
96 0           $self->{install_json} = $install_json;
97 0           $self->{install_json_hash} = $install_json_hash;
98 0           $self->{mymeta_json} = $mymeta_json;
99 0           $self;
100             }
101              
102 0     0     sub _guess_main_module ($self, $packlist) {
  0            
  0            
  0            
103 0           my @piece = File::Spec->splitdir( File::Basename::dirname($packlist) );
104 0 0         if ($piece[-1] eq $ARCHNAME) {
105 0           return ("perl", undef);
106             }
107              
108 0           my (@module, @lib);
109 0           for my $i ( 1 .. ($#piece-2) ) {
110 0 0 0       if ($piece[$i] eq $ARCHNAME && $piece[$i+1] eq "auto") {
111 0           @module = @piece[ ($i+2) .. $#piece ];
112 0           @lib = @piece[ 0 .. ($i-1) ];
113 0           last;
114             }
115             }
116 0 0         return unless @module;
117 0           return ( _fix_module_name( join("::", @module) ), File::Spec->catdir(@lib) );
118             }
119              
120             # ugly workaround for case insensitive filesystem
121             # eg: if you install 'Version::Next' module and later 'version' module,
122             # then version's packlist is located at Version/.packlist! (capital V!)
123             # Maybe there are a lot of others...
124             my @fix_module_name = qw(version Version::Next);
125 0     0     sub _fix_module_name ($module_name) {
  0            
  0            
126 0 0         if (my ($fix) = grep { $module_name =~ /^$_$/i } @fix_module_name) {
  0            
127 0           $fix;
128             } else {
129 0           $module_name;
130             }
131             }
132              
133 0     0     sub _fill_archlib ($class, $incs) {
  0            
  0            
  0            
134 0           my %incs = map { $_ => 1 } $incs->@*;
  0            
135 0           my @out;
136 0           for my $inc ($incs->@*) {
137 0           push @out, $inc;
138 0 0         next if $inc =~ /$ARCHNAME$/o;
139 0           my $archlib = File::Spec->catdir($inc, $ARCHNAME);
140 0 0 0       if (-d $archlib && !$incs{$archlib}) {
141 0           push @out, $archlib;
142             }
143             }
144 0           \@out;
145             }
146              
147             my $decode_install_json = sub ($file) {
148             my $content = do { open my $fh, "<", $file or next; local $/; <$fh> };
149             JSON::decode_json($content);
150             };
151 0     0     sub _decode_install_json ($class, $file, $dir) {
  0            
  0            
  0            
  0            
152 0 0         if ($CACHE) {
153 0   0       $CACHE->{install_json}{$dir}{$file} ||= $decode_install_json->($file);
154             } else {
155 0           $decode_install_json->($file);
156             }
157             }
158              
159 0     0     sub _find_meta ($class, $main_module, $module, $version, $dir) {
  0            
  0            
  0            
  0            
  0            
  0            
160 0 0         return unless -d $dir;
161              
162 0           my @install_json;
163 0 0 0       if ($CACHE and $CACHE->{install_json_collected}{$dir}) {
164 0           @install_json = keys %{$CACHE->{install_json}{$dir}};
  0            
165             } else {
166 0           @install_json = do {
167 0 0         opendir my $dh, $dir or die "opendir $dir: $!";
168 0           my @meta_dir = grep { !/^[.]{1,2}$/ } readdir $dh;
  0            
169 0           grep -f, map { File::Spec->catfile($dir, $_, "install.json") } @meta_dir;
  0            
170             };
171 0 0         if ($CACHE) {
172 0   0       $CACHE->{install_json}{$dir}{$_} ||= undef for @install_json;
173 0           $CACHE->{install_json_collected}{$dir}++;
174             }
175             }
176              
177             # to speed up, first try distribution which just $module =~ s/::/-/gr;
178 0           my $naive = do { my $dist = $main_module; $dist =~ s/::/-/g; $dist };
  0            
  0            
  0            
179             @install_json = (
180 0           (sort { $b cmp $a } grep { /^$naive/ } @install_json),
  0            
181 0           (sort { $b cmp $a } grep { !/^$naive/ } @install_json),
  0            
  0            
182             );
183              
184 0           my ($meta_directory, $install_json, $install_json_hash, $mymeta_json);
185             INSTALL_JSON_LOOP:
186 0           for my $file (@install_json) {
187 0           my $hash = $class->_decode_install_json($file, $dir);
188              
189             # name VS target ? When LWP, name is LWP, and target is LWP::UserAgent
190             # So name is main_module!
191 0   0       my $name = $hash->{name} || "";
192 0 0         next if $name ne $main_module;
193 0   0       my $provides = $hash->{provides} || +{};
194 0           for my $provide (sort keys $provides->%*) {
195 0 0 0       if ($provide eq $module
      0        
196             && ($provides->{$provide}{version} || "") eq $version) {
197 0           $meta_directory = File::Basename::dirname($file);
198 0           $install_json = $file;
199 0           $mymeta_json = File::Spec->catfile($meta_directory, "MYMETA.json");
200 0           $install_json_hash = $hash;
201 0           last INSTALL_JSON_LOOP;
202             }
203             }
204 0           DEBUG and warn "==> failed to find $module $version in $file\n";
205             }
206              
207 0           return ($meta_directory, $install_json, $install_json_hash, $mymeta_json);
208             }
209              
210 0     0     sub _naive_packlist ($class, $module_file, $inc) {
  0            
  0            
  0            
  0            
211 0           for my $i ($inc->@*) {
212 0 0         if (my ($path) = $module_file =~ /$i $SEP (.+)\.pm /x) {
213 0 0         my $archlib = $i =~ /$ARCHNAME$/o ? $i : File::Spec->catdir($i, $ARCHNAME);
214 0           my $try = File::Spec->catfile( $archlib, "auto", $path, ".packlist" );
215 0 0         return $try if -f $try;
216             }
217             }
218 0           return;
219             }
220              
221             # It happens that .packlist files are symlink path.
222             # eg: OSX,
223             # in .packlist: /var/folders/...
224             # but /var/folders/.. is a symlink to /private/var/folders
225             my $extract_files = sub ($packlist) {
226             [
227             map { Cwd::abs_path($_) } grep { -f }
228             sort keys %{ ExtUtils::Packlist->new($packlist) || +{} }
229             ];
230             };
231 0     0     sub _extract_files ($class, $packlist) {
  0            
  0            
  0            
232 0 0         if ($CACHE) {
233 0   0       $CACHE->{packlist}{$packlist} ||= $extract_files->($packlist);
234             } else {
235 0           $extract_files->($packlist);
236             }
237             }
238              
239 0     0     sub _core_packlist ($self, $inc) {
  0            
  0            
  0            
240 0           for my $dir (grep -d, $inc->@*) {
241 0 0         opendir my $dh, $dir or die "Cannot open dir $dir: $!\n";
242 0           my ($packlist) = map { File::Spec->catfile($dir, $_) } grep {$_ eq ".packlist"} readdir $dh;
  0            
  0            
243 0 0         return $packlist if $packlist;
244             }
245 0           return;
246             }
247              
248 0     0     sub _find_packlist ($class, $module_file, $inc) {
  0            
  0            
  0            
  0            
249              
250 0 0 0       if ($CACHE and my $core_packlist = $CACHE->{core_packlist}) {
251 0           my $files = $class->_extract_files($core_packlist);
252 0 0         if (grep {$module_file eq $_} $files->@*) {
  0            
253 0           return ($core_packlist, $files);
254             }
255             }
256              
257             # to speed up, first try packlist which is naively guessed by $module_file
258 0 0         if (my $naive_packlist = $class->_naive_packlist($module_file, $inc)) {
259 0           my $files = $class->_extract_files($naive_packlist);
260 0 0         if ( grep { $module_file eq $_ } $files->@* ) {
  0            
261 0           DEBUG and warn "-> naively found packlist: $module_file\n";
262 0           return ($naive_packlist, $files);
263             }
264             }
265              
266 0           my @packlists;
267 0 0 0       if ($CACHE and $CACHE->{packlist_collected}) {
268 0           @packlists = keys $CACHE->{packlist}->%*;
269             } else {
270 0 0         if (my $core_packlist = $class->_core_packlist($inc)) {
271 0           push @packlists, $core_packlist;
272 0 0         $CACHE->{core_packlist} = $core_packlist if $CACHE;
273             }
274 0     0     File::Find::find sub (@) {
  0            
275 0 0         return unless -f;
276 0 0         return unless $_ eq ".packlist";
277 0           push @packlists, $File::Find::name;
278 0           }, grep -d, map { File::Spec->catdir($_, "auto") } @{$class->_fill_archlib($inc)};
  0            
  0            
279 0 0         if ($CACHE) {
280 0   0       $CACHE->{packlist}{$_} ||= undef for @packlists;
281 0           $CACHE->{packlist_collected}++;
282             }
283             }
284              
285 0           for my $try (@packlists) {
286 0           my $files = $class->_extract_files($try);
287 0 0         if (grep { $module_file eq $_ } $files->@*) {
  0            
288 0           return ($try, $files);
289             }
290             }
291 0           return;
292             }
293              
294 0     0     sub _abs_path ($class, $dirs) {
  0            
  0            
  0            
295 0           my @out;
296 0           for my $dir (grep -d, $dirs->@*) {
297 0           my $abs = Cwd::abs_path($dir);
298 0           $abs =~ s/$SEP+$//;
299 0 0         push @out, $abs if $abs;
300             }
301 0           \@out;
302             }
303              
304 0     0 1   sub packlist ($self) { $self->{packlist} }
  0            
  0            
  0            
305 0     0 1   sub meta_directory ($self) { $self->{meta_directory} }
  0            
  0            
  0            
306 0     0 1   sub install_json ($self) { $self->{install_json} }
  0            
  0            
  0            
307 0     0 1   sub mymeta_json ($self) { $self->{mymeta_json} }
  0            
  0            
  0            
308 0     0 1   sub main_module ($self) { $self->{main_module} }
  0            
  0            
  0            
309 0     0 1   sub main_module_version ($self) { $self->{main_module_version} }
  0            
  0            
  0            
310 0     0 1   sub main_module_file ($self) { $self->{main_module_file} }
  0            
  0            
  0            
311 0     0 1   sub files ($self) { $self->{files} }
  0            
  0            
  0            
312 0     0 1   sub install_json_hash ($self) { $self->{install_json_hash} }
  0            
  0            
  0            
313              
314 0     0 1   sub mymeta_json_hash ($self) {
  0            
  0            
315 0 0         return unless my $mymeta_json = $self->mymeta_json;
316 0   0       $self->{mymeta_json_hash} ||= CPAN::Meta->load_file($mymeta_json)->as_struct;
317             }
318              
319 0     0     sub _distnameinfo ($self) {
  0            
  0            
320 0 0         return unless my $hash = $self->install_json_hash;
321 0   0       $self->{_distnameinfo} ||= CPAN::DistnameInfo->new( $hash->{pathname} );
322             }
323              
324             for my $attr (qw(dist version cpanid distvname pathname)) {
325 1     1   26 no strict 'refs';
  1         23  
  1         256  
326 0     0     *$attr = sub ($self) {
  0            
  0            
327 0 0         return $self->{$attr} if exists $self->{$attr}; # for 'perl' distribution
328 0 0         return unless $self->_distnameinfo;
329 0           $self->_distnameinfo->$attr;
330             };
331             }
332              
333             # alias
334 0     0 1   sub name ($self) { $self->dist }
  0            
  0            
  0            
335 0     0 1   sub author ($self) { $self->cpanid }
  0            
  0            
  0            
336              
337             1;
338              
339             __END__