File Coverage

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