File Coverage

blib/lib/CPANPLUS/Dist/Fedora.pm
Criterion Covered Total %
statement 42 270 15.5
branch 0 50 0.0
condition 0 6 0.0
subroutine 14 36 38.8
pod 5 5 100.0
total 61 367 16.6


line stmt bran cond sub pod time code
1             #
2             # This file is part of CPANPLUS::Dist::Fedora.
3             # Copyright (c) 2007 Jerome Quelin, all rights reserved.
4             #
5             # This program is free software; you can redistribute it and/or modify
6             # it under the same terms as Perl itself.
7             #
8              
9             package CPANPLUS::Dist::Fedora;
10             $CPANPLUS::Dist::Fedora::VERSION = '0.4.2';
11 2     2   496248 use strict;
  2         9  
  2         66  
12 2     2   30 use warnings;
  2         5  
  2         66  
13              
14 2     2   501 use parent 'CPANPLUS::Dist::Base';
  2         286  
  2         15  
15              
16 2     2   100579 use Cwd qw[ cwd ];
  2         4  
  2         103  
17 2     2   13 use CPANPLUS::Error qw[ error msg ];
  2         4  
  2         98  
18 2     2   12 use File::Basename qw[ basename dirname ];
  2         4  
  2         106  
19 2     2   547 use File::Copy qw[ copy ];
  2         2392  
  2         107  
20 2     2   14 use IPC::Cmd qw[ run can_run ];
  2         4  
  2         108  
21 2     2   14 use List::Util qw[ first min ];
  2         4  
  2         201  
22 2     2   1627 use Path::Tiny qw[ path ];
  2         22908  
  2         127  
23 2     2   1346 use Pod::POM ();
  2         43712  
  2         50  
24 2     2   1018 use Pod::POM::View::Text ();
  2         10085  
  2         55  
25 2     2   38 use POSIX qw[ strftime ];
  2         17  
  2         19  
26 2     2   2208 use Template ();
  2         39995  
  2         6278  
27              
28             $CPANPLUS::Dist::Fedora::_testme = 0;
29              
30             sub _get_spec_perl_exe
31             {
32 0     0     return 'perl';
33             }
34              
35             sub _get_spec_template
36             {
37             # Dealing with DATA gets increasingly messy, IMHO
38             # So we're going to use the Template Toolkit instead
39 0     0     return <<'END_SPEC';
40             [% SET makefile_pl_common = "INSTALLDIRS=vendor NO_PERLLOCAL=1 NO_PACKLIST=1" %]
41             [% BLOCK rpm_req_wrap %][%- rpm_prefix %] [% rpm_req(br) -%][%- IF (brs.$br != 0) %] >= [% brs.$br %][% END -%][%- "\n" -%][% END %]
42             Name: [% status.rpmname %]
43             Version: [% status.distvers %]
44             Release: [% status.rpmvers %]%{?dist}
45             License: [% status.license %]
46             Group: Development/Libraries
47             Summary: [% status.summary %]
48             Source: http://search.cpan.org/CPAN/[% module.path %]/[% status.distname %]-%{version}.[% module.package_extension %]
49             Url: http://metacpan.org/release/[% status.distname %]
50             Requires: perl(:MODULE_COMPAT_%(eval "`[% perl_exe %] -V:version`"; echo $version))
51             [% IF status.is_noarch %]BuildArch: noarch[% END %]
52             [% brs = buildreqs; FOREACH br = brs.keys.sort -%]
53             [% INCLUDE rpm_req_wrap br = br , rpm_prefix = "Requires:" %]
54             [% END -%]
55             BuildRequires: coreutils
56             BuildRequires: make
57             BuildRequires: perl(ExtUtils::MakeMaker)
58             BuildRequires: perl-devel
59             BuildRequires: perl-generators
60             BuildRequires: perl-interpreter
61             [% FOREACH br = brs.keys.sort -%]
62             [% INCLUDE rpm_req_wrap br = br , rpm_prefix = "BuildRequires:" %]
63             [% END -%]
64              
65              
66             %description
67             [% status.description -%]
68              
69              
70             %prep
71             %setup -q -n [% status.distname %]-%{version}
72              
73             %build
74             [% IF (!status.is_noarch) -%]
75             [% perl_exe %] Makefile.PL [% makefile_pl_common %] OPTIMIZE="%{optflags}"
76             [% ELSE -%]
77             [% perl_exe %] Makefile.PL [% makefile_pl_common %]
78             [% END -%]
79             %{make_build}
80              
81             %install
82             %{make_install}
83             [% IF 0 -%]
84             find %{buildroot} -type f \( -name .packlist -o -name perllocal.pod \) -exec rm -f {} ';'
85             [%- END -%]
86             [% IF (!status.is_noarch) -%]
87             find %{buildroot} -type f -name '*.bs' -a -size 0 -exec rm -f {} ';'
88             [% END -%]
89             find %{buildroot} -depth -type d -exec rmdir {} 2>/dev/null ';'
90              
91             %{_fixperms} %{buildroot}/*
92              
93             %check
94             make test
95              
96             %files
97             [%- IF 0 -%]
98             %defattr(-,root,root,-)[% "\n" %]
99             [%- END -%]
100             [%- IF licensefiles %]%license [% licensefiles _ "\n" %][%- END -%]
101             %doc [% docfiles %]
102             [% IF (status.is_noarch) -%]
103             %{perl_vendorlib}/*
104             [% ELSE -%]
105             %{perl_vendorarch}/*
106             %exclude %dir %{perl_vendorarch}/auto
107             [% END -%]
108             %{_mandir}/man3/*.3*
109             [% distextra %]
110              
111             %changelog
112             * [% date %] [% packager %] [% status.distvers %]-[% status.rpmvers %]
113             - initial Fedora packaging
114             - generated with cpan2dist (CPANPLUS::Dist::Fedora version [% packagervers %])
115             END_SPEC
116             }
117              
118             #--
119             # class methods
120              
121             #
122             # my $bool = CPANPLUS::Dist::Fedora->format_available;
123             #
124             # Return a boolean indicating whether or not you can use this package to
125             # create and install modules in your environment.
126             #
127             sub format_available
128             {
129             # Check Fedora release file
130 0 0 0 0 1   if ( not( -f '/etc/fedora-release' or -f '/etc/redhat-release' ) )
131             {
132 0           error('Not on a Fedora system');
133 0           return;
134             }
135              
136 0           my $flag;
137              
138             # check prereqs
139 0           for my $prog (qw[ rpm rpmbuild gcc ])
140             {
141 0 0         next if can_run($prog);
142 0           error("'$prog' is a required program to build Fedora packages");
143 0           $flag++;
144             }
145              
146 0           return not $flag;
147             }
148              
149             #--
150             # public methods
151              
152             #
153             # my $bool = $fedora->init;
154             #
155             # Sets up the C object for use, and return true if
156             # everything went fine.
157             #
158             sub init
159             {
160 0     0 1   my ($self) = @_;
161 0           my $status = $self->status; # an Object::Accessor
162             # distname: Foo-Bar
163             # distvers: 1.23
164             # extra_files: qw[ /bin/foo /usr/bin/bar ]
165             # rpmname: perl-Foo-Bar
166             # rpmpath: $RPMDIR/RPMS/noarch/perl-Foo-Bar-1.23-1mdv2008.0.noarch.rpm
167             # rpmvers: 1
168             # rpmdir: $DIR
169             # srpmpath: $RPMDIR/SRPMS/perl-Foo-Bar-1.23-1mdv2008.0.src.rpm
170             # specpath: $RPMDIR/SPECS/perl-Foo-Bar.spec
171             # is_noarch: true if pure-perl
172             # license: try to figure out the actual license
173             # summary: one-liner summary
174             # description: a paragraph summary or so
175 0           $status->mk_accessors(
176             qw[ distname distvers extra_files rpmname rpmpath rpmvers rpmdir
177             srpmpath specpath is_noarch license summary description
178             ]
179             );
180              
181             # This is done to initialise it.
182 0           $self->_get_current_dir();
183              
184 0           return 1;
185             }
186              
187             sub _calc_spec_text
188             {
189 0     0     my $self = shift;
190 0           my $module = $self->parent; # CPANPLUS::Module
191 0           my $tmpl = Template->new( { EVAL_PERL => 1 } );
192 0           my $status = $self->status; # Private hash
193              
194 0           my @files = @{ $module->status->files };
  0            
195              
196             my @basenames =
197 0           map { basename $_ } @files;
  0            
198              
199             # Files for %doc
200             my @docfiles =
201 0           grep { /(?:README|Change(?:s|log))$/i } @basenames;
  0            
202              
203             my @licensefiles =
204 0           grep { /(?:LICENSE)$/i } @basenames;
  0            
205 0           my $spec_template = $self->_get_spec_template();
206              
207 0           my $spec_text = '';
208              
209             # Handle build/test/requires
210 0           my $buildreqs = $module->status->prereqs;
211 0 0         $buildreqs->{'Module::Build::Compat'} = 0
212             if _is_module_build_compat($module);
213              
214             # Process template into spec
215             $tmpl->process(
216             \$spec_template,
217             {
218             status => $status,
219             module => $module,
220             buildreqs => $buildreqs,
221             date => strftime( "%a %b %d %Y", localtime ),
222             perl_exe => $self->_get_spec_perl_exe(),
223             packager => $self->_get_packager(),
224             docfiles => join( ' ', @docfiles ),
225             licensefiles => join( ' ', @licensefiles ),
226             rpm_req => sub {
227 0     0     my $br = shift;
228 0 0         return ( ( $br eq 'perl' ) ? $br : "perl($br)" );
229             },
230              
231             packagervers => $CPANPLUS::Dist::Fedora::VERSION,
232 0 0         distextra => join( "\n", @{ $status->extra_files || [] } ),
  0            
233             },
234             \$spec_text,
235             );
236              
237 0           my $ret = +{ text => $spec_text, };
238 0 0         if ($CPANPLUS::Dist::Fedora::_testme)
239             {
240 0           die $ret;
241             }
242 0           return $ret;
243             }
244              
245             sub prepare
246             {
247 0     0 1   my ( $self, %args ) = @_;
248 0           msg("dry-run prepare with makemaker...");
249 0           $self->SUPER::prepare(%args);
250 0           my $status = $self->status; # Private hash
251 0           my $module = $self->parent; # CPANPLUS::Module
252 0           my $intern = $module->parent; # CPANPLUS::Internals
253 0           my $conf = $intern->configure_object; # CPANPLUS::Configure
254 0           my $distmm = $module->status->dist_cpan; # CPANPLUS::Dist::MM
255              
256             # Parse args.
257 0           my %opts = (
258             force => $conf->get_conf('force'), # force rebuild
259             perl => $^X,
260             verbose => $conf->get_conf('verbose'),
261             %args,
262             );
263              
264             # Dry-run with makemaker: find build prereqs.
265 0           if (0)
266             {
267             msg("dry-run prepare with makemaker...");
268             $self->SUPER::prepare(%args);
269             }
270              
271             # Compute & store package information
272 0           my $distname = $module->package_name;
273 0           $status->distname($distname);
274 0           $status->distvers( $module->package_version );
275 0           $status->summary( _module_summary($module) );
276 0           $status->description( _module_description($module) );
277 0           $status->license( $self->_module_license($module) );
278              
279             #$status->disttop($module->name=~ /([^:]+)::/);
280 0           my $dir = $status->rpmdir( $self->_get_current_dir() );
281 0           $status->rpmvers(1);
282              
283             # Cache files
284 0           my @files = @{ $module->status->files };
  0            
285              
286             # Figure out if we're noarch or not
287             $status->is_noarch(
288             do
289 0 0         {
290 0     0     first { /\.(c|xs)$/i } @files;
  0            
291             }
292             ? 0
293             : 1
294             );
295              
296 0           my $rpmname = _mk_pkg_name($distname);
297 0           $status->rpmname($rpmname);
298              
299             # check whether package has been build.
300 0 0         if ( my $pkg = $self->_has_been_built( $rpmname, $status->distvers ) )
301             {
302 0           my $modname = $module->module;
303 0           msg("already created package for '$modname' at '$pkg'");
304              
305 0 0         if ( not $opts{force} )
306             {
307 0           msg("won't re-spec package since --force isn't in use");
308              
309             # c::d::mdv store
310 0           $status->rpmpath($pkg); # store the path of rpm
311             # cpanplus api
312 0           $status->prepared(1);
313 0           $status->created(1);
314 0           $status->dist($pkg);
315 0           return $pkg;
316              
317             # XXX check if it works
318             }
319              
320 0           msg('--force in use, re-specing anyway');
321              
322             # FIXME: bump rpm version
323             }
324             else
325             {
326 0           msg("writing specfile for '$distname'...");
327             }
328              
329             # Compute & store path of specfile.
330 0           $status->specpath("$dir/$rpmname.spec");
331              
332             # Prepare our template
333 0           my $text = $self->_calc_spec_text()->{text};
334 0           my $specpath = path( $status->specpath );
335 0           $specpath->spew_utf8($text);
336 0           print "spec file written $specpath\n";
337              
338 0 0         if ( $intern->_callbacks->munge_dist_metafile )
339             {
340 0           print 'munging...';
341              
342 0           my $orig_contents = _read_file( $status->specpath );
343 0           my $new_contents = $intern->_callbacks->munge_dist_metafile->(
344             $intern, $orig_contents
345             );
346 0           _write_file( $status->specpath, $new_contents );
347             }
348              
349             # copy package.
350 0           my $tarball = "$dir/" . basename $module->status->fetch;
351 0           copy $module->status->fetch, $tarball;
352              
353 0           msg("specfile for '$distname' written");
354              
355             # return success
356 0           $status->prepared(1);
357 0           return 1;
358             }
359              
360             sub create
361             {
362 0     0 1   my ( $self, %args ) = @_;
363 0           my $status = $self->status; # private hash
364 0           my $module = $self->parent; # CPANPLUS::Module
365 0           my $intern = $module->parent; # CPANPLUS::Internals
366 0           my $conf = $intern->configure_object; # CPANPLUS::Configure
367 0           my $distmm = $module->status->dist_cpan; # CPANPLUS::Dist::MM
368              
369             # parse args.
370 0           my %opts = (
371             force => $conf->get_conf('force'), # force rebuild
372             perl => $^X,
373             verbose => $conf->get_conf('verbose'),
374             %args,
375             );
376              
377             # check if we need to rebuild package.
378 0 0 0       if ( $status->created && defined $status->dist )
379             {
380 0 0         if ( not $opts{force} )
381             {
382 0           msg("won't re-build package since --force isn't in use");
383 0           return $status->dist;
384             }
385 0           msg('--force in use, re-building anyway');
386             }
387              
388             RPMBUILD:
389             {
390             # dry-run with makemaker: handle prereqs.
391 0           msg('dry-run build with makemaker...');
  0            
392 0           $self->SUPER::create(%args);
393              
394 0           my $spec = $status->specpath;
395 0           my $distname = $status->distname;
396 0           my $rpmname = $status->rpmname;
397              
398 0           msg("Building '$distname' from specfile $spec...");
399              
400             # dry-run, to see if we forgot some files
401 0           my ( $buffer, $success );
402 0           my $dir = $status->rpmdir;
403             DRYRUN:
404             {
405 0           local $ENV{LC_ALL} = 'C';
  0            
406             $success = run(
407              
408             #command => "rpmbuild -ba --quiet $spec",
409             command => 'rpmbuild -ba '
410             . qq{--define '_sourcedir $dir' }
411             . qq{--define '_builddir $dir' }
412             . qq{--define '_srcrpmdir $dir' }
413             . qq{--define '_rpmdir $dir' }
414             . $spec,
415             verbose => $opts{verbose},
416 0           buffer => \$buffer,
417             );
418             }
419              
420             # check if the dry-run finished correctly
421 0 0         if ($success)
422             {
423 0           my ($rpm) = ( sort glob "$dir/*/$rpmname-*.rpm" )[-1];
424 0           my ($srpm) = ( sort glob "$dir/$rpmname-*.src.rpm" )[-1];
425 0           msg("RPM created successfully: $rpm");
426 0           msg("SRPM available: $srpm");
427              
428             # c::d::mdv store
429 0           $status->rpmpath($rpm);
430 0           $status->srpmpath($srpm);
431              
432             # cpanplus api
433 0           $status->created(1);
434 0           $status->dist($rpm);
435 0           return $rpm;
436             }
437              
438             # unknown error, aborting.
439 0 0         if (
440             not $buffer =~
441             /^\s+Installed .but unpackaged. file.s. found:\n(.*)\z/ms )
442             {
443 0           error("Failed to create Fedora package for '$distname': $buffer");
444              
445             # cpanplus api
446 0           $status->created(0);
447 0           return;
448             }
449              
450             # additional files to be packaged
451 0           msg("extra files installed, fixing spec file");
452 0           my $files = $1;
453 0           $files =~ s/^\s+//mg; # remove spaces
454 0           my @files = split /\n/, $files;
455 0           $status->extra_files( \@files );
456 0           $self->prepare( %opts, force => 1 );
457 0           msg('restarting build phase');
458 0           redo RPMBUILD;
459             }
460             }
461              
462             sub install
463             {
464 0     0 1   my ( $self, %args ) = @_;
465 0           my $rpm = $self->status->rpm;
466 0           error("installing $rpm");
467 0           die;
468              
469             #$dist->status->installed
470             }
471              
472             #--
473             # Private methods:
474              
475             sub _read_file
476             {
477 0     0     my ($filename) = @_;
478 0           open my $fh, '< :encoding(utf8)', $filename;
479 0           local $/;
480 0           my $contents = <$fh>;
481 0           close($fh);
482              
483 0           return $contents;
484             }
485              
486             sub _write_file
487             {
488 0     0     my ( $filename, $contents ) = @_;
489 0           open my $fh, '> :encoding(utf8)', $filename;
490 0           print {$fh} $contents;
  0            
491 0           close($fh);
492              
493 0           return;
494             }
495              
496             #
497             # my $bool = $self->_has_been_built;
498             #
499             # Returns true if there's already a package built for this module.
500             #
501             sub _has_been_built
502             {
503 0     0     my ( $self, $name, $vers ) = @_;
504 0           my $RPMDIR = $self->_get_RPMDIR();
505 0           my $pkg = ( sort glob "$RPMDIR/RPMS/*/$name-$vers-*.rpm" )[-1];
506 0           return $pkg;
507              
508             # FIXME: should we check cooker?
509             }
510              
511             #--
512             # Private subs
513              
514             sub _is_module_build_compat
515             {
516 0     0     my ($module) = @_;
517 0           my $makefile = $module->_status->extract . '/Makefile.PL';
518              
519 0           open my $mk_fh, "<", $makefile;
520              
521 0           my $found = 0;
522              
523             LINES:
524 0           while ( my $line = <$mk_fh> )
525             {
526 0 0         if ( $line =~ /Module::Build::Compat/ )
527             {
528 0           $found = 1;
529 0           last LINES;
530             }
531             }
532              
533 0           close($mk_fh);
534              
535 0           return $found;
536             }
537              
538             #
539             # my $name = _mk_pkg_name($dist);
540             #
541             # given a distribution name, return the name of the mandriva rpm
542             # package. in most cases, it will be the same, but some pakcage name
543             # will be too long as a rpm name: we'll have to cut it.
544             #
545             sub _mk_pkg_name
546             {
547 0     0     my ($dist) = @_;
548 0           my $name = 'perl-' . $dist;
549 0           return $name;
550             }
551              
552             # determine the module license.
553             #
554             # FIXME! for now just return the default licence
555              
556             sub _module_license
557             {
558 0     0     my $self = shift;
559 0           my $module = shift;
560              
561 0           return $self->_get_default_license();
562             }
563              
564             sub _get_default_license
565             {
566 0     0     return 'CHECK(GPL+ or Artistic)';
567             }
568              
569             #
570             # my $description = _module_description($module);
571             #
572             # given a cpanplus::module, try to extract its description from the
573             # embedded pod in the extracted files. this would be the first paragraph
574             # of the DESCRIPTION head1.
575             #
576             sub _module_description
577             {
578 0     0     my ($module) = @_;
579              
580 0           my $path =
581             dirname $module->_status->extract; # where tarball has been extracted
582             my @docfiles =
583 0           map { "$path/$_" } # prepend extract directory
584 0           sort { length $a <=> length $b } # sort by length: we prefer top-level module description
585 0           grep { /\.(pod|pm)$/ } # filter out those that can contain pod
586 0           @{ $module->_status->files }; # list of embedded files
  0            
587              
588             # parse file, trying to find a header
589 0           my $parser = Pod::POM->new;
590             DOCFILE:
591 0           foreach my $docfile (@docfiles)
592             {
593 0           my $pom = $parser->parse_file($docfile); # try to find some pod
594             next DOCFILE
595 0 0         unless defined $pom; # the file may contain no pod, that's ok
596             HEAD1:
597 0           foreach my $head1 ( $pom->head1 )
598             {
599 0 0         next HEAD1 unless $head1->title eq 'DESCRIPTION';
600 0           my $pom = $head1->content; # get pod for DESCRIPTION paragraph
601 0           my $text =
602             $pom->present('Pod::POM::View::Text'); # transform pod to text
603 0           my @paragraphs = ( split /\n\n/, $text );
604 0           @paragraphs = @paragraphs[ 0 .. min( $#paragraphs, 2 ) ]
605             ; # only the 3 first paragraphs
606 0           return join "\n\n", @paragraphs;
607             }
608             }
609              
610 0           return 'no description found';
611             }
612              
613             #
614             # my $summary = _module_summary($module);
615             #
616             # Given a CPANPLUS::Module, return its registered description (if any)
617             # or try to extract it from the embedded POD in the extracted files.
618             #
619             sub _module_summary
620             {
621 0     0     my ($module) = @_;
622              
623             # registered modules won't go farther...
624 0 0         return $module->description if $module->description;
625              
626 0           my $path =
627             dirname $module->_status->extract; # where tarball has been extracted
628             my @docfiles =
629 0           map { "$path/$_" } # prepend extract directory
630 0           sort { length $a <=> length $b } # sort by length: we prefer top-level module summary
631 0           grep { /\.(pod|pm)$/ } # filter out those that can contain pod
632 0           @{ $module->_status->files }; # list of files embedded
  0            
633              
634             # parse file, trying to find a header
635 0           my $parser = Pod::POM->new;
636             DOCFILE:
637 0           foreach my $docfile (@docfiles)
638             {
639 0           my $pom = $parser->parse_file($docfile); # try to find some pod
640 0 0         next unless defined $pom; # the file may contain no pod, that's ok
641             HEAD1:
642 0           foreach my $head1 ( $pom->head1 )
643             {
644 0           my $title = $head1->title;
645 0 0         next HEAD1 unless $title eq 'NAME';
646 0           my $content = $head1->content;
647 0 0         next DOCFILE unless $content =~ /^[^-]+ - (.*)$/m;
648 0 0         return $1 if $content;
649             }
650             }
651              
652 0           return 'no summary found';
653             }
654              
655             sub _get_RPMDIR
656             {
657 0     0     my $self = shift;
658              
659             # Memoize it.
660 0 0         if ( !defined( $self->{_RPMDIR} ) )
661             {
662 0           chomp( my $d = qx[ rpm --eval %_topdir ] );
663 0           $self->{_RPMDIR} = $d;
664             }
665              
666 0           return $self->{_RPMDIR};
667             }
668              
669             sub _get_packager
670             {
671 0     0     my $self = shift;
672              
673             # Memoize it.
674 0 0         if ( !defined( $self->{_packager} ) )
675             {
676 0           my $d = `rpm --eval '%{packager}'`;
677 0           chomp $d;
678 0           $self->{_packager} = $d;
679             }
680              
681 0           return $self->{_packager};
682             }
683              
684             sub _get_current_dir
685             {
686 0     0     my $self = shift;
687              
688             # Memoize it.
689 0 0         if ( !defined( $self->{_current_dir} ) )
690             {
691 0           $self->{_current_dir} = cwd();
692             }
693              
694 0           return $self->{_current_dir};
695             }
696              
697             1;
698              
699             __END__