File Coverage

blib/lib/CPANPLUS/Dist/Debora/Package.pm
Criterion Covered Total %
statement 519 591 87.8
branch 115 192 59.9
condition 35 65 53.8
subroutine 104 114 91.2
pod 36 36 100.0
total 809 998 81.0


line stmt bran cond sub pod time code
1             package CPANPLUS::Dist::Debora::Package;
2              
3             # SPDX-License-Identifier: Artistic-1.0-Perl OR GPL-1.0-or-later
4              
5 8     8   31931 use 5.016;
  8         37  
6 8     8   45 use warnings;
  8         16  
  8         195  
7 8     8   40 use utf8;
  8         16  
  8         35  
8              
9             our $VERSION = '0.011';
10              
11 8     8   400 use Carp qw(croak);
  8         17  
  8         481  
12 8     8   60 use Config;
  8         24  
  8         322  
13 8     8   4423 use CPAN::Meta;
  8         246903  
  8         314  
14 8     8   2515 use English qw(-no_match_vars);
  8         10868  
  8         56  
15 8     8   3162 use File::Basename qw(dirname);
  8         21  
  8         746  
16 8     8   54 use File::Path qw(remove_tree);
  8         19  
  8         513  
17 8     8   2007 use File::Spec::Functions qw(catdir catfile splitdir splitpath);
  8         3585  
  8         618  
18 8     8   3122 use File::Temp qw(tempdir);
  8         47683  
  8         463  
19 8     8   3968 use Net::Domain qw(hostfqdn);
  8         59360  
  8         641  
20 8     8   3508 use Software::LicenseUtils 0.103014;
  8         478728  
  8         305  
21              
22 8     8   3280 use CPANPLUS::Dist::Debora::License;
  8         32  
  8         354  
23 8     8   4239 use CPANPLUS::Dist::Debora::Pod;
  8         42  
  8         420  
24 8         60199 use CPANPLUS::Dist::Debora::Util qw(
25             parse_version
26             module_is_distributed_with_perl
27             decode_utf8
28             can_run
29             run
30             find_most_recent_mtime
31             find_shared_objects
32 8     8   63 );
  8         22  
33              
34             # Map some distribution names to special package names.
35             my %PACKAGE_NAME_FOR = (
36             'ack' => 'ack',
37             'App-Licensecheck' => 'licensecheck',
38             'App-perlbrew' => 'perlbrew',
39             'TermReadKey' => 'perl-Term-ReadKey',
40             );
41              
42             # Version quirks.
43             my %VERSION_FOR = ('BioPerl-Run' => sub { parse_version($_[0])->normal });
44              
45             # Modules with summaries and descriptions.
46             my %POD_FOR = (
47             'ack' => 'ack',
48             'App-Licensecheck' => 'licensecheck',
49             'TermReadKey' => 'ReadKey.pm.PL',
50             'TimeDate' => 'Date::Parse',
51             'YAML-LibYAML' => 'YAML::XS',
52             );
53              
54             # Common modules whose license might not be guessed.
55             my %LICENSE_FOR = (
56             'AnyEvent' => 'Artistic-1.0-Perl OR GPL-1.0-or-later',
57             'Apache-Htpasswd' => 'Artistic-1.0-Perl OR GPL-1.0-or-later',
58             'Cache-Cache' => 'Artistic-1.0-Perl OR GPL-1.0-or-later',
59             'Canary-Stability' => 'Artistic-1.0-Perl OR GPL-1.0-or-later',
60             'CGI-FormBuilder' => 'Artistic-1.0-Perl OR GPL-1.0-or-later',
61             'CGI-FormBuilder-Source-Perl' => 'Artistic-1.0-Perl OR GPL-1.0-or-later',
62             'Crypt-CBC' => 'Artistic-1.0-Perl OR GPL-1.0-or-later',
63             'Encode-Detect' => 'MPL-1.1',
64             'Guard' => 'Artistic-1.0-Perl OR GPL-1.0-or-later',
65             'Iterator' => 'Artistic-1.0-Perl OR GPL-1.0-or-later',
66             'Iterator-Util' => 'Artistic-1.0-Perl OR GPL-1.0-or-later',
67             'Lingua-EN-Words2Nums' => 'Artistic-1.0-Perl OR GPL-1.0-or-later',
68             'Lingua-Stem-Snowball-Da' => 'GPL-2.0-only',
69             'Mozilla-CA' => 'MPL-2.0',
70             'Socket6' => 'BSD',
71             'String-ShellQuote' => 'Artistic-1.0-Perl OR GPL-1.0-or-later',
72             'Sub-Delete' => 'Artistic-1.0-Perl OR GPL-1.0-or-later',
73             'XML-Writer' => 'CC0-1.0',
74             );
75              
76             sub new {
77 6     6 1 50328 my ($class, %attrs) = @_;
78              
79 6         214 my $attrs = $class->_buildargs(%attrs);
80              
81 6         188 return bless $attrs, $class;
82             }
83              
84             sub _buildargs {
85 6     6   94 my ($class, %attrs) = @_;
86              
87 6 50       88 if (!exists $attrs{module}) {
88 0         0 croak 'No module';
89             }
90              
91 6         208 my $builddir = $attrs{builddir} = $attrs{module}->status->extract;
92 6 50       1621 if (!defined $builddir) {
93 0         0 croak 'No builddir';
94             }
95              
96 6 100       99 if (!exists $attrs{installdirs}) {
97 4         45 $attrs{installdirs} = 'vendor';
98             }
99              
100 6         52 my $installdirs = $attrs{installdirs};
101 6 50 66     108 if ($installdirs ne 'vendor' && $installdirs ne 'site') {
102 0         0 croak "installdirs is neither 'vendor' nor 'site': '$installdirs'";
103             }
104              
105 6 50       89 if (!exists $attrs{build_number}) {
106 6         293 $attrs{build_number} = 1;
107             }
108              
109 6         52 my $build_number = $attrs{build_number};
110 6 50       177 if ($build_number !~ m{\A [1-9]\d* \z}xms) {
111 0         0 croak "build_number is not a positive integer: '$build_number'";
112             }
113              
114 6         143 $attrs{last_modification} = find_most_recent_mtime($builddir);
115              
116 6         66 return \%attrs;
117             }
118              
119             sub _read {
120 159     159   539 my ($self, $name, $default) = @_;
121              
122 159 100       556 if (!exists $self->{$name}) {
123 82         231 $self->{$name} = $default->();
124             }
125              
126 159         3950 return $self->{$name};
127             }
128              
129             sub module {
130 65     65 1 116 my $self = shift;
131              
132 65         597 return $self->{module};
133             }
134              
135             sub installdirs {
136 8     8 1 5439 my $self = shift;
137              
138 8         55 return $self->{installdirs};
139             }
140              
141             sub sourcefile {
142 2     2 1 5 my $self = shift;
143              
144             my $sourcefile
145 2     1   25 = $self->_read('sourcefile', sub { $self->module->status->fetch });
  1         8  
146              
147 2         43 return $sourcefile;
148             }
149              
150             sub sourcedir {
151 1     1 1 4 my $self = shift;
152              
153             my $sourcedir
154 1     1   16 = $self->_read('sourcedir', sub { dirname($self->sourcefile) });
  1         3  
155              
156 1         8 return $sourcedir;
157             }
158              
159             sub last_modification {
160 19     19 1 40 my $self = shift;
161              
162 19         98 return $self->{last_modification};
163             }
164              
165             sub builddir {
166 24     24 1 54 my $self = shift;
167              
168 24         405 return $self->{builddir};
169             }
170              
171             sub outputdir {
172 8     8 1 23 my $self = shift;
173              
174 8     2   118 my $outputdir = $self->_read('outputdir', sub { dirname($self->builddir) });
  2         15  
175              
176 8         115 return $outputdir;
177             }
178              
179             sub stagingdir {
180 19     19 1 7428 my $self = shift;
181              
182             my $stagingdir = $self->_read('stagingdir',
183 19     6   221 sub { tempdir('stagingXXXX', DIR => $self->outputdir) });
  6         47  
184              
185 19         258 return $stagingdir;
186             }
187              
188             sub shared_objects {
189 4     4 1 1013 my $self = shift;
190              
191             my $shared_objects
192 4     3   40 = $self->_read('shared_objects', sub { $self->_get_shared_objects });
  3         51  
193              
194 4         27 return $shared_objects;
195             }
196              
197             sub is_noarch {
198 7     7 1 856 my $self = shift;
199              
200 7     3   66 my $is_noarch = $self->_read('is_noarch', sub { $self->_get_is_noarch });
  3         44  
201              
202 7         49 return $is_noarch;
203             }
204              
205             sub module_name {
206 3     3 1 363 my $self = shift;
207              
208             my $module_name
209 3     3   52 = $self->_read('module_name', sub { $self->_get_module_name });
  3         29  
210              
211 3         23 return $module_name;
212             }
213              
214             sub dist_name {
215 50     50 1 490 my $self = shift;
216              
217 50         159 return $self->module->package_name;
218             }
219              
220             sub name {
221 11     11 1 7856 my $self = shift;
222              
223             my $name = $self->_read('name',
224 11     4   110 sub { $self->_normalize_name($self->dist_name) });
  4         46  
225              
226 11         172 return $name;
227             }
228              
229             sub dist_version {
230 4     4 1 18 my $self = shift;
231              
232 4         13 return $self->module->package_version;
233             }
234              
235             sub version {
236 10     10 1 893 my $self = shift;
237              
238             my $version = $self->_read('version',
239 10     4   91 sub { $self->_normalize_version($self->dist_version) });
  4         60  
240              
241 10         114 return $version;
242             }
243              
244             sub build_number {
245 5     5 1 198 my $self = shift;
246              
247 5         101 return $self->{build_number};
248             }
249              
250             sub author {
251 1     1 1 4 my $self = shift;
252              
253 1     1   13 my $author = $self->_read('author', sub { $self->module->author->author });
  1         6  
254              
255 1         9 return $author;
256             }
257              
258             sub packager {
259 5     5 1 1146 my $self = shift;
260              
261 5     3   63 my $packager = $self->_read('packager', sub { $self->_get_packager });
  3         53  
262              
263 5         45 return $packager;
264             }
265              
266             sub vendor {
267 3     3 1 220 my $self = shift;
268              
269 3     3   85 my $vendor = $self->_read('vendor', sub { $self->_get_vendor });
  3         48  
270              
271 3         20 return $vendor;
272             }
273              
274             sub url {
275 3     3 1 428 my $self = shift;
276              
277             # A link to MetaCPAN is more useful than the homepage.
278             my $url = $self->_read('url',
279 3     3   39 sub { 'https://metacpan.org/dist/' . $self->dist_name });
  3         13  
280              
281 3         28 return $url;
282             }
283              
284             sub summary {
285 3     3 1 725 my $self = shift;
286              
287 3     3   49 my $summary = $self->_read('summary', sub { $self->_get_summary });
  3         40  
288              
289 3         28 return $summary;
290             }
291              
292             sub description {
293 3     3 1 817 my $self = shift;
294              
295             my $description
296 3     3   49 = $self->_read('description', sub { $self->_get_description });
  3         36  
297              
298 3         68 return $description;
299             }
300              
301             sub dependencies {
302 3     3 1 44 my $self = shift;
303              
304             my $dependencies
305 3     3   51 = $self->_read('dependencies', sub { $self->_get_dependencies });
  3         39  
306              
307 3         17 return $dependencies;
308             }
309              
310             sub copyrights {
311 5     5 1 2588 my $self = shift;
312              
313 5     3   53 my $copyrights = $self->_read('copyrights', sub { $self->_get_copyrights });
  3         38  
314              
315 5         43 return $copyrights;
316             }
317              
318             sub licenses {
319 5     5 1 898 my $self = shift;
320              
321 5     3   54 my $licenses = $self->_read('licenses', sub { $self->_get_licenses });
  3         35  
322              
323 5         22 return $licenses;
324             }
325              
326             sub license {
327 3     3 1 1280 my $self = shift;
328              
329 3     3   44 my $license = $self->_read('license', sub { $self->_get_license });
  3         44  
330              
331 3         22 return $license;
332             }
333              
334             sub files {
335 7     7 1 397 my $self = shift;
336              
337             my $files = $self->_read('files',
338 7     5   129 sub { [@{$self->_get_docfiles}, @{$self->_get_stagingfiles}] });
  5         20  
  5         90  
  5         97  
339              
340 7         50 return $files;
341             }
342              
343             sub files_by_type {
344 4     4 1 254 my ($self, $type) = @_;
345              
346 4         9 my @files = map { $_->{name} } grep { $_->{type} eq $type } @{$self->files};
  6         16  
  56         107  
  4         35  
347              
348 4         28 return \@files;
349             }
350              
351             sub mb_opt {
352 1     1 1 4 my $self = shift;
353              
354 1         4 my $installdirs = $self->installdirs;
355              
356 1         8 return << "END_MB_OPT";
357             --installdirs $installdirs
358             END_MB_OPT
359             }
360              
361             sub mm_opt {
362 1     1 1 5 my $self = shift;
363              
364 1         9 my $installdirs = $self->installdirs;
365              
366 1         17 return << "END_MM_OPT";
367             INSTALLDIRS=$installdirs
368             END_MM_OPT
369             }
370              
371             sub sanitize_stagingdir {
372 4     4 1 7504 my $self = shift;
373              
374 4         13 my $fail_count = 0;
375              
376             my $finddepth = sub {
377 64     64   170 my $dir = shift;
378              
379 64 50       1734 opendir my $dh, $dir
380             or croak "Could not traverse '$dir': $OS_ERROR";
381             ENTRY:
382 64         1167 while (defined(my $entry = readdir $dh)) {
383 204 100 100     1273 next ENTRY if $entry eq q{.} || $entry eq q{..};
384              
385 76         429 my $path = catfile($dir, $entry);
386              
387             # Skip symbolic links.
388 76 50       961 next ENTRY if -l $path;
389              
390             # Process sub directories first.
391 76 100       837 if (-d $path) {
392 60         329 __SUB__->($path);
393             }
394              
395             # Sanitize the permissions.
396 76         1029 my @stat = lstat $path;
397 76 50       253 if (!@stat) {
398 0         0 error("Could not stat '$path': $OS_ERROR");
399 0         0 next ENTRY;
400             }
401              
402 76         175 my $old_mode = $stat[2] & oct '0777';
403 76         131 my $new_mode = ($old_mode & oct '0755') | oct '0200';
404 76 50       172 if ($old_mode != $new_mode) {
405 0 0       0 if (!chmod $new_mode, $path) {
406 0         0 error("Could not chmod '$path': $OS_ERROR");
407 0         0 ++$fail_count;
408             }
409             }
410              
411             # Remove empty directories and some files.
412 76 100       808 if (-d $path) {
413 60         2045 rmdir $path;
414             }
415             else {
416 16 100 100     242 if ( $entry eq 'perllocal.pod'
      33        
      66        
417             || $entry eq '.packlist'
418             || ($entry =~ m{[.]bs \z}xms && -z $path))
419             {
420 8 50       467 if (!unlink $path) {
421 0         0 error("Could not remove '$path': $OS_ERROR");
422 0         0 ++$fail_count;
423             }
424             }
425             }
426             }
427 64         648 closedir $dh;
428              
429 64         308 return;
430 4         70 };
431 4         31 $finddepth->($self->stagingdir);
432              
433 4         126 return $fail_count == 0;
434             }
435              
436             sub remove_stagingdir {
437 0     0 1 0 my $self = shift;
438              
439 0         0 my $stagingdir = $self->{stagingdir};
440 0 0       0 if (defined $stagingdir) {
441 0         0 remove_tree($stagingdir);
442 0         0 delete $self->{stagingdir};
443             }
444              
445 0         0 return 1;
446             }
447              
448             sub rpm_cmd {
449 0     0 1 0 my $self = shift;
450              
451 0         0 state $rpm_cmd = can_run('rpm');
452              
453 0         0 return $rpm_cmd;
454             }
455              
456             sub rpm_eval {
457 9     9 1 46 my ($self, $expr) = @_;
458              
459 9         24 my $string = q{};
460              
461 9         36 my $rpm_cmd = $self->rpm_cmd;
462 9 50       785 if ($rpm_cmd) {
463 0         0 my @eval_cmd = ($rpm_cmd, '--eval', $expr);
464 0         0 my $output = q{};
465 0 0       0 if (run(command => \@eval_cmd, buffer => \$output)) {
466 0         0 chomp $output;
467 0   0     0 $string = eval { decode_utf8($output) } // q{};
  0         0  
468             }
469             }
470              
471 9         69 return $string;
472             }
473              
474             sub sudo_cmd {
475 0     0 1 0 my $self = shift;
476              
477 0         0 my $module = $self->module;
478 0         0 my $backend = $module->parent;
479 0         0 my $config = $backend->configure_object;
480 0   0     0 my $sudo_cmd = $config->get_program('sudo') // 'sudo';
481              
482 0         0 return $sudo_cmd;
483             }
484              
485             sub DESTROY {
486 6     6   21098 my $self = shift;
487              
488 6         26 my $stagingdir = $self->{stagingdir};
489 6 50       40 if (defined $stagingdir) {
490             ##no critic (ErrorHandling::RequireCheckingReturnValueOfEval)
491 6         16 eval { remove_tree($stagingdir) };
  6         10963  
492             }
493              
494 6         57 return;
495             }
496              
497             ## no critic (Subroutines::ProhibitExcessComplexity)
498              
499             sub _normalize_name {
500 13     13   290 my ($self, $dist_name) = @_;
501              
502 13         23 my $name;
503 13 50       45 if (exists $PACKAGE_NAME_FOR{$dist_name}) {
504 0         0 $name = $PACKAGE_NAME_FOR{$dist_name};
505             }
506             else {
507 13         27 $name = $dist_name;
508              
509             # Prepend "perl-" unless the name starts with "perl-".
510 13 100       55 if ($name !~ m{\A perl-}xms) {
511 12         42 $name = 'perl-' . $name;
512             }
513             }
514              
515 13         71 return $name;
516             }
517              
518             sub _normalize_version {
519 18     18   308 my ($self, $dist_version) = @_;
520              
521 18         56 my $dist_name = $self->dist_name;
522              
523 18   100     1170 my $version = $dist_version // 0;
524              
525 18 50       70 if (exists $VERSION_FOR{$dist_name}) {
526 0         0 $version = $VERSION_FOR{$dist_name}->($version);
527             }
528              
529 18         81 $version =~ s{\A v}{}xms; # Strip "v".
530              
531 18         97 return $version;
532             }
533              
534             sub _unnumify_version {
535 0     0   0 my ($self, $dist_version) = @_;
536              
537 0         0 my $version
538             = $self->_normalize_version(parse_version($dist_version)->normal);
539              
540 0         0 return $version;
541             }
542              
543             sub _get_meta {
544 3     3   9 my $self = shift;
545              
546 3         6 my $meta;
547              
548 3         19 my $builddir = $self->builddir;
549             META:
550 3         20 for (qw(META.json META.yml)) {
551 3         26 my $metafile = catfile($builddir, $_);
552 3 50       74 if (-f $metafile) {
553 3         17 $meta = eval { CPAN::Meta->load_file($metafile) };
  3         97  
554 3 50       185448 last META if defined $meta;
555             }
556             }
557              
558 3         56 return $meta;
559             }
560              
561             sub _meta {
562 11     11   26 my $self = shift;
563              
564 11     3   84 my $meta = $self->_read('meta', sub { $self->_get_meta });
  3         106  
565              
566 11         58 return $meta;
567             }
568              
569             sub _get_pod {
570 3     3   12 my $self = shift;
571              
572 3         13 my $builddir = $self->builddir;
573              
574 3   33     14 my $name = $POD_FOR{$self->dist_name} // $self->module_name;
575 3         15 my @dirs = map { catdir($builddir, $_) } qw(blib/lib blib/bin lib bin .);
  15         94  
576 3         91 my $pod = CPANPLUS::Dist::Debora::Pod->find($name, @dirs, $builddir);
577              
578 3         40 return $pod;
579             }
580              
581             sub _pod {
582 11     11   30 my $self = shift;
583              
584 11     3   73 return $self->_read('pod', sub { $self->_get_pod });
  3         28  
585             }
586              
587             sub _get_shared_objects {
588 3     3   9 my $self = shift;
589              
590 3         8 my $stagingdir = $self->{stagingdir};
591 3 50       14 if (!defined $stagingdir) {
592 0         0 croak 'Call shared_objects after the distribution has been built';
593             }
594              
595 3         18 my $shared_objects = find_shared_objects($stagingdir);
596              
597 3         16 return $shared_objects;
598             }
599              
600             sub _get_is_noarch {
601 3     3   9 my $self = shift;
602              
603             # Searching for source code files isn't reliable as there are Perl
604             # distributions with C files in example directories.
605             #
606             # Instead, we look for an "auto" directory and search for shared objects
607             # after the distribution has been installed in the staging directory.
608              
609 3         12 my $stagingdir = $self->{stagingdir};
610 3 50       13 if (!defined $stagingdir) {
611 0         0 croak 'Call is_arch after the distribution has been built';
612             }
613              
614 3         7 my $is_noarch = @{$self->shared_objects} == 0;
  3         37  
615 3 50       13 if ($is_noarch) {
616 3         37 my $installdirs = $self->installdirs;
617 3         109 my $archdir = $Config{"install${installdirs}arch"};
618 3 50       18 if (defined $archdir) {
619 3         40 my $autodir = catdir($stagingdir, $archdir, 'auto');
620 3 50       98 if (-d $autodir) {
621 0         0 $is_noarch = 0;
622             }
623             }
624             }
625              
626 3         34 return $is_noarch;
627             }
628              
629             sub _get_module_name {
630 3     3   12 my $self = shift;
631              
632 3         12 my $name = $self->module->module;
633              
634             # Is there a .pm file with the distribution's name?
635 3         253 my @module = split qr{-}xms, $self->dist_name;
636 3         217 my $filename = catfile($self->builddir, 'lib', @module) . '.pm';
637 3 50       136 if (-f $filename) {
638 3         23 $name = join q{::}, @module;
639             }
640              
641 3         17 return $name;
642             }
643              
644             sub _get_packager {
645 3     3   10 my $self = shift;
646              
647 3         21 my $name;
648             my $email;
649              
650 3         18 my $EMAIL = qr{ \A
651             (?:([^<]*) \h+)? # name
652             ]+@[^>]+) >? # email
653             }xms;
654              
655 3 50       27 if ($self->rpm_eval('%{?packager}') =~ $EMAIL) {
656 0         0 $name = $1;
657 0         0 $email = $2;
658             }
659              
660 3 50       19 if (!$name) {
661             NAME:
662 3         24 for my $key (qw(DEBFULLNAME NAME GITLAB_USER_NAME)) {
663 7 100       41 if ($ENV{$key}) {
664 1         10 $name = eval { decode_utf8($ENV{$key}) };
  1         15  
665 1 50       101 last NAME if $name;
666             }
667             }
668             }
669              
670 3         19 for my $key (qw(DEBEMAIL EMAIL GITLAB_USER_EMAIL)) {
671 9 100       35 if ($ENV{$key}) {
672 1         3 my $value = eval { decode_utf8($ENV{$key}) };
  1         5  
673 1 50 33     78 if ($value && $value =~ $EMAIL) {
674 1 50       5 if (!$name) {
675 0         0 $name = $1;
676             }
677 1 50       4 if (!$email) {
678 1         4 $email = $2;
679             }
680             }
681             }
682             }
683              
684 3         16 my $user;
685              
686 3         13 my @pw = eval { getpwuid $UID };
  3         520  
687 3 50       21 if (@pw) {
688 3         8 $user = eval { decode_utf8($pw[0]) };
  3         48  
689              
690 3 100       244 if (!$name) {
691 2         6 my $gecos = eval { decode_utf8($pw[6]) };
  2         23  
692 2 50       97 if ($gecos) {
693 2         42 ($name) = split qr{,}xms, $gecos;
694             }
695             }
696             }
697              
698 3 50       21 if (!$user) {
699             USER:
700 0         0 for my $key (qw(LOGNAME USER USERNAME)) {
701 0 0       0 if ($ENV{$key}) {
702 0         0 $user = eval { decode_utf8($ENV{$key}) };
  0         0  
703 0 0       0 last USER if $user;
704             }
705             }
706             }
707              
708 3 50       13 if (!$user) {
709 0         0 $user = 'nobody';
710             }
711              
712 3 50       13 if (!$name) {
713 0         0 $name = $user;
714             }
715              
716 3 100       11 if (!$email) {
717 2         34 my $host = hostfqdn;
718 2         1053 $host =~ s{[.]$}{}xms;
719 2         10 $email = $user . q{@} . $host;
720             }
721              
722 3         33 return "$name <$email>";
723             }
724              
725             sub _get_vendor {
726 3     3   10 my $self = shift;
727              
728 3         21 my $vendor = $self->rpm_eval('%{?vendor}');
729 3 50 33     18 if (!$vendor || $vendor =~ m{%}xms) {
730 3         18 $vendor = 'CPANPLUS';
731             }
732              
733 3         14 return $vendor;
734             }
735              
736             sub _get_summary_from_meta {
737 4     4   16 my $self = shift;
738              
739 4         10 my $summary;
740              
741 4         40 my $meta = $self->_meta;
742 4 50       25 if (defined $meta) {
743 4         21 my $text = $meta->{abstract};
744 4 50 33     60 if ($text && $text !~ m{unknown}xmsi) {
745 4         13 $summary = $text;
746             }
747             }
748              
749 4         33 return $summary;
750             }
751              
752             sub _get_summary_from_pod {
753 1     1   3 my $self = shift;
754              
755 1         3 my $summary;
756              
757 1         15 my $pod = $self->_pod;
758 1 50       11 if (defined $pod) {
759 1         8 $summary = $pod->summary;
760             }
761              
762 1         17 return $summary;
763             }
764              
765             sub _get_summary {
766 3     3   12 my $self = shift;
767              
768 3   33     23 my $summary = $self->_get_summary_from_meta // $self->_get_summary_from_pod
      50        
769             // 'Module for the Perl programming language';
770 3         15 $summary =~ s{\v+}{ }xmsg; # Replace newlines.
771 3         11 $summary =~ s{[.]+ \z}{}xms; # Remove trailing dots.
772 3         24 $summary =~ s{\A (?:An? | The) \h+}{}xmsi; # Remove leading articles.
773              
774 3         26 return ucfirst $summary;
775             }
776              
777             sub _get_description {
778 3     3   11 my $self = shift;
779              
780 3         13 my $description = q{};
781              
782 3         23 my $pod = $self->_pod;
783 3 50       19 if (defined $pod) {
784 3         18 $description = $pod->description;
785             }
786              
787 3 50       16 if (!$description) {
788 0         0 my $module_name = $self->module_name;
789 0         0 $description
790             = "$module_name is a module for the Perl programming language.";
791             }
792              
793 3         15 return $description;
794             }
795              
796             sub _get_requires {
797 3     3   12 my $self = shift;
798              
799 3         24 my %requires;
800              
801 3   50     15 my $prereqs = $self->module->status->prereqs // {};
802              
803 3         473 my $meta = $self->_meta;
804 3 50 33     73 if (defined $meta && ref $meta->{prereqs} eq 'HASH') {
805 3   50     23 my $meta_runtime = $meta->{prereqs}->{runtime} // {};
806 3   50     14 my $meta_requires = $meta_runtime->{requires} // {};
807              
808             # We can only have dependencies that are in the prereqs.
809 12         55 %requires = map { $_ => $meta_requires->{$_} }
810 3         9 grep { exists $prereqs->{$_} } keys %{$meta_requires};
  105         190  
  3         41  
811             }
812             else {
813 0         0 %requires = %{$prereqs};
  0         0  
814             }
815              
816 3         37 return \%requires;
817             }
818              
819             sub _get_dependencies {
820 3     3   12 my $self = shift;
821              
822 3         7 my %requires = %{$self->_get_requires};
  3         38  
823 3         21 my $backend = $self->module->parent;
824              
825             # Sometimes versions are numified and cannot be compared with stringified
826             # versions.
827             my %version_for = (
828 0     0   0 'Algorithm-Diff' => sub {0},
829 0     0   0 'BioPerl' => sub { $self->_unnumify_version($_[0]) },
830 0     0   0 'Catalyst' => sub {0},
831 0     0   0 'Catalyst-Runtime' => sub {0},
832 0     0   0 'CGI-Simple' => sub {0},
833 0     0   0 'DBD-Pg' => sub { $self->_unnumify_version($_[0]) },
834 3         407 );
835              
836 3         9 my %dependency;
837              
838             MODULE:
839 3         34 for my $module_name (keys %requires) {
840 12         412 my $module = $backend->module_tree($module_name);
841 12 50       1864 next MODULE if !$module;
842              
843             # Task::Weaken is only a build dependency.
844 12 50       38 next MODULE if $module_name eq 'Task::Weaken';
845              
846             # Ignore dependencies on modules for VMS and Windows.
847 12 50       66 next MODULE if $module_name =~ m{\A (?:VMS | Win32)}xms;
848              
849 12         90 my $dist_name = $module->package_name;
850 12         759 my $version = parse_version($requires{$module_name});
851              
852 12   33     75 my $is_core
853             = $module_name eq 'perl'
854             || module_is_distributed_with_perl($module_name, $version)
855             || $module->package_is_perl_core;
856              
857 12 50       899 if (exists $version_for{$dist_name}) {
858 0         0 $version = $version_for{$dist_name}->($version);
859             }
860              
861 12 50 33     56 if (!exists $dependency{$module_name}
862             || $dependency{$module_name}->{version} < $version)
863             {
864 12         110 $dependency{$module_name} = {
865             dist_name => $dist_name,
866             version => $version,
867             is_module => $module_name ne 'perl',
868             is_core => $is_core,
869             };
870             }
871             }
872              
873             my @dependencies = map { {
874             module_name => $_,
875             dist_name => $dependency{$_}->{dist_name},
876             package_name => $self->_normalize_name($dependency{$_}->{dist_name}),
877             version => $self->_normalize_version($dependency{$_}->{version}),
878             is_module => $dependency{$_}->{is_module},
879             is_core => $dependency{$_}->{is_core},
880 3         149 } } sort { uc $a cmp uc $b } keys %dependency;
  12         60  
  15         45  
881              
882 3         49 return \@dependencies;
883             }
884              
885             sub _get_copyrights {
886 3     3   16 my $self = shift;
887              
888 3         7 my @copyrights;
889              
890 3         22 my $pod = $self->_pod;
891 3 50       25 if (defined $pod) {
892 3         16 push @copyrights, @{$pod->copyrights};
  3         21  
893             }
894              
895 3 50       16 if (!@copyrights) {
896 0         0 my $author = $self->author;
897 0 0       0 my $holder
898             = $author ? "$author and possibly others" : 'unknown authors';
899 0         0 my $time = $self->last_modification;
900 0         0 my $year = (gmtime $time)[5] + 1900;
901 0         0 push @copyrights, {year => $year, holder => $holder};
902             }
903              
904 3         30 return \@copyrights;
905             }
906              
907             sub _get_licenses_from_meta {
908 4     4   1222 my $self = shift;
909              
910 4         11 my @licenses;
911              
912 4         15 my $meta = $self->_meta;
913 4 50       20 if (defined $meta) {
914 4         23 my $keys = $meta->{license};
915 4 50       14 if (defined $keys) {
916 4 50       17 if (!ref $keys) {
917 0         0 $keys = [$keys];
918             }
919 4         12 my %ignore_key = map { $_ => 1 } qw(open_source unrestricted);
  8         36  
920 4         13 for my $key (grep { !exists $ignore_key{$_} } @{$keys}) {
  4         22  
  4         13  
921             my @license
922 4         79 = Software::LicenseUtils->guess_license_from_meta_key($key,
923             2);
924 4 50       156 if (@license) {
925 4         22 push @licenses, @license;
926             }
927             }
928             }
929             }
930              
931 4         14 return \@licenses;
932             }
933              
934             sub _get_licenses_from_pod {
935 4     4   841 my $self = shift;
936              
937 4         10 my @licenses;
938              
939 4         13 my $pod = $self->_pod;
940 4 50       34 if (defined $pod) {
941             my @license
942 4         21 = Software::LicenseUtils->guess_license_from_pod($pod->text);
943 4 50       4263 if (@license) {
944 4         15 push @licenses, @license;
945             }
946             }
947              
948 4         17 return \@licenses;
949             }
950              
951             sub _get_licenses {
952 3     3   8 my $self = shift;
953              
954 3         6 my %copyright = %{$self->copyrights->[-1]};
  3         19  
955              
956             my $get_license = sub {
957 3     3   30 my $spdx_expression = shift;
958              
959 3         7 my $license = eval {
960 3         46 Software::LicenseUtils->new_from_spdx_expression({
961             spdx_expression => $spdx_expression,
962             %copyright
963             });
964             };
965 3 50       246 if (!$license) {
966 0         0 $license = CPANPLUS::Dist::Debora::License->new({
967             package => $self,
968             %copyright
969             });
970             }
971              
972 3         16 return $license;
973 3         41 };
974              
975             my %unique_guesses
976 6         166 = map { $_->name => $_ } @{$self->_get_licenses_from_meta},
  3         42  
977 3         11 @{$self->_get_licenses_from_pod};
  3         25  
978              
979             # Add the copyright year and author to the guessed licenses.
980             my @licenses
981 3         35 = map { $get_license->($_->spdx_expression) } values %unique_guesses;
  3         32  
982 3 50       13 if (!@licenses) {
983 0         0 push @licenses, $get_license->($LICENSE_FOR{$self->dist_name});
984             }
985              
986             my @sorted_licenses
987 3         13 = sort { $a->spdx_expression cmp $b->spdx_expression } @licenses;
  0         0  
988              
989 3         35 return \@sorted_licenses;
990             }
991              
992             sub _get_license {
993 3     3   10 my $self = shift;
994              
995 3         8 my @names = map { $_->spdx_expression } @{$self->licenses};
  3         16  
  3         14  
996             my $license = join ' AND ',
997 3 50 33     19 map { @names > 1 && m{\b OR \b}xmsi ? "($_)" : $_ } @names;
  3         27  
998              
999 3         15 return $license;
1000             }
1001              
1002             sub _get_docfiles {
1003 5     5   23 my $self = shift;
1004              
1005 5         57 my $LICENSE = qr{ \A (?:
1006             COPYING(?:[.](?:LESSER|LIB))?
1007             | COPYRIGHT
1008             | LICEN[CS]E
1009             ) (?:[.](?:md|mkdn|pod|txt))? \z
1010             }xmsi;
1011              
1012 5         28 my $CHANGELOG = qr{ \A (?:
1013             Change(?:s|Log)
1014             ) (?:[.](?:md|mkdn|pod|txt))? \z
1015             }xmsi;
1016              
1017 5         26 my $DOC = qr{ \A (?:
1018             AUTHORS
1019             | BUGS
1020             | CONTRIBUTING
1021             | CREDITS
1022             | FAQ
1023             | NEWS
1024             | README
1025             | THANKS
1026             | TODO
1027             ) (?:[.](?:md|mkdn|pod|txt))? \z
1028             }xmsi;
1029              
1030 5         42 my %regex_for = (
1031             'license' => $LICENSE,
1032             'changelog' => $CHANGELOG,
1033             'doc' => $DOC,
1034             );
1035              
1036 5         19 my @files;
1037              
1038             my $fix_permissions = sub {
1039 5     5   14 my $dir = shift;
1040              
1041 5         85 chmod oct '0755', $dir;
1042              
1043 5 50       173 opendir my $dh, $dir
1044             or croak "Could not traverse '$dir': $OS_ERROR";
1045             ENTRY:
1046 5         123 while (defined(my $entry = readdir $dh)) {
1047 15 100 100     151 next ENTRY if $entry eq q{.} || $entry eq q{..};
1048              
1049 5         95 my $path = catfile($dir, $entry);
1050              
1051             # Skip symbolic links.
1052 5 50       89 next ENTRY if -l $path;
1053              
1054 5 50       77 if (-d $path) {
1055 0         0 __SUB__->($path);
1056             }
1057             else {
1058 5         121 chmod oct '0644', $path;
1059             }
1060             }
1061 5         74 closedir $dh;
1062              
1063 5         45 return;
1064 5         81 };
1065              
1066             my $find = sub {
1067 5     5   15 my $dir = shift;
1068              
1069 5 50       226 opendir my $dh, $dir
1070             or croak "Could not traverse '$dir': $OS_ERROR";
1071             ENTRY:
1072 5         188 while (defined(my $entry = readdir $dh)) {
1073 115 100 100     509 next ENTRY if $entry eq q{.} || $entry eq q{..};
1074              
1075 105         513 my $path = catfile($dir, $entry);
1076              
1077             # Skip symbolic links.
1078 105 50       1487 next ENTRY if -l $path;
1079              
1080 105 100       2015 if (-d $path) {
    100          
1081 25 100       164 if ($entry eq 'examples') {
1082 5         26 $fix_permissions->($path);
1083 5         102 my $file = {name => $entry, type => 'doc'};
1084 5         33 push @files, $file;
1085             }
1086             }
1087             elsif (-s $path) {
1088             TYPE:
1089 70         255 for my $type (keys %regex_for) {
1090 191 100       1191 if ($entry =~ $regex_for{$type}) {
1091 20         450 chmod oct '0644', $path;
1092 20         124 my $file = {name => $entry, type => $type};
1093 20         59 push @files, $file;
1094 20         116 last TYPE;
1095             }
1096             }
1097             }
1098             }
1099 5         71 closedir $dh;
1100              
1101 5         34 return;
1102 5         90 };
1103 5         59 $find->($self->builddir);
1104              
1105 5         73 my @sorted_files = sort { $a->{name} cmp $b->{name} } @files;
  40         133  
1106              
1107 5         125 return \@sorted_files;
1108             }
1109              
1110             sub _get_excludedirs {
1111 5     5   15 my $self = shift;
1112              
1113             # A list of directories that are provided by Perl and must not be removed
1114             # by packages.
1115              
1116 5         87 my @vars = qw(
1117             installsitearch
1118             installsitebin
1119             installsitelib
1120             installsiteman1dir
1121             installsiteman3dir
1122             installsitescript
1123             installvendorarch
1124             installvendorbin
1125             installvendorlib
1126             installvendorman1dir
1127             installvendorman3dir
1128             installvendorscript
1129             );
1130              
1131 5         23 my %excludedirs = map { $_ => 1 } qw(/etc);
  5         83  
1132             VAR:
1133 5         33 for my $var (@vars) {
1134 60         2235 my $value = $Config{$var};
1135 60 100       242 next VAR if !$value;
1136              
1137 20 100       96 if ($var =~ m{arch \z}xms) {
1138 5         43 $value = catdir($value, 'auto');
1139             }
1140              
1141 20         172 my ($volume, $path) = File::Spec->splitpath($value, 1);
1142              
1143 20         92 my ($dir, @dirs) = splitdir($path);
1144 20         192 while (@dirs) {
1145 100         342 $dir = catdir($dir, shift @dirs);
1146 100         309 $excludedirs{$dir} = 1;
1147             }
1148             }
1149              
1150 5         30 return \%excludedirs;
1151             }
1152              
1153             sub _get_stagingfiles {
1154 5     5   27 my $self = shift;
1155              
1156 5         32 my $stagingdir = $self->stagingdir;
1157 5         20 my $stagingdir_length = length $stagingdir;
1158 5         62 my $excludedirs = $self->_get_excludedirs;
1159              
1160 5         14 my @files;
1161              
1162             my $find = sub {
1163 40     40   95 my $dir = shift;
1164              
1165 40 50       1028 opendir my $dh, $dir
1166             or croak "Could not traverse '$dir': $OS_ERROR";
1167             ENTRY:
1168 40         683 while (defined(my $entry = readdir $dh)) {
1169 127 100 100     743 next ENTRY if $entry eq q{.} || $entry eq q{..};
1170              
1171 47         273 my $path = catfile($dir, $entry);
1172              
1173 47         149 my $name = substr $path, $stagingdir_length;
1174 47 100       1080 my $type = -l $path ? 'link' : -d $path ? 'dir' : 'file';
    50          
1175 47 100       154 if ($type eq 'file') {
1176 12         59 my ($volume, $dirs, $file) = splitpath($name);
1177 12         235 my %subdir = map { $_ => 1 } splitdir($dirs);
  59         694  
1178 12 50       166 if (exists $subdir{etc}) {
    100          
1179 0         0 $type = 'config';
1180             }
1181             elsif (exists $subdir{man}) {
1182 5         37 $type = 'man';
1183             }
1184             }
1185              
1186 47 100       134 if (!exists $excludedirs->{$name}) {
1187 43         149 my $file = {name => $name, type => $type};
1188 43         127 push @files, $file;
1189             }
1190              
1191             # Skip symbolic links.
1192 47 50       567 next ENTRY if -l $path;
1193              
1194 47 100       576 if (-d $path) {
1195 35         215 __SUB__->($path);
1196             }
1197             }
1198 40         476 closedir $dh;
1199              
1200 40         310 return;
1201 5         94 };
1202 5         30 $find->($stagingdir);
1203              
1204 5         30 my @sorted_files = sort { $a->{name} cmp $b->{name} } @files;
  74         136  
1205              
1206 5         123 return \@sorted_files;
1207             }
1208              
1209             1;
1210             __END__