File Coverage

blib/lib/CPANPLUS/Dist/Debora/Package/RPM.pm
Criterion Covered Total %
statement 151 200 75.5
branch 14 44 31.8
condition 2 15 13.3
subroutine 45 48 93.7
pod 14 14 100.0
total 226 321 70.4


line stmt bran cond sub pod time code
1             package CPANPLUS::Dist::Debora::Package::RPM;
2              
3             # SPDX-License-Identifier: Artistic-1.0-Perl OR GPL-1.0-or-later
4              
5 4     4   26875 use 5.016;
  4         24  
6 4     4   20 use warnings;
  4         12  
  4         107  
7 4     4   22 use utf8;
  4         7  
  4         24  
8              
9             our $VERSION = '0.011';
10              
11 4     4   243 use parent qw(CPANPLUS::Dist::Debora::Package);
  4         25  
  4         33  
12              
13 4     4   276 use Carp qw(croak);
  4         12  
  4         198  
14 4     4   27 use Config;
  4         13  
  4         176  
15 4     4   26 use English qw(-no_match_vars);
  4         8  
  4         45  
16 4     4   1638 use File::Path qw(remove_tree);
  4         12  
  4         206  
17 4     4   33 use File::Spec::Functions qw(catdir catfile);
  4         9  
  4         198  
18 4     4   34 use File::Temp qw(tempdir);
  4         43  
  4         206  
19 4     4   30 use POSIX qw(uname);
  4         33  
  4         35  
20 4     4   2134 use Text::Template 1.22 qw();
  4         101  
  4         154  
21 4     4   24 use Text::Wrap qw();
  4         9  
  4         104  
22              
23             use CPANPLUS::Dist::Debora::Util
24 4     4   32 qw(can_run run slurp_utf8 spew_utf8 is_testing);
  4         36  
  4         316  
25 4     4   29 use CPANPLUS::Error qw(error);
  4         44  
  4         9905  
26              
27             # Add some package names.
28             my %OBSOLETES_FOR = (
29             'ack' => [qw(perl-App-Ack)],
30             'Alien-Build' => [qw{
31             perl-Alien-Base
32             perl-Alien-Build-Plugin-Decode-HTML
33             perl-Alien-Build-Plugin-Decode-Mojo
34             perl-Alien-Build-tests
35             }],
36             'App-Licensecheck' => [qw(perl-App-Licensecheck)],
37             'App-perlbrew' => [qw(perl-App-perlbrew)],
38             'Catalyst-Runtime' => [qw{perl-Catalyst-Runtime-scripts}],
39             'Encode' => [qw{perl-Encode-devel perl-encoding}],
40             'Module-CoreList' => [qw{perl-Module-CoreList-tools}],
41             'Mojolicious' => [qw(perl-Test-Mojo)],
42             'Perl-Critic' => [qw(perl-Test-Perl-Critic-Policy)],
43             'perl-ldap' => [qw(perl-LDAP)],
44             'Perl-Tidy' => [qw(perltidy)],
45             'TermReadKey' => [qw(perl-TermReadKey)],
46             'Razor2-Client-Agent' =>
47             [qw(perl-Razor-Agent perl-razor-agents razor-agents)],
48             );
49              
50             # Add additional capabilities to some packages.
51             my %PROVIDES_FOR = (
52             'libwww-perl' => [qw{
53             perl(LWP::Debug::TraceHTTP::Socket)
54             perl(LWP::Protocol::http::Socket)
55             perl(LWP::Protocol::http::SocketMethods)
56             }],
57             'Moose' => [qw{perl(Moose::Conflicts)}],
58             'Package-Stash' => [qw{perl(Package::Stash::Conflicts)}],
59             'XS-Parse-Keyword' => [qw{perl(:XS_Parse_Keyword_ABI_2)}],
60             );
61              
62             sub format_priority {
63 2     2 1 7 my $class = shift;
64              
65 2         10 my @commands = qw(rpm rpmbuild tar);
66              
67 2         6 my $priority = 0;
68 2 50       7 if (@commands == grep { can_run($_) } @commands) {
  6         1462  
69 0         0 $priority = 1;
70 0 0 0     0 if (-f '/etc/redhat-release' || -d '/usr/lib/rpm/suse') {
71 0         0 $priority = 2;
72             }
73             }
74              
75 2         614 return $priority;
76             }
77              
78             sub create {
79 0     0 1 0 my ($self, %options) = @_;
80              
81 0         0 my $builddir = $self->builddir;
82 0         0 my $outputdir = $self->outputdir;
83 0         0 my $rpmdir = $self->rpmdir;
84 0         0 my $sourcedir = $self->sourcedir;
85 0         0 my $specfile = catfile($outputdir, $self->name . '.spec');
86              
87 0         0 my $buildrootdir = tempdir('buildrootXXXX', DIR => $outputdir);
88              
89 0         0 my @rpmbuild_cmd = (
90             'rpmbuild', '-bb',
91             '-D', "_builddir $builddir",
92             '-D', "_rpmdir $rpmdir",
93             '-D', "_sourcedir $sourcedir",
94             '-D', "_buildrootdir $buildrootdir",
95             '-D', 'source_date_epoch_from_changelog 0',
96             '-D', 'use_source_date_epoch_as_buildtime 1',
97             '-D', 'clamp_mtime_to_source_date_epoch 1',
98             );
99              
100 0 0       0 if ($self->installdirs eq 'site') {
101 0         0 my $prefix = $Config{siteprefix};
102 0         0 my $datadir = catdir($prefix, 'share');
103 0         0 push @rpmbuild_cmd, '-D', "_datadir $datadir";
104             }
105              
106 0         0 push @rpmbuild_cmd, $specfile;
107              
108 0         0 my $ok = 0;
109              
110 0         0 my $spec = $self->spec;
111 0 0       0 if (!$spec) {
112 0         0 error('Could not render the spec file');
113             }
114             else {
115 0         0 $ok = spew_utf8($specfile, $spec);
116 0 0       0 if (!$ok) {
117 0         0 error("Could not create '$specfile': $OS_ERROR");
118             }
119             }
120              
121 0 0       0 if ($ok) {
122             local $ENV{SOURCE_DATE_EPOCH} = $ENV{SOURCE_DATE_EPOCH}
123 0   0     0 // $self->last_modification;
124              
125             $ok = run(
126             command => \@rpmbuild_cmd,
127             dir => $builddir,
128             verbose => $options{verbose},
129 0         0 );
130             }
131              
132 0         0 remove_tree($buildrootdir);
133              
134 0         0 return $ok;
135             }
136              
137             sub install {
138 0     0 1 0 my ($self, %options) = @_;
139              
140             # We always pass "--force" to rpm. The CPANPLUS option "force" is more
141             # annoying than useful and thus not used here.
142 0         0 my $sudo_cmd = $self->sudo_cmd;
143 0         0 my @install_cmd = ($sudo_cmd, qw(rpm --upgrade --force --verbose));
144              
145 0 0       0 if (is_testing) {
146 0         0 @install_cmd = qw(rpm -qlvp);
147             }
148              
149 0         0 push @install_cmd, $self->outputname;
150              
151 0         0 my $ok = run(command => \@install_cmd, verbose => $options{verbose});
152              
153 0         0 return $ok;
154             }
155              
156             sub outputname {
157 1     1 1 4 my $self = shift;
158              
159             my $outputname = $self->_read(
160             'outputname',
161             sub {
162 1     1   15 catfile($self->rpmdir, $self->arch,
163             $self->name . q{-}
164             . $self->version . q{-}
165             . $self->release . q{.}
166             . $self->arch
167             . q{.rpm});
168             }
169 1         28 );
170              
171 1         22 return $outputname;
172             }
173              
174             sub license {
175 1     1 1 352 my $self = shift;
176              
177 1         12 my $license = $self->SUPER::license;
178              
179             # Fedora's rpmlint expects the licenses in reversed order.
180 1 50       5 if ($license eq 'Artistic-1.0-Perl OR GPL-1.0-or-later') {
181 1         3 $license = 'GPL-1.0-or-later OR Artistic-1.0-Perl';
182             }
183              
184 1         6 return $license;
185             }
186              
187             sub rpmdir {
188 1     1 1 3 my $self = shift;
189              
190 1     1   25 my $rpmdir = $self->_read('rpmdir', sub { $self->_get_rpmdir });
  1         10  
191              
192 1         9 return $rpmdir;
193             }
194              
195             sub arch {
196 2     2 1 5 my $self = shift;
197              
198             my $arch = $self->_read(
199             'arch',
200             sub {
201 1 50 0 1   10 $self->is_noarch ? 'noarch' : $self->rpm_eval('%{?_arch}')
202             || (uname)[4];
203             }
204 2         17 );
205              
206 2         40 return $arch;
207             }
208              
209             sub dist {
210 1     1 1 6 my $self = shift;
211              
212 1     1   15 my $dist = $self->_read('dist', sub { $self->rpm_eval('%{?dist}') });
  1         8  
213              
214 1         12 return $dist;
215             }
216              
217             sub release {
218 3     3 1 213 my $self = shift;
219              
220             my $release
221 3     1   26 = $self->_read('release', sub { $self->build_number . $self->dist });
  1         11  
222              
223 3         20 return $release;
224             }
225              
226             sub epoch {
227 1     1 1 11 my $self = shift;
228              
229 1     1   13 my $epoch = $self->_read('epoch', sub { $self->_get_epoch });
  1         12  
230              
231 1         4 return $epoch;
232             }
233              
234             sub distribution {
235 1     1 1 8 my $self = shift;
236              
237             my $distribution
238 1     1   14 = $self->_read('distribution', sub { $self->_get_distribution });
  1         7  
239              
240 1         5 return $distribution;
241             }
242              
243             sub provides {
244 1     1 1 15 my $self = shift;
245              
246 1         4 my $dist_name = $self->dist_name;
247              
248 1         69 my @provides;
249 1 50       6 if (exists $PROVIDES_FOR{$dist_name}) {
250 0         0 push @provides, @{$PROVIDES_FOR{$dist_name}};
  0         0  
251             }
252              
253 1         3 return \@provides;
254             }
255              
256             sub obsoletes {
257 1     1 1 10 my $self = shift;
258              
259 1         4 my $dist_name = $self->dist_name;
260              
261 1         59 my @obsoletes;
262 1 50       6 if (exists $OBSOLETES_FOR{$dist_name}) {
263 0         0 push @obsoletes, @{$OBSOLETES_FOR{$dist_name}};
  0         0  
264             }
265              
266 1         3 return \@obsoletes;
267             }
268              
269             sub _escape {
270 24     24   70 my ($self, $text) = @_;
271              
272 24 50       71 if ($text) {
273 24         70 $text =~ s{%}{%%}xmsg;
274              
275             # Insert a non-visible space before "#" characters at the start of
276             # a line so that RPM doesn't interpret such lines as comments.
277 24         56 $text =~ s{^ (\h*) [#]}{$1\N{U+200B}#}xmsg;
278             }
279              
280 24         193 return $text;
281             }
282              
283             sub _glob_escape {
284 12     12   112 my ($self, $filename) = @_;
285              
286 12         25 $filename =~ s{([%*?\[\]\\])}{[$1]}xmsg;
287 12         20 $filename =~ s{[ '{}]}{?}xmsg;
288              
289 12         39 return $filename;
290             }
291              
292             sub _date {
293 1     1   5 my ($self, $timestamp) = @_;
294              
295 1         54 my ($week_day, $month, $day, $time, $year) = split q{ },
296             scalar gmtime $timestamp;
297              
298 1         9 my $date = sprintf '%s %s %02d %s', $week_day, $month, $day, $year;
299              
300 1         38 return $date;
301             }
302              
303             sub _fill_in {
304 1     1   6 my ($self, $template, %vars) = @_;
305              
306             my $text = $template->fill_in(
307             STRICT => 1,
308             HASH => {
309 24     24   777 escape => \sub { $self->_escape(@_) },
310 12     12   94 glob_escape => \sub { $self->_glob_escape(@_) },
311 1         48 package => \$self,
312             date => $self->_date($self->last_modification),
313             %vars
314             },
315             );
316              
317 1         153 return $text;
318             }
319              
320             sub spec {
321 1     1 1 8 my ($self, %vars) = @_;
322              
323 1         36 my $template = Text::Template->new(
324             DELIMITERS => ['[%', '%]'],
325             TYPE => 'STRING',
326             SOURCE => <<'END_TEMPLATE');
327             Name: [% $escape->($package->name) %]
328             Version: [% $escape->($package->version) %]
329             Release: [% $escape->($package->release) %]
330             Summary: [% $escape->($package->summary) %]
331             License: [% $escape->($package->license) %]
332             Packager: [% $escape->($package->packager) %]
333             Vendor: [% $escape->($package->vendor) %]
334             URL: [% $escape->($package->url) %]
335             [%
336             use Config;
337              
338             my $perl_version = $Config{version};
339             my $perl_vendorlib = $Config{installvendorlib};
340              
341             my $distdir = "$perl_vendorlib/auto/share/dist/CPANPLUS-Dist-Debora";
342              
343             my $has_shared_objects = (@{$package->shared_objects} > 0);
344              
345             my $epoch = $package->epoch;
346             if ($epoch) {
347             $OUT .= 'Epoch: ' . $escape->($epoch). "\n";
348             }
349              
350             my $distribution = $package->distribution;
351             if ($distribution) {
352             $OUT .= '%global distribution '. $escape->($distribution) . "\n";
353             }
354              
355             if ($package->is_noarch) {
356             $OUT .= "BuildArch: noarch\n";
357             }
358              
359             # See "Renaming/Replacing or Removing Existing Packages" in the Fedora
360             # documentation.
361             my $evr = $package->version . q{-} . $package->release;
362             if ($epoch) {
363             $evr = $epoch . q{:} . $package->version;
364             }
365             my $escaped_evr = $escape->($evr);
366              
367             for my $name (@{$package->provides}) {
368             $OUT .= sprintf "Provides: %s\n", $escape->($name);
369             }
370              
371             for my $name (@{$package->obsoletes}) {
372             $OUT .= sprintf "Provides: %s = %s\n", $escape->($name), $escaped_evr;
373             $OUT .= sprintf "Obsoletes: %s < %s\n", $escape->($name), $escaped_evr;
374             }
375              
376             $OUT .= "AutoProv: 1\n";
377              
378             # We have to use an updated perl.prov on CentOS 7.
379             my $perl_prov = "$distdir/perl.prov";
380             if (-x $perl_prov) {
381             $OUT .= "%global __perllib_provides $perl_prov\n";
382             }
383              
384             # /usr/lib/rpm/perl.req finds too many circular, internal and optional
385             # dependencies, but we have to add shared library dependencies to
386             # architecture-dependent Perl distributions.
387             if ($package->is_noarch) {
388             $OUT .= "AutoReq: 0\n";
389             }
390             else {
391             if (!$has_shared_objects) {
392             $OUT .= "%global debug_package %{nil}\n";
393             }
394             $OUT .= "%global __perl_requires /bin/true\n";
395             $OUT .= "%global __perllib_requires /bin/true\n";
396             $OUT .= "%global __perltest_requires /bin/true\n";
397             $OUT .= "AutoReq: 1\n";
398             }
399              
400             $OUT .= "%if 0%{?fedora} > 0 || 0%{?rhel} > 0\n";
401             if ($has_shared_objects) {
402             $OUT .= 'Requires: perl(:MODULE_COMPAT_' . $escape->($perl_version) . ")\n";
403             }
404             else {
405             $OUT .= "Requires: perl-libs\n";
406             }
407             $OUT .= "%endif\n";
408             for my $dependency (@{$package->dependencies}) {
409             if ($dependency->{is_module}) {
410             $OUT .= 'Requires: perl(' . $escape->($dependency->{module_name}) . ')';
411             }
412             else {
413             $OUT .= 'Requires: $escape->($dependency->{package_name})';
414             }
415             if ($dependency->{version}) {
416             $OUT .= ' >= ' . $escape->($dependency->{version});
417             }
418             $OUT .= "\n";
419             }
420             $OUT .= "%{?perl_requires}\n";
421             q{};
422             %]
423             %{?perl_default_filter}
424              
425             %description
426             [%
427             local $Text::Wrap::unexpand = 0;
428             $escape->(Text::Wrap::wrap(q{}, q{}, $package->description))
429             %]
430              
431             %{?debug_package}
432              
433             %prep
434              
435             %build
436              
437             %check
438              
439             %install
440             tar -C '[% $escape->($package->stagingdir) %]' -cf - . | tar -C %{buildroot} -xf -
441              
442             %clean
443              
444             %files
445             %defattr(-, root, root)
446             [%
447             my %format = (
448             'changelog' => '%%doc %s',
449             'config' => '%%config(noreplace) %s',
450             'dir' => '%%dir %s',
451             'doc' => '%%doc %s',
452             'license' => '%%license %s',
453             'man' => '%s*',
454             );
455             for my $file (@{$package->files}) {
456             my $name = $file->{name};
457             my $type = $file->{type};
458             if (exists $format{$type}) {
459             $OUT .= sprintf $format{$type}, $glob_escape->($name);
460             }
461             else {
462             $OUT .= $glob_escape->($name);
463             }
464             $OUT .= "\n";
465             }
466             q{};
467             %]
468             %changelog
469             * [% $date %] [% $escape->($package->packager) %] - [% $escape->($package->version) %]-[% $escape->($package->build_number) %]
470             - Package [% $escape->($package->dist_name) %] [% $escape->($package->version) %]
471             END_TEMPLATE
472              
473 1         295 my $text = $self->_fill_in($template, %vars);
474              
475 1         27 return $text;
476             }
477              
478             sub _get_rpmdir {
479 1     1   4 my $self = shift;
480              
481 1         19 my $topdir = $self->rpm_eval('%{?_topdir}');
482              
483 1 50       5 if (!$topdir) {
484 1         4 my $homedir = $ENV{HOME};
485 1 50       3 if ($homedir) {
486 1         7 $topdir = catdir($homedir, 'rpmbuild');
487             }
488             }
489              
490 1 50       4 if (!$topdir) {
491 0         0 $topdir = $self->outputdir;
492             }
493              
494 1         4 my $rpmdir = catdir($topdir, 'RPMS');
495              
496 1         6 return $rpmdir;
497             }
498              
499             sub _get_epoch_from_env {
500 1     1   3 my $self = shift;
501              
502 1         2 my $epoch = 0;
503 1 50 33     7 if (defined $ENV{EPOCH} && $ENV{EPOCH} =~ m{\A \d+ \z}xms) {
504 0         0 $epoch = $ENV{EPOCH};
505             }
506              
507 1         3 return $epoch;
508             }
509              
510             sub _get_epoch_from_system {
511 1     1   4 my $self = shift;
512              
513 1         12 my $epoch = 0;
514 1         5 my $rpm_cmd = $self->rpm_cmd;
515 1 50       90 if ($rpm_cmd) {
516 0         0 my @query_cmd = ($rpm_cmd, '-q', '--qf', '%{EPOCH}', $self->name);
517 0         0 my $output = q{};
518              
519             my $ok = run(
520             command => \@query_cmd,
521             buffer => \$output,
522       0     on_error => sub { }
523 0         0 );
524 0 0       0 if ($ok) {
525 0         0 chomp $output;
526 0 0       0 if ($output =~ m{\A \d+ \z}xms) {
527 0         0 $epoch = $output;
528             }
529             }
530             }
531              
532 1         3 return $epoch;
533             }
534              
535             sub _get_epoch {
536 1     1   3 my $self = shift;
537              
538 1         11 my $epoch_env = $self->_get_epoch_from_env;
539 1         11 my $epoch_sys = $self->_get_epoch_from_system;
540 1 50       4 my $epoch = $epoch_env > $epoch_sys ? $epoch_env : $epoch_sys;
541              
542 1         4 return $epoch;
543             }
544              
545             sub _get_distribution {
546 1     1   4 my $self = shift;
547              
548             # Values with escaped characters are deliberately ignored.
549 1         6 my $BRACKETED_REST = qr{[(] [^\\"]*}xms;
550 1         41 my $PRETTY_NAME
551             = qr{^ PRETTY_NAME = " ([^\\"]+?) \h* (?:$BRACKETED_REST)? " $}xms;
552              
553 1         7 my $distribution = $self->rpm_eval('%{?distribution}');
554 1 50       14 if (!$distribution) {
555             OS_RELEASE:
556 1         4 for my $filename (grep {-f} qw(/etc/os-release /usr/lib/os-release)) {
  2         85  
557 1         4 my $os_release = eval { slurp_utf8($filename) };
  1         5  
558 1 50 33     27 if ($os_release && $os_release =~ $PRETTY_NAME) {
559 1         4 $distribution = $1;
560 1         5 last OS_RELEASE;
561             }
562             }
563             }
564              
565 1         8 return $distribution;
566             }
567              
568             1;
569             __END__