]> git.vpit.fr Git - perl/modules/CPANPLUS-Dist-Gentoo.git/blob - lib/CPANPLUS/Dist/Gentoo.pm
Put the META extraction logic into a separate ->meta method
[perl/modules/CPANPLUS-Dist-Gentoo.git] / lib / CPANPLUS / Dist / Gentoo.pm
1 package CPANPLUS::Dist::Gentoo;
2
3 use strict;
4 use warnings;
5
6 use Cwd        ();
7 use List::Util qw/reduce/;
8 use File::Copy ();
9 use File::Path ();
10 use File::Spec;
11
12 use IPC::Cmd          ();
13 use Parse::CPAN::Meta ();
14
15 use CPANPLUS::Error ();
16
17 use base qw/CPANPLUS::Dist::Base/;
18
19 use CPANPLUS::Dist::Gentoo::Atom;
20 use CPANPLUS::Dist::Gentoo::Maps;
21
22 =head1 NAME
23
24 CPANPLUS::Dist::Gentoo - CPANPLUS backend generating Gentoo ebuilds.
25
26 =head1 VERSION
27
28 Version 0.09
29
30 =cut
31
32 our $VERSION = '0.09';
33
34 =head1 SYNOPSIS
35
36     cpan2dist --format=CPANPLUS::Dist::Gentoo \
37               --dist-opts overlay=/usr/local/portage \
38               --dist-opts distdir=/usr/portage/distfiles \
39               --dist-opts manifest=yes \
40               --dist-opts keywords=x86 \
41               --dist-opts header="# Copyright 1999-2008 Gentoo Foundation" \
42               --dist-opts footer="# End" \
43               Any::Module You::Like
44
45 =head1 DESCRPITON
46
47 This module is a CPANPLUS backend that recursively generates Gentoo ebuilds for a given package in the specified overlay (defaults to F</usr/local/portage>), updates the manifest, and even emerges it (together with its dependencies) if the user requires it.
48 You need write permissions on the directory where Gentoo fetches its source files (usually F</usr/portage/distfiles>).
49 The valid C<KEYWORDS> for the generated ebuilds are by default those given in C<ACCEPT_KEYWORDS>, but you can specify your own with the C<keywords> dist-option.
50
51 The generated ebuilds are placed into the C<perl-gcpanp> category.
52 They favour depending on a C<virtual>, on C<perl-core>, C<dev-perl> or C<perl-gcpan> (in that order) rather than C<perl-gcpanp>.
53
54 =head1 INSTALLATION
55
56 Before installing this module, you should append C<perl-gcpanp> to your F</etc/portage/categories> file.
57
58 You have two ways for installing this module :
59
60 =over 4
61
62 =item *
63
64 Use the perl overlay located at L<http://git.overlays.gentoo.org/gitweb/?p=proj/perl-overlay.git>.
65 It contains an ebuild for L<CPANPLUS::Dist::Gentoo>.
66
67 =item *
68
69 Bootstrap an ebuild for L<CPANPLUS::Dist::Gentoo> using itself.
70 Note that if your Gentoo system C<perl> is C<5.8.x>, L<CPANPLUS> and its dependencies are not installed and not even available in the main portage tree.
71 So you need to bootstrap them as well.
72
73 First, fetch tarballs for L<CPANPLUS> and L<CPANPLUS::Dist::Gentoo> :
74
75     $ cd /tmp
76     $ wget http://search.cpan.org/CPAN/authors/id/B/BI/BINGOS/CPANPLUS-0.9003.tar.gz
77     $ wget http://search.cpan.org/CPAN/authors/id/V/VP/VPIT/CPANPLUS-Dist-Gentoo-0.09.tar.gz
78
79 Log in as root and unpack them in e.g. your home directory :
80
81     # cd
82     # tar xzf /tmp/CPANPLUS-0.9003.tar.gz
83     # tar xzf /tmp/CPANPLUS-Dist-Gentoo-0.09.tar.gz
84
85 Set up environment variables so that the toolchain is temporarily available :
86
87     # export OLDPATH=$PATH
88     # export PATH=/root/CPANPLUS-0.9003/bin:$PATH
89     # export PERL5LIB=/root/CPANPLUS-Dist-Gentoo-0.09/blib/lib:/root/CPANPLUS-0.9003/lib:/root/CPANPLUS-0.9003/inc/bundle
90
91 Make sure you don't have an old C<.cpanplus> configuration visible :
92
93     # [ -d /root/.cpanplus ] && mv /root/.cpanplus{,.bak}
94
95 Bootstrap L<CPANPLUS> :
96
97     # cd /root/CPANPLUS-Dist-Gentoo-0.09
98     # samples/g-cpanp CPANPLUS
99
100 Reset the environment :
101
102     # export PATH=$OLDPATH
103     # unset PERL5LIB OLDPATH
104
105 Emerge L<CPANPLUS> with the ebuilds you've just generated :
106
107     # emerge -tv CPANPLUS
108
109 As of september 2009, C<podlators> and C<ExtUtils-MakeMaker> may fail to emerge due to collisions.
110 You can work around this by disabling the C<protect-owned> C<FEATURE> for them :
111
112     # FEATURES="-protect-owned" emerge podlators
113     # FEATURES="-protect-owned" emerge ExtUtils-MakeMaker
114
115 You may need to run each of these commands two times for them to succeed.
116
117 At this point, you can bootstrap L<CPANPLUS::Dist::Gentoo> using the system L<CPANPLUS> :
118
119     # PERL5LIB=/root/CPANPLUS-Dist-Gentoo-0.09/blib/lib samples/g-cpanp CPANPLUS::Dist::Gentoo
120     # emerge -tv CPANPLUS-Dist-Gentoo
121
122 =back
123
124 =head1 METHODS
125
126 This module inherits all the methods from L<CPANPLUS::Dist::Base>.
127 Please refer to its documentation for precise information on what's done at each step.
128
129 =cut
130
131 use constant CATEGORY => 'perl-gcpanp';
132
133 my $overlays;
134 my $default_keywords;
135 my $default_distdir;
136 my $main_portdir;
137
138 my %forced;
139
140 my $unquote = sub {
141  my $s = shift;
142  $s =~ s/^["']*//;
143  $s =~ s/["']*$//;
144  return $s;
145 };
146
147 my $format_available;
148
149 sub format_available {
150  return $format_available if defined $format_available;
151
152  for my $prog (qw/emerge ebuild/) {
153   unless (IPC::Cmd::can_run($prog)) {
154    __PACKAGE__->_abort("$prog is required to write ebuilds");
155    return $format_available = 0;
156   }
157  }
158
159  if (IPC::Cmd->can_capture_buffer) {
160   my $buffers;
161   my ($success, $errmsg) = IPC::Cmd::run(
162    command => [ qw/emerge --info/ ],
163    verbose => 0,
164    buffer  => \$buffers,
165   );
166   if ($success) {
167    if ($buffers =~ /^PORTDIR_OVERLAY=(.*)$/m) {
168     $overlays = [ map Cwd::abs_path($_), split ' ', $unquote->($1) ];
169    }
170    if ($buffers =~ /^ACCEPT_KEYWORDS=(.*)$/m) {
171     $default_keywords = [ split ' ', $unquote->($1) ];
172    }
173    if ($buffers =~ /^DISTDIR=(.*)$/m) {
174     $default_distdir = Cwd::abs_path($unquote->($1));
175    }
176    if ($buffers =~ /^PORTDIR=(.*)$/m) {
177     $main_portdir = Cwd::abs_path($unquote->($1));
178    }
179   } else {
180    __PACKAGE__->_abort($errmsg);
181   }
182  }
183
184  $default_keywords = [ 'x86' ] unless defined $default_keywords;
185  $default_distdir  = '/usr/portage/distfiles' unless defined $default_distdir;
186
187  return $format_available = 1;
188 }
189
190 sub init {
191  my ($self) = @_;
192  my $stat = $self->status;
193  my $conf = $self->parent->parent->configure_object;
194
195  $stat->mk_accessors(qw/name version author distribution desc uri src license
196                         meta
197                         fetched_arch requires
198                         ebuild_name ebuild_version ebuild_dir ebuild_file
199                         portdir_overlay
200                         overlay distdir keywords do_manifest header footer
201                         force verbose/);
202
203  $stat->force($conf->get_conf('force'));
204  $stat->verbose($conf->get_conf('verbose'));
205
206  return 1;
207 }
208
209 sub prepare {
210  my $self = shift;
211  my $mod  = $self->parent;
212  my $stat = $self->status;
213  my $int  = $mod->parent;
214  my $conf = $int->configure_object;
215
216  my %opts = @_;
217
218  my $OK   = sub { $stat->prepared(1); 1 };
219  my $FAIL = sub { $stat->prepared(0); $self->_abort(@_) if @_; 0 };
220  my $SKIP = sub { $stat->prepared(1); $stat->created(1); $self->_skip(@_) if @_; 1 };
221
222  my $keywords = delete $opts{keywords};
223  if (defined $keywords) {
224   $keywords = [ split ' ', $keywords ];
225  } else {
226   $keywords = $default_keywords;
227  }
228  $stat->keywords($keywords);
229
230  my $manifest = delete $opts{manifest};
231  $manifest = 1 unless defined $manifest;
232  $manifest = 0 if $manifest =~ /^\s*no?\s*$/i;
233  $stat->do_manifest($manifest);
234
235  my $header = delete $opts{header};
236  if (defined $header) {
237   1 while chomp $header;
238   $header .= "\n\n";
239  } else {
240   my $year = (localtime)[5] + 1900;
241   $header = <<"  DEF_HEADER";
242 # Copyright 1999-$year Gentoo Foundation
243 # Distributed under the terms of the GNU General Public License v2
244 # \$Header: \$
245   DEF_HEADER
246  }
247  $stat->header($header);
248
249  my $footer = delete $opts{footer};
250  if (defined $footer) {
251   $footer = "\n" . $footer;
252  } else {
253   $footer = '';
254  }
255  $stat->footer($footer);
256
257  my $overlay = delete $opts{overlay};
258  $overlay = (defined $overlay) ? Cwd::abs_path($overlay) : '/usr/local/portage';
259  $stat->overlay($overlay);
260
261  my $distdir = delete $opts{distdir};
262  $distdir = (defined $distdir) ? Cwd::abs_path($distdir) : $default_distdir;
263  $stat->distdir($distdir);
264
265  return $FAIL->("distdir isn't writable") if $stat->do_manifest && !-w $distdir;
266
267  $stat->fetched_arch($mod->status->fetch);
268
269  my $cur = File::Spec->curdir();
270  my $portdir_overlay;
271  for (@$overlays) {
272   if ($_ eq $overlay or File::Spec->abs2rel($overlay, $_) eq $cur) {
273    $portdir_overlay = [ @$overlays ];
274    last;
275   }
276  }
277  $portdir_overlay = [ @$overlays, $overlay ] unless $portdir_overlay;
278  $stat->portdir_overlay($portdir_overlay);
279
280  my $name = $mod->package_name;
281  $stat->name($name);
282
283  my $version = $mod->package_version;
284  $stat->version($version);
285
286  my $author = $mod->author->cpanid;
287  $stat->author($author);
288
289  $stat->distribution($name . '-' . $version);
290
291  $stat->ebuild_version(CPANPLUS::Dist::Gentoo::Maps::version_c2g($version));
292
293  $stat->ebuild_name(CPANPLUS::Dist::Gentoo::Maps::name_c2g($name));
294
295  $stat->ebuild_dir(File::Spec->catdir(
296   $stat->overlay,
297   CATEGORY,
298   $stat->ebuild_name,
299  ));
300
301  my $file = File::Spec->catfile(
302   $stat->ebuild_dir,
303   $stat->ebuild_name . '-' . $stat->ebuild_version . '.ebuild',
304  );
305  $stat->ebuild_file($file);
306
307  if ($stat->force) {
308   # Always generate an ebuild in our category when forcing
309   if ($forced{$file}) {
310    $stat->dist($file);
311    return $SKIP->('Ebuild already forced for', $stat->distribution);
312   }
313   ++$forced{$file};
314   if (-e $file) {
315    unless (-w $file) {
316     $stat->dist($file);
317     return $SKIP->("Can't force rewriting of $file");
318    }
319    1 while unlink $file;
320   }
321  } else {
322   if (my $atom = $self->_cpan2portage($name, $version)) {
323    $stat->dist($atom->ebuild);
324    return $SKIP->('Ebuild already generated for', $stat->distribution);
325   }
326  }
327
328  $stat->prepared(0);
329
330  $self->SUPER::prepare(@_);
331
332  return $FAIL->() unless $stat->prepared;
333
334  my $desc = $mod->description;
335  ($desc = $name) =~ s/-+/::/g unless $desc;
336  $stat->desc($desc);
337
338  $stat->uri('http://search.cpan.org/dist/' . $name);
339
340  $author =~ /^(.)(.)/ or return $FAIL->('Wrong author name');
341  $stat->src("mirror://cpan/modules/by-authors/id/$1/$1$2/$author/" . $mod->package);
342
343  $stat->license($self->intuit_license);
344
345  my $prereqs = $mod->status->requires;
346  my @requires;
347  for my $prereq (sort keys %$prereqs) {
348   next if $prereq =~ /^perl(?:-|\z)/;
349   my $obj = $int->module_tree($prereq);
350   next unless $obj; # Not in the module tree (e.g. Config)
351   next if $obj->package_is_perl_core;
352   {
353    my $version;
354    if ($prereqs->{$prereq}) {
355     if ($obj->installed_version && $obj->installed_version < $obj->version) {
356      $version = $obj->installed_version;
357     } else {
358      $version = $obj->package_version;
359     }
360    }
361    push @requires, [ $obj->package_name, $version ];
362   }
363  }
364  $stat->requires(\@requires);
365
366  return $OK->();
367 }
368
369 =head2 C<meta>
370
371 Returns the contents of the F<META.yml> or F<META.json> files as parsed by L<Parse::CPAN::Meta>.
372
373 =cut
374
375 sub meta {
376  my $self = shift;
377  my $mod  = $self->parent;
378  my $stat = $self->status;
379
380  my $meta = $stat->meta;
381  return $meta if defined $meta;
382
383  my $extract_dir = $mod->status->extract;
384
385  for my $name (qw/META.json META.yml/) {
386   my $meta_file = File::Spec->catdir($extract_dir, $name);
387   next unless -e $meta_file;
388
389   my $meta = eval { Parse::CPAN::Meta::LoadFile($meta_file) };
390   if (defined $meta) {
391    $stat->meta($meta);
392    return $meta;
393   }
394  }
395
396  return;
397 }
398
399 =head2 C<intuit_license>
400
401 Returns an array reference to a list of Gentoo licences identifiers under which the current distribution is released.
402
403 =cut
404
405 my %dslip_license = (
406  p => 'perl',
407  g => 'gpl',
408  l => 'lgpl',
409  b => 'bsd',
410  a => 'artistic',
411  2 => 'artistic_2',
412 );
413
414 sub intuit_license {
415  my $self = shift;
416  my $mod  = $self->parent;
417
418  my $dslip = $mod->dslip;
419  if (defined $dslip and $dslip =~ /\S{4}(\S)/) {
420   my @licenses = CPANPLUS::Dist::Gentoo::Maps::license_c2g($dslip_license{$1});
421   return \@licenses if @licenses;
422  }
423
424  my $license = $self->meta->{license};
425  if (defined $license) {
426   my @licenses = CPANPLUS::Dist::Gentoo::Maps::license_c2g($license);
427   return \@licenses if @licenses;
428  }
429
430  return [ CPANPLUS::Dist::Gentoo::Maps::license_c2g('perl') ];
431 }
432
433 sub create {
434  my $self = shift;
435  my $stat = $self->status;
436
437  my $file;
438
439  my $OK   = sub {
440   $stat->created(1);
441   $stat->dist($file) if defined $file;
442   1;
443  };
444
445  my $FAIL = sub {
446   $stat->created(0);
447   $stat->dist(undef);
448   $self->_abort(@_) if @_;
449   if (defined $file and -f $file) {
450    1 while unlink $file;
451   }
452   0;
453  };
454
455  unless ($stat->prepared) {
456   return $FAIL->(
457    'Can\'t create', $stat->distribution, 'since it was never prepared'
458   );
459  }
460
461  if ($stat->created) {
462   $self->_skip($stat->distribution, 'was already created');
463   $file = $stat->dist; # Keep the existing one.
464   return $OK->();
465  }
466
467  my $dir = $stat->ebuild_dir;
468  unless (-d $dir) {
469   eval { File::Path::mkpath($dir) };
470   return $FAIL->("mkpath($dir): $@") if $@;
471  }
472
473  $file = $stat->ebuild_file;
474
475  # Create a placeholder ebuild to prevent recursion with circular dependencies.
476  {
477   open my $eb, '>', $file or return $FAIL->("open($file): $!");
478   print $eb "PLACEHOLDER\n";
479  }
480
481  $stat->created(0);
482  $stat->dist(undef);
483
484  $self->SUPER::create(@_);
485
486  return $FAIL->() unless $stat->created;
487
488  {
489   open my $eb, '>', $file or return $FAIL->("open($file): $!");
490   my $source = $self->ebuild_source;
491   return $FAIL->() unless defined $source;
492   print $eb $source;
493  }
494
495  return $FAIL->() if $stat->do_manifest and not $self->update_manifest;
496
497  return $OK->();
498 }
499
500 =head2 C<update_manifest>
501
502 Updates the F<Manifest> file for the ebuild associated to the current dist object.
503
504 =cut
505
506 sub update_manifest {
507  my $self = shift;
508  my $stat = $self->status;
509
510  my $file = $stat->ebuild_file;
511  unless (defined $file and -e $file) {
512   return $self->_abort('The ebuild file is invalid or does not exist');
513  }
514
515  unless (File::Copy::copy($stat->fetched_arch => $stat->distdir)) {
516   return $self->_abort("Couldn\'t copy the distribution file to distdir ($!)");
517  }
518
519  $self->_notify('Adding Manifest entry for', $stat->distribution);
520
521  return $self->_run([ 'ebuild', $file, 'manifest' ], 0);
522 }
523
524 =head2 C<ebuild_source>
525
526 Returns the source of the ebuild for the current dist object, or C<undef> when one of the dependencies couldn't be mapped to an existing ebuild.
527
528 =cut
529
530 sub ebuild_source {
531  my $self = shift;
532  my $stat = $self->status;
533
534  # We must resolve the deps now and not inside prepare because _cpan2portage
535  # has to see the ebuilds already generated for the dependencies of the current
536  # dist.
537  my @requires;
538  for (@{$stat->requires}) {
539   my $atom = $self->_cpan2portage(@$_);
540   unless (defined $atom) {
541    $self->_abort(
542     "Couldn't find an appropriate ebuild for $_->[0] in the portage tree"
543    );
544    return;
545   }
546   push @requires, $atom;
547  }
548
549  my $perl = CPANPLUS::Dist::Gentoo::Atom->new(
550   category => 'dev-lang',
551   name     => 'perl',
552  );
553  @requires = CPANPLUS::Dist::Gentoo::Atom->fold($perl, @requires);
554
555  my $d = $stat->header;
556  $d   .= "# Generated by CPANPLUS::Dist::Gentoo version $VERSION\n\n";
557  $d   .= 'MODULE_AUTHOR="' . $stat->author . "\"\ninherit perl-module\n\n";
558  $d   .= 'S="${WORKDIR}/' . $stat->distribution . "\"\n";
559  $d   .= 'DESCRIPTION="' . $stat->desc . "\"\n";
560  $d   .= 'HOMEPAGE="' . $stat->uri . "\"\n";
561  $d   .= 'SRC_URI="' . $stat->src . "\"\n";
562  $d   .= "SLOT=\"0\"\n";
563  $d   .= 'LICENSE="|| ( ' . join(' ', sort @{$stat->license}) . " )\"\n";
564  $d   .= 'KEYWORDS="' . join(' ', sort @{$stat->keywords}) . "\"\n";
565  $d   .= 'RDEPEND="' . join("\n", sort @requires) . "\"\n";
566  $d   .= "DEPEND=\"\${RDEPEND}\"\n";
567  $d   .= "SRC_TEST=\"do\"\n";
568  $d   .= $stat->footer;
569
570  return $d;
571 }
572
573 sub _cpan2portage {
574  my ($self, $name, $version) = @_;
575
576  $name    = CPANPLUS::Dist::Gentoo::Maps::name_c2g($name);
577  $version = CPANPLUS::Dist::Gentoo::Maps::version_c2g($version);
578
579  my @portdirs = ($main_portdir, @{$self->status->portdir_overlay});
580
581  for my $category (qw/virtual perl-core dev-perl perl-gcpan/, CATEGORY) {
582   my $name = ($category eq 'virtual' ? 'perl-' : '') . $name;
583
584   for my $portdir (@portdirs) {
585    my @ebuilds = glob File::Spec->catfile(
586     $portdir,
587     $category,
588     $name,
589     "$name-*.ebuild",
590    ) or next;
591
592    my $last = reduce { $a < $b ? $b : $a } # handles overloading
593                map CPANPLUS::Dist::Gentoo::Atom->new_from_ebuild($_),
594                 @ebuilds;
595    next if defined $version and $last < $version;
596
597    return CPANPLUS::Dist::Gentoo::Atom->new(
598     category => $last->category,
599     name     => $last->name,
600     (defined $version ? (version => $version, range => '>=') : ()),
601     ebuild   => $last->ebuild,
602    );
603   }
604
605  }
606
607  return;
608 }
609
610 sub install {
611  my $self = shift;
612  my $stat = $self->status;
613  my $conf = $self->parent->parent->configure_object;
614
615  my $sudo = $conf->get_program('sudo');
616  my @cmd = ('emerge', '=' . $stat->ebuild_name . '-' . $stat->ebuild_version);
617  unshift @cmd, $sudo if $sudo;
618
619  my $success = $self->_run(\@cmd, 1);
620  $stat->installed($success);
621
622  return $success;
623 }
624
625 sub uninstall {
626  my $self = shift;
627  my $stat = $self->status;
628  my $conf = $self->parent->parent->configure_object;
629
630  my $sudo = $conf->get_program('sudo');
631  my @cmd = ('emerge', '-C', '=' . $stat->ebuild_name . '-' . $stat->ebuild_version);
632  unshift @cmd, $sudo if $sudo;
633
634  my $success = $self->_run(\@cmd, 1);
635  $stat->uninstalled($success);
636
637  return $success;
638 }
639
640 sub _run {
641  my ($self, $cmd, $verbose) = @_;
642  my $stat = $self->status;
643
644  my ($success, $errmsg, $output) = do {
645   local $ENV{PORTDIR_OVERLAY}     = join ' ', @{$stat->portdir_overlay};
646   local $ENV{PORTAGE_RO_DISTDIRS} = $stat->distdir;
647   IPC::Cmd::run(
648    command => $cmd,
649    verbose => $verbose,
650   );
651  };
652
653  unless ($success) {
654   $self->_abort($errmsg);
655   if (not $verbose and defined $output and $stat->verbose) {
656    my $msg = join '', @$output;
657    1 while chomp $msg;
658    CPANPLUS::Error::error($msg);
659   }
660  }
661
662  return $success;
663 }
664
665 sub _abort {
666  my $self = shift;
667
668  CPANPLUS::Error::error("@_ -- aborting");
669
670  return 0;
671 }
672
673 sub _notify {
674  my $self = shift;
675
676  CPANPLUS::Error::msg("@_");
677
678  return 1;
679 }
680
681 sub _skip { shift->_notify(@_, '-- skipping') }
682
683 =head1 DEPENDENCIES
684
685 Gentoo (L<http://gentoo.org>).
686
687 L<CPANPLUS>, L<IPC::Cmd> (core modules since 5.9.5), L<Parse::CPAN::Meta> (since 5.10.1).
688
689 L<Cwd>, L<Carp> (since perl 5), L<File::Path> (5.001), L<File::Copy> (5.002), L<File::Spec> (5.00405), L<List::Util> (5.007003).
690
691 =head1 SEE ALSO
692
693 L<cpan2dist>.
694
695 L<CPANPLUS::Dist::Base>, L<CPANPLUS::Dist::Deb>, L<CPANPLUS::Dist::Mdv>.
696
697 =head1 AUTHOR
698
699 Vincent Pit, C<< <perl at profvince.com> >>, L<http://www.profvince.com>.
700
701 You can contact me by mail or on C<irc.perl.org> (vincent).
702
703 =head1 BUGS
704
705 Please report any bugs or feature requests to C<bug-cpanplus-dist-gentoo at rt.cpan.org>, or through the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=CPANPLUS-Dist-Gentoo>.
706 I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
707
708 =head1 SUPPORT
709
710 You can find documentation for this module with the perldoc command.
711
712     perldoc CPANPLUS::Dist::Gentoo
713
714 =head1 ACKNOWLEDGEMENTS
715
716 The module was inspired by L<CPANPLUS::Dist::Deb> and L<CPANPLUS::Dist::Mdv>.
717
718 Kent Fredric, for testing and suggesting improvements.
719
720 =head1 COPYRIGHT & LICENSE
721
722 Copyright 2008,2009,2010 Vincent Pit, all rights reserved.
723
724 This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
725
726 =cut
727
728 1; # End of CPANPLUS::Dist::Gentoo