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   31424 use 5.016;
  8         39  
6 8     8   42 use warnings;
  8         17  
  8         211  
7 8     8   41 use utf8;
  8         15  
  8         42  
8              
9             our $VERSION = '0.009';
10              
11 8     8   405 use Carp qw(croak);
  8         25  
  8         434  
12 8     8   60 use Config;
  8         24  
  8         374  
13 8     8   4376 use CPAN::Meta;
  8         244041  
  8         325  
14 8     8   2573 use English qw(-no_match_vars);
  8         10756  
  8         56  
15 8     8   3140 use File::Basename qw(dirname);
  8         19  
  8         781  
16 8     8   61 use File::Path qw(remove_tree);
  8         18  
  8         535  
17 8     8   1956 use File::Spec::Functions qw(catdir catfile splitdir splitpath);
  8         3364  
  8         597  
18 8     8   3207 use File::Temp qw(tempdir);
  8         46973  
  8         506  
19 8     8   3985 use Net::Domain qw(hostfqdn);
  8         59991  
  8         598  
20 8     8   3431 use Software::LicenseUtils 0.103014;
  8         480173  
  8         404  
21              
22 8     8   3328 use CPANPLUS::Dist::Debora::License;
  8         34  
  8         332  
23 8     8   4228 use CPANPLUS::Dist::Debora::Pod;
  8         38  
  8         439  
24 8         57865 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   74 );
  8         20  
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 58736 my ($class, %attrs) = @_;
78              
79 6         198 my $attrs = $class->_buildargs(%attrs);
80              
81 6         210 return bless $attrs, $class;
82             }
83              
84             sub _buildargs {
85 6     6   76 my ($class, %attrs) = @_;
86              
87 6 50       122 if (!exists $attrs{module}) {
88 0         0 croak 'No module';
89             }
90              
91 6         231 my $builddir = $attrs{builddir} = $attrs{module}->status->extract;
92 6 50       1450 if (!defined $builddir) {
93 0         0 croak 'No builddir';
94             }
95              
96 6 100       77 if (!exists $attrs{installdirs}) {
97 4         46 $attrs{installdirs} = 'vendor';
98             }
99              
100 6         49 my $installdirs = $attrs{installdirs};
101 6 50 66     103 if ($installdirs ne 'vendor' && $installdirs ne 'site') {
102 0         0 croak "installdirs is neither 'vendor' nor 'site': '$installdirs'";
103             }
104              
105 6 50       56 if (!exists $attrs{build_number}) {
106 6         228 $attrs{build_number} = 1;
107             }
108              
109 6         41 my $build_number = $attrs{build_number};
110 6 50       163 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         123 $attrs{last_modification} = find_most_recent_mtime($builddir);
115              
116 6         72 return \%attrs;
117             }
118              
119             sub _read {
120 156     156   574 my ($self, $name, $default) = @_;
121              
122 156 100       545 if (!exists $self->{$name}) {
123 82         208 $self->{$name} = $default->();
124             }
125              
126 156         4049 return $self->{$name};
127             }
128              
129             sub module {
130 65     65 1 121 my $self = shift;
131              
132 65         683 return $self->{module};
133             }
134              
135             sub installdirs {
136 8     8 1 5492 my $self = shift;
137              
138 8         50 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         39 return $sourcefile;
148             }
149              
150             sub sourcedir {
151 1     1 1 4 my $self = shift;
152              
153             my $sourcedir
154 1     1   13 = $self->_read('sourcedir', sub { dirname($self->sourcefile) });
  1         3  
155              
156 1         7 return $sourcedir;
157             }
158              
159             sub last_modification {
160 19     19 1 42 my $self = shift;
161              
162 19         108 return $self->{last_modification};
163             }
164              
165             sub builddir {
166 24     24 1 80 my $self = shift;
167              
168 24         426 return $self->{builddir};
169             }
170              
171             sub outputdir {
172 8     8 1 29 my $self = shift;
173              
174 8     2   142 my $outputdir = $self->_read('outputdir', sub { dirname($self->builddir) });
  2         19  
175              
176 8         127 return $outputdir;
177             }
178              
179             sub stagingdir {
180 19     19 1 8272 my $self = shift;
181              
182             my $stagingdir = $self->_read('stagingdir',
183 19     6   244 sub { tempdir('stagingXXXX', DIR => $self->outputdir) });
  6         49  
184              
185 19         197 return $stagingdir;
186             }
187              
188             sub shared_objects {
189 3     3 1 9 my $self = shift;
190              
191             my $shared_objects
192 3     3   36 = $self->_read('shared_objects', sub { $self->_get_shared_objects });
  3         21  
193              
194 3         19 return $shared_objects;
195             }
196              
197             sub is_noarch {
198 7     7 1 883 my $self = shift;
199              
200 7     3   67 my $is_noarch = $self->_read('is_noarch', sub { $self->_get_is_noarch });
  3         38  
201              
202 7         69 return $is_noarch;
203             }
204              
205             sub module_name {
206 3     3 1 403 my $self = shift;
207              
208             my $module_name
209 3     3   44 = $self->_read('module_name', sub { $self->_get_module_name });
  3         25  
210              
211 3         23 return $module_name;
212             }
213              
214             sub dist_name {
215 50     50 1 528 my $self = shift;
216              
217 50         203 return $self->module->package_name;
218             }
219              
220             sub name {
221 11     11 1 6847 my $self = shift;
222              
223             my $name = $self->_read('name',
224 11     4   101 sub { $self->_normalize_name($self->dist_name) });
  4         24  
225              
226 11         159 return $name;
227             }
228              
229             sub dist_version {
230 4     4 1 12 my $self = shift;
231              
232 4         13 return $self->module->package_version;
233             }
234              
235             sub version {
236 9     9 1 904 my $self = shift;
237              
238             my $version = $self->_read('version',
239 9     4   85 sub { $self->_normalize_version($self->dist_version) });
  4         30  
240              
241 9         106 return $version;
242             }
243              
244             sub build_number {
245 5     5 1 210 my $self = shift;
246              
247 5         55 return $self->{build_number};
248             }
249              
250             sub author {
251 1     1 1 3 my $self = shift;
252              
253 1     1   46 my $author = $self->_read('author', sub { $self->module->author->author });
  1         5  
254              
255 1         10 return $author;
256             }
257              
258             sub packager {
259 5     5 1 1182 my $self = shift;
260              
261 5     3   59 my $packager = $self->_read('packager', sub { $self->_get_packager });
  3         53  
262              
263 5         41 return $packager;
264             }
265              
266             sub vendor {
267 3     3 1 231 my $self = shift;
268              
269             my $vendor = $self->_read('vendor',
270 3 50   3   48 sub { $self->rpm_eval('%{?vendor}') || 'CPANPLUS' });
  3         21  
271              
272 3         28 return $vendor;
273             }
274              
275             sub url {
276 3     3 1 459 my $self = shift;
277              
278             # A link to MetaCPAN is more useful than the homepage.
279             my $url = $self->_read('url',
280 3     3   29 sub { 'https://metacpan.org/dist/' . $self->dist_name });
  3         16  
281              
282 3         26 return $url;
283             }
284              
285             sub summary {
286 3     3 1 708 my $self = shift;
287              
288 3     3   54 my $summary = $self->_read('summary', sub { $self->_get_summary });
  3         41  
289              
290 3         29 return $summary;
291             }
292              
293             sub description {
294 3     3 1 756 my $self = shift;
295              
296             my $description
297 3     3   37 = $self->_read('description', sub { $self->_get_description });
  3         33  
298              
299 3         45 return $description;
300             }
301              
302             sub dependencies {
303 3     3 1 29 my $self = shift;
304              
305             my $dependencies
306 3     3   43 = $self->_read('dependencies', sub { $self->_get_dependencies });
  3         48  
307              
308 3         18 return $dependencies;
309             }
310              
311             sub copyrights {
312 5     5 1 2341 my $self = shift;
313              
314 5     3   64 my $copyrights = $self->_read('copyrights', sub { $self->_get_copyrights });
  3         33  
315              
316 5         39 return $copyrights;
317             }
318              
319             sub licenses {
320 5     5 1 867 my $self = shift;
321              
322 5     3   51 my $licenses = $self->_read('licenses', sub { $self->_get_licenses });
  3         29  
323              
324 5         27 return $licenses;
325             }
326              
327             sub license {
328 3     3 1 1959 my $self = shift;
329              
330 3     3   48 my $license = $self->_read('license', sub { $self->_get_license });
  3         37  
331              
332 3         24 return $license;
333             }
334              
335             sub files {
336 7     7 1 396 my $self = shift;
337              
338             my $files = $self->_read('files',
339 7     5   129 sub { [@{$self->_get_docfiles}, @{$self->_get_stagingfiles}] });
  5         15  
  5         125  
  5         108  
340              
341 7         50 return $files;
342             }
343              
344             sub files_by_type {
345 4     4 1 259 my ($self, $type) = @_;
346              
347 4         10 my @files = map { $_->{name} } grep { $_->{type} eq $type } @{$self->files};
  6         18  
  56         107  
  4         35  
348              
349 4         38 return \@files;
350             }
351              
352             sub mb_opt {
353 1     1 1 4 my $self = shift;
354              
355 1         4 my $installdirs = $self->installdirs;
356              
357 1         9 return << "END_MB_OPT";
358             --installdirs $installdirs
359             END_MB_OPT
360             }
361              
362             sub mm_opt {
363 1     1 1 4 my $self = shift;
364              
365 1         5 my $installdirs = $self->installdirs;
366              
367 1         13 return << "END_MM_OPT";
368             INSTALLDIRS=$installdirs
369             END_MM_OPT
370             }
371              
372             sub sanitize_stagingdir {
373 4     4 1 7360 my $self = shift;
374              
375 4         10 my $fail_count = 0;
376              
377             my $finddepth = sub {
378 64     64   137 my $dir = shift;
379              
380 64 50       1706 opendir my $dh, $dir
381             or croak "Could not traverse '$dir': $OS_ERROR";
382             ENTRY:
383 64         1147 while (defined(my $entry = readdir $dh)) {
384 204 100 100     1312 next ENTRY if $entry eq q{.} || $entry eq q{..};
385              
386 76         457 my $path = catfile($dir, $entry);
387              
388             # Skip symbolic links.
389 76 50       999 next ENTRY if -l $path;
390              
391             # Process sub directories first.
392 76 100       865 if (-d $path) {
393 60         323 __SUB__->($path);
394             }
395              
396             # Sanitize the permissions.
397 76         1023 my @stat = lstat $path;
398 76 50       250 if (!@stat) {
399 0         0 error("Could not stat '$path': $OS_ERROR");
400 0         0 next ENTRY;
401             }
402              
403 76         168 my $old_mode = $stat[2] & oct '0777';
404 76         141 my $new_mode = ($old_mode & oct '0755') | oct '0200';
405 76 50       177 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       826 if (-d $path) {
414 60         1997 rmdir $path;
415             }
416             else {
417 16 100 100     213 if ( $entry eq 'perllocal.pod'
      33        
      66        
418             || $entry eq '.packlist'
419             || ($entry =~ m{[.]bs \z}xms && -z $path))
420             {
421 8 50       425 if (!unlink $path) {
422 0         0 error("Could not remove '$path': $OS_ERROR");
423 0         0 ++$fail_count;
424             }
425             }
426             }
427             }
428 64         671 closedir $dh;
429              
430 64         319 return;
431 4         59 };
432 4         39 $finddepth->($self->stagingdir);
433              
434 4         107 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 41 my ($self, $expr) = @_;
459              
460 9         31 my $string = q{};
461              
462 9         46 my $rpm_cmd = $self->rpm_cmd;
463 9 50       829 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         71 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   23066 my $self = shift;
488              
489 6         27 my $stagingdir = $self->{stagingdir};
490 6 50       35 if (defined $stagingdir) {
491             ##no critic (ErrorHandling::RequireCheckingReturnValueOfEval)
492 6         19 eval { remove_tree($stagingdir) };
  6         11203  
493             }
494              
495 6         52 return;
496             }
497              
498             ## no critic (Subroutines::ProhibitExcessComplexity)
499              
500             sub _normalize_name {
501 13     13   286 my ($self, $dist_name) = @_;
502              
503 13         30 my $name;
504 13 50       41 if (exists $PACKAGE_NAME_FOR{$dist_name}) {
505 0         0 $name = $PACKAGE_NAME_FOR{$dist_name};
506             }
507             else {
508 13         33 $name = $dist_name;
509              
510             # Prepend "perl-" unless the name starts with "perl-".
511 13 100       51 if ($name !~ m{\A perl-}xms) {
512 12         43 $name = 'perl-' . $name;
513             }
514             }
515              
516 13         53 return $name;
517             }
518              
519             sub _normalize_version {
520 18     18   292 my ($self, $dist_version) = @_;
521              
522 18         59 my $dist_name = $self->dist_name;
523              
524 18   100     1160 my $version = $dist_version // 0;
525              
526 18 50       83 if (exists $VERSION_FOR{$dist_name}) {
527 0         0 $version = $VERSION_FOR{$dist_name}->($version);
528             }
529              
530 18         64 $version =~ s{\A v}{}xms; # Strip "v".
531              
532 18         100 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   9 my $self = shift;
546              
547 3         6 my $meta;
548              
549 3         22 my $builddir = $self->builddir;
550             META:
551 3         14 for (qw(META.json META.yml)) {
552 3         28 my $metafile = catfile($builddir, $_);
553 3 50       79 if (-f $metafile) {
554 3         9 $meta = eval { CPAN::Meta->load_file($metafile) };
  3         88  
555 3 50       184521 last META if defined $meta;
556             }
557             }
558              
559 3         35 return $meta;
560             }
561              
562             sub _meta {
563 11     11   27 my $self = shift;
564              
565 11     3   89 my $meta = $self->_read('meta', sub { $self->_get_meta });
  3         84  
566              
567 11         57 return $meta;
568             }
569              
570             sub _get_pod {
571 3     3   10 my $self = shift;
572              
573 3         14 my $builddir = $self->builddir;
574              
575 3   33     18 my $name = $POD_FOR{$self->dist_name} // $self->module_name;
576 3         14 my @dirs = map { catdir($builddir, $_) } qw(blib/lib blib/bin lib bin .);
  15         72  
577 3         85 my $pod = CPANPLUS::Dist::Debora::Pod->find($name, @dirs, $builddir);
578              
579 3         33 return $pod;
580             }
581              
582             sub _pod {
583 11     11   30 my $self = shift;
584              
585 11     3   96 return $self->_read('pod', sub { $self->_get_pod });
  3         41  
586             }
587              
588             sub _get_shared_objects {
589 3     3   9 my $self = shift;
590              
591 3         7 my $stagingdir = $self->{stagingdir};
592 3 50       11 if (!defined $stagingdir) {
593 0         0 croak 'Call shared_objects after the distribution has been built';
594             }
595              
596 3         20 my $shared_objects = find_shared_objects($stagingdir);
597              
598 3         18 return $shared_objects;
599             }
600              
601             sub _get_is_noarch {
602 3     3   10 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         10 my $stagingdir = $self->{stagingdir};
611 3 50       20 if (!defined $stagingdir) {
612 0         0 croak 'Call is_arch after the distribution has been built';
613             }
614              
615 3         7 my $is_noarch = @{$self->shared_objects} == 0;
  3         39  
616 3 50       14 if ($is_noarch) {
617 3         44 my $installdirs = $self->installdirs;
618 3         95 my $archdir = $Config{"install${installdirs}arch"};
619 3 50       19 if (defined $archdir) {
620 3         21 my $autodir = catdir($stagingdir, $archdir, 'auto');
621 3 50       69 if (-d $autodir) {
622 0         0 $is_noarch = 0;
623             }
624             }
625             }
626              
627 3         17 return $is_noarch;
628             }
629              
630             sub _get_module_name {
631 3     3   10 my $self = shift;
632              
633 3         12 my $name = $self->module->module;
634              
635             # Is there a .pm file with the distribution's name?
636 3         237 my @module = split qr{-}xms, $self->dist_name;
637 3         205 my $filename = catfile($self->builddir, 'lib', @module) . '.pm';
638 3 50       125 if (-f $filename) {
639 3         19 $name = join q{::}, @module;
640             }
641              
642 3         18 return $name;
643             }
644              
645             sub _get_packager {
646 3     3   10 my $self = shift;
647              
648 3         8 my $name;
649             my $email;
650              
651 3         21 my $EMAIL = qr{ \A
652             (?:([^<]*) \h+)? # name
653             ]+@[^>]+) >? # email
654             }xms;
655              
656 3 50       26 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         23 for my $key (qw(DEBFULLNAME NAME)) {
664 5 100       36 if ($ENV{$key}) {
665 1         3 $name = eval { decode_utf8($ENV{$key}) };
  1         11  
666 1 50       81 last NAME if $name;
667             }
668             }
669             }
670              
671 3         10 for my $key (qw(DEBEMAIL EMAIL)) {
672 6 100       24 if ($ENV{$key}) {
673 1         2 my $value = eval { decode_utf8($ENV{$key}) };
  1         4  
674 1 50 33     70 if ($value && $value =~ $EMAIL) {
675 1 50       4 if (!$name) {
676 0         0 $name = $1;
677             }
678 1 50       3 if (!$email) {
679 1         4 $email = $2;
680             }
681             }
682             }
683             }
684              
685 3         7 my $user;
686              
687 3         9 my @pw = eval { getpwuid $UID };
  3         460  
688 3 50       30 if (@pw) {
689 3         8 $user = eval { decode_utf8($pw[0]) };
  3         38  
690              
691 3 100       248 if (!$name) {
692 2         5 my $gecos = eval { decode_utf8($pw[6]) };
  2         9  
693 2 50       90 if ($gecos) {
694 2         36 ($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       11 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       12 if (!$email) {
718 2         39 my $host = hostfqdn;
719 2         1358 $host =~ s{[.]$}{}xms;
720 2         10 $email = $user . q{@} . $host;
721             }
722              
723 3         27 return "$name <$email>";
724             }
725              
726             sub _get_summary_from_meta {
727 4     4   13 my $self = shift;
728              
729 4         10 my $summary;
730              
731 4         27 my $meta = $self->_meta;
732 4 50       17 if (defined $meta) {
733 4         19 my $text = $meta->{abstract};
734 4 50 33     55 if ($text && $text !~ m{unknown}xmsi) {
735 4         14 $summary = $text;
736             }
737             }
738              
739 4         33 return $summary;
740             }
741              
742             sub _get_summary_from_pod {
743 1     1   5 my $self = shift;
744              
745 1         2 my $summary;
746              
747 1         13 my $pod = $self->_pod;
748 1 50       9 if (defined $pod) {
749 1         7 $summary = $pod->summary;
750             }
751              
752 1         10 return $summary;
753             }
754              
755             sub _get_summary {
756 3     3   8 my $self = shift;
757              
758 3   33     26 my $summary = $self->_get_summary_from_meta // $self->_get_summary_from_pod
      50        
759             // 'Module for the Perl programming language';
760 3         28 $summary =~ s{\v+}{ }xmsg; # Replace newlines.
761 3         11 $summary =~ s{[.]+ \z}{}xms; # Remove trailing dots.
762 3         26 $summary =~ s{\A (?:An? | The) \h+}{}xmsi; # Remove leading articles.
763              
764 3         23 return ucfirst $summary;
765             }
766              
767             sub _get_description {
768 3     3   9 my $self = shift;
769              
770 3         13 my $description = q{};
771              
772 3         19 my $pod = $self->_pod;
773 3 50       25 if (defined $pod) {
774 3         23 $description = $pod->description;
775             }
776              
777 3 50       15 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         15 return $description;
784             }
785              
786             sub _get_requires {
787 3     3   8 my $self = shift;
788              
789 3         8 my %requires;
790              
791 3   50     11 my $prereqs = $self->module->status->prereqs // {};
792              
793 3         458 my $meta = $self->_meta;
794 3 50 33     55 if (defined $meta && ref $meta->{prereqs} eq 'HASH') {
795 3   50     25 my $meta_runtime = $meta->{prereqs}->{runtime} // {};
796 3   50     17 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         9 grep { exists $prereqs->{$_} } keys %{$meta_requires};
  105         177  
  3         34  
801             }
802             else {
803 0         0 %requires = %{$prereqs};
  0         0  
804             }
805              
806 3         35 return \%requires;
807             }
808              
809             sub _get_dependencies {
810 3     3   11 my $self = shift;
811              
812 3         8 my %requires = %{$self->_get_requires};
  3         48  
813 3         21 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         372 );
825              
826 3         22 my %dependency;
827              
828             MODULE:
829 3         30 for my $module_name (keys %requires) {
830 12         415 my $module = $backend->module_tree($module_name);
831 12 50       1833 next MODULE if !$module;
832              
833             # Task::Weaken is only a build dependency.
834 12 50       33 next MODULE if $module_name eq 'Task::Weaken';
835              
836             # Ignore dependencies on modules for VMS and Windows.
837 12 50       47 next MODULE if $module_name =~ m{\A (?:VMS | Win32)}xms;
838              
839 12         67 my $dist_name = $module->package_name;
840 12         754 my $version = parse_version($requires{$module_name});
841              
842 12   33     73 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       1206 if (exists $version_for{$dist_name}) {
848 0         0 $version = $version_for{$dist_name}->($version);
849             }
850              
851 12 50 33     41 if (!exists $dependency{$module_name}
852             || $dependency{$module_name}->{version} < $version)
853             {
854 12         113 $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         148 } } sort { uc $a cmp uc $b } keys %dependency;
  12         63  
  13         41  
871              
872 3         57 return \@dependencies;
873             }
874              
875             sub _get_copyrights {
876 3     3   12 my $self = shift;
877              
878 3         9 my @copyrights;
879              
880 3         20 my $pod = $self->_pod;
881 3 50       26 if (defined $pod) {
882 3         9 push @copyrights, @{$pod->copyrights};
  3         18  
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         22 return \@copyrights;
895             }
896              
897             sub _get_licenses_from_meta {
898 4     4   934 my $self = shift;
899              
900 4         9 my @licenses;
901              
902 4         19 my $meta = $self->_meta;
903 4 50       18 if (defined $meta) {
904 4         14 my $keys = $meta->{license};
905 4 50       13 if (defined $keys) {
906 4 50       16 if (!ref $keys) {
907 0         0 $keys = [$keys];
908             }
909 4         15 my %ignore_key = map { $_ => 1 } qw(open_source unrestricted);
  8         47  
910 4         12 for my $key (grep { !exists $ignore_key{$_} } @{$keys}) {
  4         24  
  4         12  
911             my @license
912 4         74 = Software::LicenseUtils->guess_license_from_meta_key($key,
913             2);
914 4 50       138 if (@license) {
915 4         19 push @licenses, @license;
916             }
917             }
918             }
919             }
920              
921 4         15 return \@licenses;
922             }
923              
924             sub _get_licenses_from_pod {
925 4     4   855 my $self = shift;
926              
927 4         9 my @licenses;
928              
929 4         14 my $pod = $self->_pod;
930 4 50       20 if (defined $pod) {
931             my @license
932 4         26 = Software::LicenseUtils->guess_license_from_pod($pod->text);
933 4 50       4258 if (@license) {
934 4         14 push @licenses, @license;
935             }
936             }
937              
938 4         16 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         20  
945              
946             my $get_license = sub {
947 3     3   30 my $spdx_expression = shift;
948              
949 3         8 my $license = eval {
950 3         44 Software::LicenseUtils->new_from_spdx_expression({
951             spdx_expression => $spdx_expression,
952             %copyright
953             });
954             };
955 3 50       234 if (!$license) {
956 0         0 $license = CPANPLUS::Dist::Debora::License->new({
957             package => $self,
958             %copyright
959             });
960             }
961              
962 3         27 return $license;
963 3         35 };
964              
965             my %unique_guesses
966 6         116 = map { $_->name => $_ } @{$self->_get_licenses_from_meta},
  3         40  
967 3         11 @{$self->_get_licenses_from_pod};
  3         20  
968              
969             # Add the copyright year and author to the guessed licenses.
970             my @licenses
971 3         30 = map { $get_license->($_->spdx_expression) } values %unique_guesses;
  3         34  
972 3 50       14 if (!@licenses) {
973 0         0 push @licenses, $get_license->($LICENSE_FOR{$self->dist_name});
974             }
975              
976             my @sorted_licenses
977 3         19 = sort { $a->spdx_expression cmp $b->spdx_expression } @licenses;
  0         0  
978              
979 3         48 return \@sorted_licenses;
980             }
981              
982             sub _get_license {
983 3     3   9 my $self = shift;
984              
985 3         7 my @names = map { $_->spdx_expression } @{$self->licenses};
  3         12  
  3         15  
986             my $license = join ' AND ',
987 3 50 33     21 map { @names > 1 && m{\b OR \b}xmsi ? "($_)" : $_ } @names;
  3         42  
988              
989 3         16 return $license;
990             }
991              
992             sub _get_docfiles {
993 5     5   21 my $self = shift;
994              
995 5         50 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         39 my $CHANGELOG = qr{ \A (?:
1003             Change(?:s|Log)
1004             ) (?:[.](?:md|mkdn|pod|txt))? \z
1005             }xmsi;
1006              
1007 5         27 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         43 my %regex_for = (
1021             'license' => $LICENSE,
1022             'changelog' => $CHANGELOG,
1023             'doc' => $DOC,
1024             );
1025              
1026 5         15 my @files;
1027              
1028             my $fix_permissions = sub {
1029 5     5   15 my $dir = shift;
1030              
1031 5         93 chmod oct '0755', $dir;
1032              
1033 5 50       209 opendir my $dh, $dir
1034             or croak "Could not traverse '$dir': $OS_ERROR";
1035             ENTRY:
1036 5         123 while (defined(my $entry = readdir $dh)) {
1037 15 100 100     178 next ENTRY if $entry eq q{.} || $entry eq q{..};
1038              
1039 5         116 my $path = catfile($dir, $entry);
1040              
1041             # Skip symbolic links.
1042 5 50       135 next ENTRY if -l $path;
1043              
1044 5 50       79 if (-d $path) {
1045 0         0 __SUB__->($path);
1046             }
1047             else {
1048 5         105 chmod oct '0644', $path;
1049             }
1050             }
1051 5         69 closedir $dh;
1052              
1053 5         38 return;
1054 5         68 };
1055              
1056             my $find = sub {
1057 5     5   23 my $dir = shift;
1058              
1059 5 50       278 opendir my $dh, $dir
1060             or croak "Could not traverse '$dir': $OS_ERROR";
1061             ENTRY:
1062 5         222 while (defined(my $entry = readdir $dh)) {
1063 115 100 100     488 next ENTRY if $entry eq q{.} || $entry eq q{..};
1064              
1065 105         542 my $path = catfile($dir, $entry);
1066              
1067             # Skip symbolic links.
1068 105 50       1422 next ENTRY if -l $path;
1069              
1070 105 100       2038 if (-d $path) {
    100          
1071 25 100       151 if ($entry eq 'examples') {
1072 5         30 $fix_permissions->($path);
1073 5         37 my $file = {name => $entry, type => 'doc'};
1074 5         36 push @files, $file;
1075             }
1076             }
1077             elsif (-s $path) {
1078             TYPE:
1079 70         262 for my $type (keys %regex_for) {
1080 191 100       1170 if ($entry =~ $regex_for{$type}) {
1081 20         477 chmod oct '0644', $path;
1082 20         130 my $file = {name => $entry, type => $type};
1083 20         59 push @files, $file;
1084 20         111 last TYPE;
1085             }
1086             }
1087             }
1088             }
1089 5         67 closedir $dh;
1090              
1091 5         42 return;
1092 5         78 };
1093 5         37 $find->($self->builddir);
1094              
1095 5         88 my @sorted_files = sort { $a->{name} cmp $b->{name} } @files;
  40         120  
1096              
1097 5         121 return \@sorted_files;
1098             }
1099              
1100             sub _get_excludedirs {
1101 5     5   18 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         94 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         25 my %excludedirs = map { $_ => 1 } qw(/etc);
  5         40  
1122             VAR:
1123 5         25 for my $var (@vars) {
1124 60         2087 my $value = $Config{$var};
1125 60 100       293 next VAR if !$value;
1126              
1127 20 100       115 if ($var =~ m{arch \z}xms) {
1128 5         62 $value = catdir($value, 'auto');
1129             }
1130              
1131 20         199 my ($volume, $path) = File::Spec->splitpath($value, 1);
1132              
1133 20         110 my ($dir, @dirs) = splitdir($path);
1134 20         209 while (@dirs) {
1135 100         298 $dir = catdir($dir, shift @dirs);
1136 100         300 $excludedirs{$dir} = 1;
1137             }
1138             }
1139              
1140 5         33 return \%excludedirs;
1141             }
1142              
1143             sub _get_stagingfiles {
1144 5     5   18 my $self = shift;
1145              
1146 5         28 my $stagingdir = $self->stagingdir;
1147 5         18 my $stagingdir_length = length $stagingdir;
1148 5         81 my $excludedirs = $self->_get_excludedirs;
1149              
1150 5         14 my @files;
1151              
1152             my $find = sub {
1153 40     40   90 my $dir = shift;
1154              
1155 40 50       1189 opendir my $dh, $dir
1156             or croak "Could not traverse '$dir': $OS_ERROR";
1157             ENTRY:
1158 40         744 while (defined(my $entry = readdir $dh)) {
1159 127 100 100     766 next ENTRY if $entry eq q{.} || $entry eq q{..};
1160              
1161 47         307 my $path = catfile($dir, $entry);
1162              
1163 47         153 my $name = substr $path, $stagingdir_length;
1164 47 100       1086 my $type = -l $path ? 'link' : -d $path ? 'dir' : 'file';
    50          
1165 47 100       155 if ($type eq 'file') {
1166 12         62 my ($volume, $dirs, $file) = splitpath($name);
1167 12         272 my %subdir = map { $_ => 1 } splitdir($dirs);
  59         243  
1168 12 50       94 if (exists $subdir{etc}) {
    100          
1169 0         0 $type = 'config';
1170             }
1171             elsif (exists $subdir{man}) {
1172 5         29 $type = 'man';
1173             }
1174             }
1175              
1176 47 100       133 if (!exists $excludedirs->{$name}) {
1177 43         180 my $file = {name => $name, type => $type};
1178 43         113 push @files, $file;
1179             }
1180              
1181             # Skip symbolic links.
1182 47 50       552 next ENTRY if -l $path;
1183              
1184 47 100       590 if (-d $path) {
1185 35         192 __SUB__->($path);
1186             }
1187             }
1188 40         394 closedir $dh;
1189              
1190 40         323 return;
1191 5         87 };
1192 5         43 $find->($stagingdir);
1193              
1194 5         137 my @sorted_files = sort { $a->{name} cmp $b->{name} } @files;
  74         147  
1195              
1196 5         117 return \@sorted_files;
1197             }
1198              
1199             1;
1200             __END__