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