File Coverage

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


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