File Coverage

blib/lib/CPANPLUS/Dist/Debora/Package/RPM.pm
Criterion Covered Total %
statement 146 195 74.8
branch 13 42 30.9
condition 2 15 13.3
subroutine 44 47 93.6
pod 13 13 100.0
total 218 312 69.8


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