1 package CPANPLUS::Dist::Gentoo;
7 use List::Util qw/reduce/;
13 use Parse::CPAN::Meta ();
15 use CPANPLUS::Error ();
17 use base qw/CPANPLUS::Dist::Base/;
19 use CPANPLUS::Dist::Gentoo::Atom;
20 use CPANPLUS::Dist::Gentoo::Maps;
24 CPANPLUS::Dist::Gentoo - CPANPLUS backend generating Gentoo ebuilds.
32 our $VERSION = '0.09';
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" \
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.
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>.
56 Before installing this module, you should append C<perl-gcpanp> to your F</etc/portage/categories> file.
58 You have two ways for installing this module :
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>.
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.
73 First, fetch tarballs for L<CPANPLUS> and L<CPANPLUS::Dist::Gentoo> :
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
79 Log in as root and unpack them in e.g. your home directory :
82 # tar xzf /tmp/CPANPLUS-0.9003.tar.gz
83 # tar xzf /tmp/CPANPLUS-Dist-Gentoo-0.09.tar.gz
85 Set up environment variables so that the toolchain is temporarily available :
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
91 Make sure you don't have an old C<.cpanplus> configuration visible :
93 # [ -d /root/.cpanplus ] && mv /root/.cpanplus{,.bak}
95 Bootstrap L<CPANPLUS> :
97 # cd /root/CPANPLUS-Dist-Gentoo-0.09
98 # samples/g-cpanp CPANPLUS
100 Reset the environment :
102 # export PATH=$OLDPATH
103 # unset PERL5LIB OLDPATH
105 Emerge L<CPANPLUS> with the ebuilds you've just generated :
107 # emerge -tv CPANPLUS
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 :
112 # FEATURES="-protect-owned" emerge podlators
113 # FEATURES="-protect-owned" emerge ExtUtils-MakeMaker
115 You may need to run each of these commands two times for them to succeed.
117 At this point, you can bootstrap L<CPANPLUS::Dist::Gentoo> using the system L<CPANPLUS> :
119 # PERL5LIB=/root/CPANPLUS-Dist-Gentoo-0.09/blib/lib samples/g-cpanp CPANPLUS::Dist::Gentoo
120 # emerge -tv CPANPLUS-Dist-Gentoo
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.
131 use constant CATEGORY => 'perl-gcpanp';
134 my $default_keywords;
147 my $format_available;
149 sub format_available {
150 return $format_available if defined $format_available;
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;
159 if (IPC::Cmd->can_capture_buffer) {
161 my ($success, $errmsg) = IPC::Cmd::run(
162 command => [ qw/emerge --info/ ],
167 if ($buffers =~ /^PORTDIR_OVERLAY=(.*)$/m) {
168 $overlays = [ map Cwd::abs_path($_), split ' ', $unquote->($1) ];
170 if ($buffers =~ /^ACCEPT_KEYWORDS=(.*)$/m) {
171 $default_keywords = [ split ' ', $unquote->($1) ];
173 if ($buffers =~ /^DISTDIR=(.*)$/m) {
174 $default_distdir = Cwd::abs_path($unquote->($1));
176 if ($buffers =~ /^PORTDIR=(.*)$/m) {
177 $main_portdir = Cwd::abs_path($unquote->($1));
180 __PACKAGE__->_abort($errmsg);
184 $default_keywords = [ 'x86' ] unless defined $default_keywords;
185 $default_distdir = '/usr/portage/distfiles' unless defined $default_distdir;
187 return $format_available = 1;
192 my $stat = $self->status;
193 my $conf = $self->parent->parent->configure_object;
195 $stat->mk_accessors(qw/name version author distribution desc uri src license
197 fetched_arch requires
198 ebuild_name ebuild_version ebuild_dir ebuild_file
200 overlay distdir keywords do_manifest header footer
203 $stat->force($conf->get_conf('force'));
204 $stat->verbose($conf->get_conf('verbose'));
211 my $mod = $self->parent;
212 my $stat = $self->status;
213 my $int = $mod->parent;
214 my $conf = $int->configure_object;
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 };
222 my $keywords = delete $opts{keywords};
223 if (defined $keywords) {
224 $keywords = [ split ' ', $keywords ];
226 $keywords = $default_keywords;
228 $stat->keywords($keywords);
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);
235 my $header = delete $opts{header};
236 if (defined $header) {
237 1 while chomp $header;
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
247 $stat->header($header);
249 my $footer = delete $opts{footer};
250 if (defined $footer) {
251 $footer = "\n" . $footer;
255 $stat->footer($footer);
257 my $overlay = delete $opts{overlay};
258 $overlay = (defined $overlay) ? Cwd::abs_path($overlay) : '/usr/local/portage';
259 $stat->overlay($overlay);
261 my $distdir = delete $opts{distdir};
262 $distdir = (defined $distdir) ? Cwd::abs_path($distdir) : $default_distdir;
263 $stat->distdir($distdir);
265 return $FAIL->("distdir isn't writable") if $stat->do_manifest && !-w $distdir;
267 $stat->fetched_arch($mod->status->fetch);
269 my $cur = File::Spec->curdir();
272 if ($_ eq $overlay or File::Spec->abs2rel($overlay, $_) eq $cur) {
273 $portdir_overlay = [ @$overlays ];
277 $portdir_overlay = [ @$overlays, $overlay ] unless $portdir_overlay;
278 $stat->portdir_overlay($portdir_overlay);
280 my $name = $mod->package_name;
283 my $version = $mod->package_version;
284 $stat->version($version);
286 my $author = $mod->author->cpanid;
287 $stat->author($author);
289 $stat->distribution($name . '-' . $version);
291 $stat->ebuild_version(CPANPLUS::Dist::Gentoo::Maps::version_c2g($version));
293 $stat->ebuild_name(CPANPLUS::Dist::Gentoo::Maps::name_c2g($name));
295 $stat->ebuild_dir(File::Spec->catdir(
301 my $file = File::Spec->catfile(
303 $stat->ebuild_name . '-' . $stat->ebuild_version . '.ebuild',
305 $stat->ebuild_file($file);
308 # Always generate an ebuild in our category when forcing
309 if ($forced{$file}) {
311 return $SKIP->('Ebuild already forced for', $stat->distribution);
317 return $SKIP->("Can't force rewriting of $file");
319 1 while unlink $file;
322 if (my $atom = $self->_cpan2portage($name, $version)) {
323 $stat->dist($atom->ebuild);
324 return $SKIP->('Ebuild already generated for', $stat->distribution);
330 $self->SUPER::prepare(@_);
332 return $FAIL->() unless $stat->prepared;
334 my $desc = $mod->description;
335 ($desc = $name) =~ s/-+/::/g unless $desc;
338 $stat->uri('http://search.cpan.org/dist/' . $name);
340 $author =~ /^(.)(.)/ or return $FAIL->('Wrong author name');
341 $stat->src("mirror://cpan/modules/by-authors/id/$1/$1$2/$author/" . $mod->package);
343 $stat->license($self->intuit_license);
345 my $prereqs = $mod->status->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;
354 if ($prereqs->{$prereq}) {
355 if ($obj->installed_version && $obj->installed_version < $obj->version) {
356 $version = $obj->installed_version;
358 $version = $obj->package_version;
361 push @requires, [ $obj->package_name, $version ];
364 $stat->requires(\@requires);
366 $stat->min_perl(CPANPLUS::Dist::Gentoo::Maps::perl_version_c2g(
367 eval { $self->meta->{requires}->{perl} }
375 Returns the contents of the F<META.yml> or F<META.json> files as parsed by L<Parse::CPAN::Meta>.
381 my $mod = $self->parent;
382 my $stat = $self->status;
384 my $meta = $stat->meta;
385 return $meta if defined $meta;
387 my $extract_dir = $mod->status->extract;
389 for my $name (qw/META.json META.yml/) {
390 my $meta_file = File::Spec->catdir($extract_dir, $name);
391 next unless -e $meta_file;
393 my $meta = eval { Parse::CPAN::Meta::LoadFile($meta_file) };
403 =head2 C<intuit_license>
405 Returns an array reference to a list of Gentoo licences identifiers under which the current distribution is released.
409 my %dslip_license = (
420 my $mod = $self->parent;
422 my $dslip = $mod->dslip;
423 if (defined $dslip and $dslip =~ /\S{4}(\S)/) {
424 my @licenses = CPANPLUS::Dist::Gentoo::Maps::license_c2g($dslip_license{$1});
425 return \@licenses if @licenses;
428 my $license = $self->meta->{license};
429 if (defined $license) {
430 my @licenses = CPANPLUS::Dist::Gentoo::Maps::license_c2g($license);
431 return \@licenses if @licenses;
434 return [ CPANPLUS::Dist::Gentoo::Maps::license_c2g('perl') ];
439 my $stat = $self->status;
445 $stat->dist($file) if defined $file;
452 $self->_abort(@_) if @_;
453 if (defined $file and -f $file) {
454 1 while unlink $file;
459 unless ($stat->prepared) {
461 'Can\'t create', $stat->distribution, 'since it was never prepared'
465 if ($stat->created) {
466 $self->_skip($stat->distribution, 'was already created');
467 $file = $stat->dist; # Keep the existing one.
471 my $dir = $stat->ebuild_dir;
473 eval { File::Path::mkpath($dir) };
474 return $FAIL->("mkpath($dir): $@") if $@;
477 $file = $stat->ebuild_file;
479 # Create a placeholder ebuild to prevent recursion with circular dependencies.
481 open my $eb, '>', $file or return $FAIL->("open($file): $!");
482 print $eb "PLACEHOLDER\n";
488 $self->SUPER::create(@_);
490 return $FAIL->() unless $stat->created;
493 open my $eb, '>', $file or return $FAIL->("open($file): $!");
494 my $source = $self->ebuild_source;
495 return $FAIL->() unless defined $source;
499 return $FAIL->() if $stat->do_manifest and not $self->update_manifest;
504 =head2 C<update_manifest>
506 Updates the F<Manifest> file for the ebuild associated to the current dist object.
510 sub update_manifest {
512 my $stat = $self->status;
514 my $file = $stat->ebuild_file;
515 unless (defined $file and -e $file) {
516 return $self->_abort('The ebuild file is invalid or does not exist');
519 unless (File::Copy::copy($stat->fetched_arch => $stat->distdir)) {
520 return $self->_abort("Couldn\'t copy the distribution file to distdir ($!)");
523 $self->_notify('Adding Manifest entry for', $stat->distribution);
525 return $self->_run([ 'ebuild', $file, 'manifest' ], 0);
528 =head2 C<ebuild_source>
530 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.
536 my $stat = $self->status;
538 # We must resolve the deps now and not inside prepare because _cpan2portage
539 # has to see the ebuilds already generated for the dependencies of the current
542 for (@{$stat->requires}) {
543 my $atom = $self->_cpan2portage(@$_);
544 unless (defined $atom) {
546 "Couldn't find an appropriate ebuild for $_->[0] in the portage tree"
550 push @requires, $atom;
553 my $min_perl = $stat->min_perl;
554 my $perl = CPANPLUS::Dist::Gentoo::Atom->new(
555 category => 'dev-lang',
557 (defined $min_perl ? (version => $min_perl, range => '>=') : ()),
560 @requires = CPANPLUS::Dist::Gentoo::Atom->fold($perl, @requires);
562 my $d = $stat->header;
563 $d .= "# Generated by CPANPLUS::Dist::Gentoo version $VERSION\n\n";
564 $d .= 'MODULE_AUTHOR="' . $stat->author . "\"\ninherit perl-module\n\n";
565 $d .= 'S="${WORKDIR}/' . $stat->distribution . "\"\n";
566 $d .= 'DESCRIPTION="' . $stat->desc . "\"\n";
567 $d .= 'HOMEPAGE="' . $stat->uri . "\"\n";
568 $d .= 'SRC_URI="' . $stat->src . "\"\n";
569 $d .= "SLOT=\"0\"\n";
570 $d .= 'LICENSE="|| ( ' . join(' ', sort @{$stat->license}) . " )\"\n";
571 $d .= 'KEYWORDS="' . join(' ', sort @{$stat->keywords}) . "\"\n";
572 $d .= 'RDEPEND="' . join("\n", sort @requires) . "\"\n";
573 $d .= "DEPEND=\"\${RDEPEND}\"\n";
574 $d .= "SRC_TEST=\"do\"\n";
581 my ($self, $name, $version) = @_;
583 $name = CPANPLUS::Dist::Gentoo::Maps::name_c2g($name);
584 $version = CPANPLUS::Dist::Gentoo::Maps::version_c2g($version);
586 my @portdirs = ($main_portdir, @{$self->status->portdir_overlay});
588 for my $category (qw/virtual perl-core dev-perl perl-gcpan/, CATEGORY) {
589 my $name = ($category eq 'virtual' ? 'perl-' : '') . $name;
591 for my $portdir (@portdirs) {
592 my @ebuilds = glob File::Spec->catfile(
599 my $last = reduce { $a < $b ? $b : $a } # handles overloading
600 map CPANPLUS::Dist::Gentoo::Atom->new_from_ebuild($_),
602 next if defined $version and $last < $version;
604 return CPANPLUS::Dist::Gentoo::Atom->new(
605 category => $last->category,
607 (defined $version ? (version => $version, range => '>=') : ()),
608 ebuild => $last->ebuild,
619 my $stat = $self->status;
620 my $conf = $self->parent->parent->configure_object;
622 my $sudo = $conf->get_program('sudo');
623 my @cmd = ('emerge', '=' . $stat->ebuild_name . '-' . $stat->ebuild_version);
624 unshift @cmd, $sudo if $sudo;
626 my $success = $self->_run(\@cmd, 1);
627 $stat->installed($success);
634 my $stat = $self->status;
635 my $conf = $self->parent->parent->configure_object;
637 my $sudo = $conf->get_program('sudo');
638 my @cmd = ('emerge', '-C', '=' . $stat->ebuild_name . '-' . $stat->ebuild_version);
639 unshift @cmd, $sudo if $sudo;
641 my $success = $self->_run(\@cmd, 1);
642 $stat->uninstalled($success);
648 my ($self, $cmd, $verbose) = @_;
649 my $stat = $self->status;
651 my ($success, $errmsg, $output) = do {
652 local $ENV{PORTDIR_OVERLAY} = join ' ', @{$stat->portdir_overlay};
653 local $ENV{PORTAGE_RO_DISTDIRS} = $stat->distdir;
661 $self->_abort($errmsg);
662 if (not $verbose and defined $output and $stat->verbose) {
663 my $msg = join '', @$output;
665 CPANPLUS::Error::error($msg);
675 CPANPLUS::Error::error("@_ -- aborting");
683 CPANPLUS::Error::msg("@_");
688 sub _skip { shift->_notify(@_, '-- skipping') }
692 Gentoo (L<http://gentoo.org>).
694 L<CPANPLUS>, L<IPC::Cmd> (core modules since 5.9.5), L<Parse::CPAN::Meta> (since 5.10.1).
696 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).
702 L<CPANPLUS::Dist::Base>, L<CPANPLUS::Dist::Deb>, L<CPANPLUS::Dist::Mdv>.
706 Vincent Pit, C<< <perl at profvince.com> >>, L<http://www.profvince.com>.
708 You can contact me by mail or on C<irc.perl.org> (vincent).
712 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>.
713 I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
717 You can find documentation for this module with the perldoc command.
719 perldoc CPANPLUS::Dist::Gentoo
721 =head1 ACKNOWLEDGEMENTS
723 The module was inspired by L<CPANPLUS::Dist::Deb> and L<CPANPLUS::Dist::Mdv>.
725 Kent Fredric, for testing and suggesting improvements.
727 =head1 COPYRIGHT & LICENSE
729 Copyright 2008,2009,2010 Vincent Pit, all rights reserved.
731 This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
735 1; # End of CPANPLUS::Dist::Gentoo