File Coverage

blib/lib/CPANPLUS/Dist/Fedora.pm
Criterion Covered Total %
statement 42 250 16.8
branch 0 48 0.0
condition 0 6 0.0
subroutine 14 34 41.1
pod 5 5 100.0
total 61 343 17.7


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