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   22424 use 5.016;
  4         17  
6 4     4   17 use warnings;
  4         7  
  4         91  
7 4     4   16 use utf8;
  4         12  
  4         16  
8              
9             our $VERSION = '0.010';
10              
11 4     4   157 use parent qw(CPANPLUS::Dist::Debora::Package);
  4         14  
  4         40  
12              
13 4     4   251 use Carp qw(croak);
  4         13  
  4         189  
14 4     4   26 use Config;
  4         9  
  4         134  
15 4     4   18 use English qw(-no_match_vars);
  4         14  
  4         35  
16 4     4   1325 use File::Path qw(remove_tree);
  4         7  
  4         215  
17 4     4   25 use File::Spec::Functions qw(catdir catfile);
  4         7  
  4         187  
18 4     4   23 use File::Temp qw(tempdir);
  4         29  
  4         179  
19 4     4   24 use POSIX qw(uname);
  4         25  
  4         41  
20 4     4   1821 use Text::Template 1.22 qw();
  4         77  
  4         88  
21 4     4   19 use Text::Wrap qw();
  4         8  
  4         101  
22              
23             use CPANPLUS::Dist::Debora::Util
24 4     4   22 qw(can_run run slurp_utf8 spew_utf8 is_testing);
  4         33  
  4         287  
25 4     4   22 use CPANPLUS::Error qw(error);
  4         27  
  4         8406  
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 5 my $class = shift;
64              
65 2         6 my @commands = qw(rpm rpmbuild tar);
66              
67 2         4 my $priority = 0;
68 2 50       5 if (@commands == grep { can_run($_) } @commands) {
  6         1196  
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         573 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   13 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         16 );
170              
171 1         16 return $outputname;
172             }
173              
174             sub rpmdir {
175 1     1 1 3 my $self = shift;
176              
177 1     1   18 my $rpmdir = $self->_read('rpmdir', sub { $self->_get_rpmdir });
  1         15  
178              
179 1         18 return $rpmdir;
180             }
181              
182             sub arch {
183 2     2 1 17 my $self = shift;
184              
185             my $arch = $self->_read(
186             'arch',
187             sub {
188 1 50 0 1   8 $self->is_noarch ? 'noarch' : $self->rpm_eval('%{?_arch}')
189             || (uname)[4];
190             }
191 2         20 );
192              
193 2         23 return $arch;
194             }
195              
196             sub dist {
197 1     1 1 3 my $self = shift;
198              
199 1     1   11 my $dist = $self->_read('dist', sub { $self->rpm_eval('%{?dist}') });
  1         3  
200              
201 1         7 return $dist;
202             }
203              
204             sub release {
205 3     3 1 164 my $self = shift;
206              
207             my $release
208 3     1   25 = $self->_read('release', sub { $self->build_number . $self->dist });
  1         10  
209              
210 3         14 return $release;
211             }
212              
213             sub epoch {
214 1     1 1 765 my $self = shift;
215              
216 1     1   16 my $epoch = $self->_read('epoch', sub { $self->_get_epoch });
  1         13  
217              
218 1         6 return $epoch;
219             }
220              
221             sub distribution {
222 1     1 1 10 my $self = shift;
223              
224             my $distribution
225 1     1   12 = $self->_read('distribution', sub { $self->_get_distribution });
  1         12  
226              
227 1         7 return $distribution;
228             }
229              
230             sub provides {
231 1     1 1 14 my $self = shift;
232              
233 1         3 my $dist_name = $self->dist_name;
234              
235 1         65 my @provides;
236 1 50       6 if (exists $PROVIDES_FOR{$dist_name}) {
237 0         0 push @provides, @{$PROVIDES_FOR{$dist_name}};
  0         0  
238             }
239              
240 1         3 return \@provides;
241             }
242              
243             sub obsoletes {
244 1     1 1 9 my $self = shift;
245              
246 1         4 my $dist_name = $self->dist_name;
247              
248 1         51 my @obsoletes;
249 1 50       4 if (exists $OBSOLETES_FOR{$dist_name}) {
250 0         0 push @obsoletes, @{$OBSOLETES_FOR{$dist_name}};
  0         0  
251             }
252              
253 1         9 return \@obsoletes;
254             }
255              
256             sub _escape {
257 25     25   49 my ($self, $text) = @_;
258              
259 25 50       91 if ($text) {
260 25         61 $text =~ s{%}{%%}xmsg;
261              
262             # Insert a non-visible space before "#" characters at the start of
263             # a line so that RPM doesn't interpret such lines as comments.
264 25         45 $text =~ s{^ (\h*) [#]}{$1\N{U+200B}#}xmsg;
265             }
266              
267 25         148 return $text;
268             }
269              
270             sub _glob_escape {
271 12     12   18 my ($self, $filename) = @_;
272              
273 12         20 $filename =~ s{([%*?\[\]\\])}{[$1]}xmsg;
274 12         17 $filename =~ s{[ '{}]}{?}xmsg;
275              
276 12         29 return $filename;
277             }
278              
279             sub _date {
280 1     1   4 my ($self, $timestamp) = @_;
281              
282 1         32 my ($week_day, $month, $day, $time, $year) = split q{ },
283             scalar gmtime $timestamp;
284              
285 1         12 my $date = sprintf '%s %s %02d %s', $week_day, $month, $day, $year;
286              
287 1         19 return $date;
288             }
289              
290             sub _fill_in {
291 1     1   4 my ($self, $template, %vars) = @_;
292              
293             my $text = $template->fill_in(
294             STRICT => 1,
295             HASH => {
296 25     25   693 escape => \sub { $self->_escape(@_) },
297 12     12   79 glob_escape => \sub { $self->_glob_escape(@_) },
298 1         20 package => \$self,
299             date => $self->_date($self->last_modification),
300             %vars
301             },
302             );
303              
304 1         135 return $text;
305             }
306              
307             sub spec {
308 1     1 1 5 my ($self, %vars) = @_;
309              
310 1         43 my $template = Text::Template->new(
311             DELIMITERS => ['[%', '%]'],
312             TYPE => 'STRING',
313             SOURCE => <<'END_TEMPLATE');
314             Name: [% $escape->($package->name) %]
315             Version: [% $escape->($package->version) %]
316             Release: [% $escape->($package->release) %]
317             Summary: [% $escape->($package->summary) %]
318             License: [% $escape->($package->license) %]
319             Packager: [% $escape->($package->packager) %]
320             Vendor: [% $escape->($package->vendor) %]
321             URL: [% $escape->($package->url) %]
322             [%
323             use Config;
324              
325             my $perl_version = $Config{version};
326             my $perl_vendorlib = $Config{installvendorlib};
327              
328             my $distdir = "$perl_vendorlib/auto/share/dist/CPANPLUS-Dist-Debora";
329              
330             my $epoch = $package->epoch;
331             if ($epoch) {
332             $OUT .= 'Epoch: ' . $escape->($epoch). "\n";
333             }
334              
335             my $distribution = $package->distribution;
336             if ($distribution) {
337             $OUT .= '%global distribution '. $escape->($distribution) . "\n";
338             }
339              
340             if ($package->is_noarch) {
341             $OUT .= "BuildArch: noarch\n";
342             }
343              
344             # See "Renaming/Replacing or Removing Existing Packages" in the Fedora
345             # documentation.
346             my $evr = $package->version . q{-} . $package->release;
347             if ($epoch) {
348             $evr = $epoch . q{:} . $package->version;
349             }
350             my $escaped_evr = $escape->($evr);
351              
352             for my $name (@{$package->provides}) {
353             $OUT .= sprintf "Provides: %s\n", $escape->($name);
354             }
355              
356             for my $name (@{$package->obsoletes}) {
357             $OUT .= sprintf "Provides: %s = %s\n", $escape->($name), $escaped_evr;
358             $OUT .= sprintf "Obsoletes: %s < %s\n", $escape->($name), $escaped_evr;
359             }
360              
361             $OUT .= "AutoProv: 1\n";
362              
363             # We have to use an updated perl.prov on CentOS 7.
364             my $perl_prov = "$distdir/perl.prov";
365             if (-x $perl_prov) {
366             $OUT .= "%global __perllib_provides $perl_prov\n";
367             }
368              
369             # /usr/lib/rpm/perl.req finds too many circular, internal and optional
370             # dependencies, but we have to add shared library dependencies to
371             # architecture-dependent Perl distributions.
372             if ($package->is_noarch) {
373             $OUT .= "AutoReq: 0\n";
374             }
375             else {
376             if (@{$package->shared_objects} == 0) {
377             $OUT .= "%global debug_package %{nil}\n";
378             }
379             $OUT .= "%global __perl_requires /bin/true\n";
380             $OUT .= "%global __perllib_requires /bin/true\n";
381             $OUT .= "%global __perltest_requires /bin/true\n";
382             $OUT .= "AutoReq: 1\n";
383             }
384              
385             $OUT .= "%if 0%{?fedora} > 0 || 0%{?rhel} > 0 || 0%{?suse_version} > 0\n";
386             $OUT .= 'Requires: perl(:MODULE_COMPAT_' . $escape->($perl_version) . ")\n";
387             $OUT .= "%endif\n";
388             for my $dependency (@{$package->dependencies}) {
389             if ($dependency->{is_module}) {
390             $OUT .= 'Requires: perl(' . $escape->($dependency->{module_name}) . ')';
391             }
392             else {
393             $OUT .= 'Requires: $escape->($dependency->{package_name})';
394             }
395             if ($dependency->{version}) {
396             $OUT .= ' >= ' . $escape->($dependency->{version});
397             }
398             $OUT .= "\n";
399             }
400             q{};
401             %]
402             %{?perl_default_filter}
403              
404             %description
405             [%
406             local $Text::Wrap::unexpand = 0;
407             $escape->(Text::Wrap::wrap(q{}, q{}, $package->description))
408             %]
409              
410             %{?debug_package}
411              
412             %prep
413              
414             %build
415              
416             %check
417              
418             %install
419             tar -C '[% $escape->($package->stagingdir) %]' -cf - . | tar -C %{buildroot} -xf -
420              
421             %clean
422              
423             %files
424             %defattr(-, root, root)
425             [%
426             my %format = (
427             'changelog' => '%%doc %s',
428             'config' => '%%config(noreplace) %s',
429             'dir' => '%%dir %s',
430             'doc' => '%%doc %s',
431             'license' => '%%license %s',
432             'man' => '%s*',
433             );
434             for my $file (@{$package->files}) {
435             my $name = $file->{name};
436             my $type = $file->{type};
437             if (exists $format{$type}) {
438             $OUT .= sprintf $format{$type}, $glob_escape->($name);
439             }
440             else {
441             $OUT .= $glob_escape->($name);
442             }
443             $OUT .= "\n";
444             }
445             q{};
446             %]
447             %changelog
448             * [% $date %] [% $escape->($package->packager) %] - [% $escape->($package->version) %]-[% $escape->($package->build_number) %]
449             - Package [% $escape->($package->dist_name) %] [% $escape->($package->version) %]
450             END_TEMPLATE
451              
452 1         229 my $text = $self->_fill_in($template, %vars);
453              
454 1         21 return $text;
455             }
456              
457             sub _get_rpmdir {
458 1     1   3 my $self = shift;
459              
460 1         15 my $topdir = $self->rpm_eval('%{?_topdir}');
461              
462 1 50       4 if (!$topdir) {
463 1         4 my $homedir = $ENV{HOME};
464 1 50       3 if ($homedir) {
465 1         6 $topdir = catdir($homedir, 'rpmbuild');
466             }
467             }
468              
469 1 50       4 if (!$topdir) {
470 0         0 $topdir = $self->outputdir;
471             }
472              
473 1         5 my $rpmdir = catdir($topdir, 'RPMS');
474              
475 1         4 return $rpmdir;
476             }
477              
478             sub _get_epoch_from_env {
479 1     1   5 my $self = shift;
480              
481 1         2 my $epoch = 0;
482 1 50 33     7 if (defined $ENV{EPOCH} && $ENV{EPOCH} =~ m{\A \d+ \z}xms) {
483 0         0 $epoch = $ENV{EPOCH};
484             }
485              
486 1         3 return $epoch;
487             }
488              
489             sub _get_epoch_from_system {
490 1     1   2 my $self = shift;
491              
492 1         2 my $epoch = 0;
493 1         4 my $rpm_cmd = $self->rpm_cmd;
494 1 50       80 if ($rpm_cmd) {
495 0         0 my @query_cmd = ($rpm_cmd, '-q', '--qf', '%{EPOCH}', $self->name);
496 0         0 my $output = q{};
497              
498             my $ok = run(
499             command => \@query_cmd,
500             buffer => \$output,
501       0     on_error => sub { }
502 0         0 );
503 0 0       0 if ($ok) {
504 0         0 chomp $output;
505 0 0       0 if ($output =~ m{\A \d+ \z}xms) {
506 0         0 $epoch = $output;
507             }
508             }
509             }
510              
511 1         3 return $epoch;
512             }
513              
514             sub _get_epoch {
515 1     1   3 my $self = shift;
516              
517 1         10 my $epoch_env = $self->_get_epoch_from_env;
518 1         7 my $epoch_sys = $self->_get_epoch_from_system;
519 1 50       5 my $epoch = $epoch_env > $epoch_sys ? $epoch_env : $epoch_sys;
520              
521 1         4 return $epoch;
522             }
523              
524             sub _get_distribution {
525 1     1   2 my $self = shift;
526              
527             # Values with escaped characters are deliberately ignored.
528 1         6 my $BRACKETED_REST = qr{[(] [^\\"]*}xms;
529 1         48 my $PRETTY_NAME
530             = qr{^ PRETTY_NAME = " ([^\\"]+?) \h* (?:$BRACKETED_REST)? " $}xms;
531              
532 1         6 my $distribution = $self->rpm_eval('%{?distribution}');
533 1 50       4 if (!$distribution) {
534             OS_RELEASE:
535 1         3 for my $filename (grep {-f} qw(/etc/os-release /usr/lib/os-release)) {
  2         112  
536 1         4 my $os_release = eval { slurp_utf8($filename) };
  1         5  
537 1 50 33     54 if ($os_release && $os_release =~ $PRETTY_NAME) {
538 1         7 $distribution = $1;
539 1         4 last OS_RELEASE;
540             }
541             }
542             }
543              
544 1         7 return $distribution;
545             }
546              
547             1;
548             __END__