File Coverage

blib/lib/CPANPLUS/Dist/Debora/Package.pm
Criterion Covered Total %
statement 527 603 87.4
branch 116 196 59.1
condition 38 71 53.5
subroutine 105 118 88.9
pod 36 36 100.0
total 822 1024 80.2


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