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::Guard;
21 use CPANPLUS::Dist::Gentoo::Maps;
25 CPANPLUS::Dist::Gentoo - CPANPLUS backend generating Gentoo ebuilds.
33 our $VERSION = '0.10';
37 cpan2dist --format=CPANPLUS::Dist::Gentoo \
38 --dist-opts overlay=/usr/local/portage \
39 --dist-opts distdir=/usr/portage/distfiles \
40 --dist-opts manifest=yes \
41 --dist-opts keywords=x86 \
42 --dist-opts header="# Copyright 1999-2008 Gentoo Foundation" \
43 --dist-opts footer="# End" \
48 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.
49 You need write permissions on the directory where Gentoo fetches its source files (usually F</usr/portage/distfiles>).
50 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.
52 The generated ebuilds are placed into the C<perl-gcpanp> category.
53 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>.
57 Before installing this module, you should append C<perl-gcpanp> to your F</etc/portage/categories> file.
59 You have two ways for installing this module :
65 Use the perl overlay located at L<http://git.overlays.gentoo.org/gitweb/?p=proj/perl-overlay.git>.
66 It contains an ebuild for L<CPANPLUS::Dist::Gentoo>.
70 Bootstrap an ebuild for L<CPANPLUS::Dist::Gentoo> using itself.
71 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.
72 So you need to bootstrap them as well.
74 First, fetch tarballs for L<CPANPLUS> and L<CPANPLUS::Dist::Gentoo> :
77 $ wget http://search.cpan.org/CPAN/authors/id/B/BI/BINGOS/CPANPLUS-0.9003.tar.gz
78 $ wget http://search.cpan.org/CPAN/authors/id/V/VP/VPIT/CPANPLUS-Dist-Gentoo-0.10.tar.gz
80 Log in as root and unpack them in e.g. your home directory :
83 # tar xzf /tmp/CPANPLUS-0.9003.tar.gz
84 # tar xzf /tmp/CPANPLUS-Dist-Gentoo-0.10.tar.gz
86 Set up environment variables so that the toolchain is temporarily available :
88 # export OLDPATH=$PATH
89 # export PATH=/root/CPANPLUS-0.9003/bin:$PATH
90 # export PERL5LIB=/root/CPANPLUS-Dist-Gentoo-0.10/blib/lib:/root/CPANPLUS-0.9003/lib:/root/CPANPLUS-0.9003/inc/bundle
92 Make sure you don't have an old C<.cpanplus> configuration visible :
94 # [ -d /root/.cpanplus ] && mv /root/.cpanplus{,.bak}
96 Bootstrap L<CPANPLUS> :
98 # cd /root/CPANPLUS-Dist-Gentoo-0.10
99 # samples/g-cpanp CPANPLUS
101 Reset the environment :
103 # export PATH=$OLDPATH
104 # unset PERL5LIB OLDPATH
106 Emerge L<CPANPLUS> with the ebuilds you've just generated :
108 # emerge -tv CPANPLUS
110 As of september 2009, C<podlators> and C<ExtUtils-MakeMaker> may fail to emerge due to collisions.
111 You can work around this by disabling the C<protect-owned> C<FEATURE> for them :
113 # FEATURES="-protect-owned" emerge podlators
114 # FEATURES="-protect-owned" emerge ExtUtils-MakeMaker
116 You may need to run each of these commands two times for them to succeed.
118 At this point, you can bootstrap L<CPANPLUS::Dist::Gentoo> using the system L<CPANPLUS> :
120 # PERL5LIB=/root/CPANPLUS-Dist-Gentoo-0.10/blib/lib samples/g-cpanp CPANPLUS::Dist::Gentoo
121 # emerge -tv CPANPLUS-Dist-Gentoo
127 This module inherits all the methods from L<CPANPLUS::Dist::Base>.
128 Please refer to its documentation for precise information on what's done at each step.
132 use constant CATEGORY => 'perl-gcpanp';
135 my $default_keywords;
148 my $format_available;
150 sub format_available {
151 return $format_available if defined $format_available;
153 for my $prog (qw/emerge ebuild/) {
154 unless (IPC::Cmd::can_run($prog)) {
155 __PACKAGE__->_abort("$prog is required to write ebuilds");
156 return $format_available = 0;
160 if (IPC::Cmd->can_capture_buffer) {
162 my ($success, $errmsg) = IPC::Cmd::run(
163 command => [ qw/emerge --info/ ],
168 if ($buffers =~ /^PORTDIR_OVERLAY=(.*)$/m) {
169 $overlays = [ map Cwd::abs_path($_), split ' ', $unquote->($1) ];
171 if ($buffers =~ /^ACCEPT_KEYWORDS=(.*)$/m) {
172 $default_keywords = [ split ' ', $unquote->($1) ];
174 if ($buffers =~ /^DISTDIR=(.*)$/m) {
175 $default_distdir = Cwd::abs_path($unquote->($1));
177 if ($buffers =~ /^PORTDIR=(.*)$/m) {
178 $main_portdir = Cwd::abs_path($unquote->($1));
181 __PACKAGE__->_abort($errmsg);
185 $default_keywords = [ 'x86' ] unless defined $default_keywords;
186 $default_distdir = '/usr/portage/distfiles' unless defined $default_distdir;
188 return $format_available = 1;
193 my $stat = $self->status;
194 my $conf = $self->parent->parent->configure_object;
196 $stat->mk_accessors(qw/name version author distribution desc uri src license
198 fetched_arch requires
199 ebuild_name ebuild_version ebuild_dir ebuild_file
201 overlay distdir keywords do_manifest header footer
204 $stat->force($conf->get_conf('force'));
205 $stat->verbose($conf->get_conf('verbose'));
212 my $mod = $self->parent;
213 my $stat = $self->status;
214 my $int = $mod->parent;
215 my $conf = $int->configure_object;
219 my $OK = sub { $stat->prepared(1); 1 };
220 my $FAIL = sub { $stat->prepared(0); $self->_abort(@_) if @_; 0 };
221 my $SKIP = sub { $stat->prepared(1); $stat->created(1); $self->_skip(@_) if @_; 1 };
223 my $keywords = delete $opts{keywords};
224 if (defined $keywords) {
225 $keywords = [ split ' ', $keywords ];
227 $keywords = $default_keywords;
229 $stat->keywords($keywords);
231 my $manifest = delete $opts{manifest};
232 $manifest = 1 unless defined $manifest;
233 $manifest = 0 if $manifest =~ /^\s*no?\s*$/i;
234 $stat->do_manifest($manifest);
236 my $header = delete $opts{header};
237 if (defined $header) {
238 1 while chomp $header;
241 my $year = (localtime)[5] + 1900;
242 $header = <<" DEF_HEADER";
243 # Copyright 1999-$year Gentoo Foundation
244 # Distributed under the terms of the GNU General Public License v2
248 $stat->header($header);
250 my $footer = delete $opts{footer};
251 if (defined $footer) {
252 $footer = "\n" . $footer;
256 $stat->footer($footer);
258 my $overlay = delete $opts{overlay};
259 $overlay = (defined $overlay) ? Cwd::abs_path($overlay) : '/usr/local/portage';
260 $stat->overlay($overlay);
262 my $distdir = delete $opts{distdir};
263 $distdir = (defined $distdir) ? Cwd::abs_path($distdir) : $default_distdir;
264 $stat->distdir($distdir);
266 return $FAIL->("distdir isn't writable") if $stat->do_manifest && !-w $distdir;
268 $stat->fetched_arch($mod->status->fetch);
270 my $cur = File::Spec->curdir();
273 if ($_ eq $overlay or File::Spec->abs2rel($overlay, $_) eq $cur) {
274 $portdir_overlay = [ @$overlays ];
278 $portdir_overlay = [ @$overlays, $overlay ] unless $portdir_overlay;
279 $stat->portdir_overlay($portdir_overlay);
281 my $name = $mod->package_name;
284 my $version = $mod->package_version;
285 $stat->version($version);
287 my $author = $mod->author->cpanid;
288 $stat->author($author);
290 $stat->distribution($name . '-' . $version);
292 $stat->ebuild_version(CPANPLUS::Dist::Gentoo::Maps::version_c2g($version));
294 $stat->ebuild_name(CPANPLUS::Dist::Gentoo::Maps::name_c2g($name));
296 $stat->ebuild_dir(File::Spec->catdir(
302 my $file = File::Spec->catfile(
304 $stat->ebuild_name . '-' . $stat->ebuild_version . '.ebuild',
306 $stat->ebuild_file($file);
309 # Always generate an ebuild in our category when forcing
310 if ($forced{$file}) {
312 return $SKIP->('Ebuild already forced for', $stat->distribution);
318 return $SKIP->("Can't force rewriting of $file");
320 1 while unlink $file;
323 if (my $atom = $self->_cpan2portage($name, $version)) {
324 $stat->dist($atom->ebuild);
325 return $SKIP->('Ebuild already generated for', $stat->distribution);
331 $self->SUPER::prepare(@_);
333 return $FAIL->() unless $stat->prepared;
335 my $desc = $mod->description;
336 ($desc = $name) =~ s/-+/::/g unless $desc;
339 $stat->uri('http://search.cpan.org/dist/' . $name);
341 $author =~ /^(.)(.)/ or return $FAIL->('Wrong author name');
342 $stat->src("mirror://cpan/modules/by-authors/id/$1/$1$2/$author/" . $mod->package);
344 $stat->license($self->intuit_license);
346 my $prereqs = $mod->status->requires;
348 for my $prereq (sort keys %$prereqs) {
349 next if $prereq =~ /^perl(?:-|\z)/;
350 my $obj = $int->module_tree($prereq);
351 next unless $obj; # Not in the module tree (e.g. Config)
352 next if $obj->package_is_perl_core;
355 if ($prereqs->{$prereq}) {
356 if ($obj->installed_version && $obj->installed_version < $obj->version) {
357 $version = $obj->installed_version;
359 $version = $obj->package_version;
362 push @requires, [ $obj->package_name, $version ];
365 $stat->requires(\@requires);
367 $stat->min_perl(CPANPLUS::Dist::Gentoo::Maps::perl_version_c2g(
368 eval { $self->meta->{requires}->{perl} }
376 Returns the contents of the F<META.yml> or F<META.json> files as parsed by L<Parse::CPAN::Meta>.
382 my $mod = $self->parent;
383 my $stat = $self->status;
385 my $meta = $stat->meta;
386 return $meta if defined $meta;
388 my $extract_dir = $mod->status->extract;
390 for my $name (qw/META.json META.yml/) {
391 my $meta_file = File::Spec->catdir($extract_dir, $name);
392 next unless -e $meta_file;
394 my $meta = eval { Parse::CPAN::Meta::LoadFile($meta_file) };
404 =head2 C<intuit_license>
406 Returns an array reference to a list of Gentoo licences identifiers under which the current distribution is released.
410 my %dslip_license = (
421 my $mod = $self->parent;
423 my $dslip = $mod->dslip;
424 if (defined $dslip and $dslip =~ /\S{4}(\S)/) {
425 my @licenses = CPANPLUS::Dist::Gentoo::Maps::license_c2g($dslip_license{$1});
426 return \@licenses if @licenses;
429 my $license = $self->meta->{license};
430 if (defined $license) {
431 my @licenses = CPANPLUS::Dist::Gentoo::Maps::license_c2g($license);
432 return \@licenses if @licenses;
435 return [ CPANPLUS::Dist::Gentoo::Maps::license_c2g('perl') ];
440 my $stat = $self->status;
444 my $guard = CPANPLUS::Dist::Gentoo::Guard->new(sub {
445 if (defined $file and -e $file and -w _) {
446 1 while unlink $file;
450 my $SIG_INT = $SIG{INT};
451 local $SIG{INT} = sub {
454 eval { $SIG_INT->() };
463 $stat->dist($file) if defined $file;
470 $self->_abort(@_) if @_;
474 unless ($stat->prepared) {
476 'Can\'t create', $stat->distribution, 'since it was never prepared'
480 if ($stat->created) {
481 $self->_skip($stat->distribution, 'was already created');
482 $file = $stat->dist; # Keep the existing one.
486 my $dir = $stat->ebuild_dir;
488 eval { File::Path::mkpath($dir) };
489 return $FAIL->("mkpath($dir): $@") if $@;
492 $file = $stat->ebuild_file;
494 # Create a placeholder ebuild to prevent recursion with circular dependencies.
496 open my $eb, '>', $file or return $FAIL->("open($file): $!");
497 print $eb "PLACEHOLDER\n";
503 $self->SUPER::create(@_);
505 return $FAIL->() unless $stat->created;
508 open my $eb, '>', $file or return $FAIL->("open($file): $!");
509 my $source = $self->ebuild_source;
510 return $FAIL->() unless defined $source;
514 return $FAIL->() if $stat->do_manifest and not $self->update_manifest;
519 =head2 C<update_manifest>
521 Updates the F<Manifest> file for the ebuild associated to the current dist object.
525 sub update_manifest {
527 my $stat = $self->status;
529 my $file = $stat->ebuild_file;
530 unless (defined $file and -e $file) {
531 return $self->_abort('The ebuild file is invalid or does not exist');
534 unless (File::Copy::copy($stat->fetched_arch => $stat->distdir)) {
535 return $self->_abort("Couldn\'t copy the distribution file to distdir ($!)");
538 $self->_notify('Adding Manifest entry for', $stat->distribution);
540 return $self->_run([ 'ebuild', $file, 'manifest' ], 0);
543 =head2 C<ebuild_source>
545 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.
551 my $stat = $self->status;
553 # We must resolve the deps now and not inside prepare because _cpan2portage
554 # has to see the ebuilds already generated for the dependencies of the current
557 for (@{$stat->requires}) {
558 my $atom = $self->_cpan2portage(@$_);
559 unless (defined $atom) {
561 "Couldn't find an appropriate ebuild for $_->[0] in the portage tree"
565 push @requires, $atom;
568 my $min_perl = $stat->min_perl;
569 my $perl = CPANPLUS::Dist::Gentoo::Atom->new(
570 category => 'dev-lang',
572 (defined $min_perl ? (version => $min_perl, range => '>=') : ()),
575 @requires = CPANPLUS::Dist::Gentoo::Atom->fold($perl, @requires);
577 my $d = $stat->header;
578 $d .= "# Generated by CPANPLUS::Dist::Gentoo version $VERSION\n\n";
579 $d .= 'MODULE_AUTHOR="' . $stat->author . "\"\ninherit perl-module\n\n";
580 $d .= 'S="${WORKDIR}/' . $stat->distribution . "\"\n";
581 $d .= 'DESCRIPTION="' . $stat->desc . "\"\n";
582 $d .= 'HOMEPAGE="' . $stat->uri . "\"\n";
583 $d .= 'SRC_URI="' . $stat->src . "\"\n";
584 $d .= "SLOT=\"0\"\n";
585 $d .= 'LICENSE="|| ( ' . join(' ', sort @{$stat->license}) . " )\"\n";
586 $d .= 'KEYWORDS="' . join(' ', sort @{$stat->keywords}) . "\"\n";
587 $d .= 'RDEPEND="' . join("\n", sort @requires) . "\"\n";
588 $d .= "DEPEND=\"\${RDEPEND}\"\n";
589 $d .= "SRC_TEST=\"do\"\n";
596 my ($self, $name, $version) = @_;
598 $name = CPANPLUS::Dist::Gentoo::Maps::name_c2g($name);
599 $version = CPANPLUS::Dist::Gentoo::Maps::version_c2g($version);
601 my @portdirs = ($main_portdir, @{$self->status->portdir_overlay});
603 for my $category (qw/virtual perl-core dev-perl perl-gcpan/, CATEGORY) {
604 my $name = ($category eq 'virtual' ? 'perl-' : '') . $name;
606 for my $portdir (@portdirs) {
607 my @ebuilds = glob File::Spec->catfile(
614 my $last = reduce { $a < $b ? $b : $a } # handles overloading
615 map CPANPLUS::Dist::Gentoo::Atom->new_from_ebuild($_),
617 next if defined $version and $last < $version;
619 return CPANPLUS::Dist::Gentoo::Atom->new(
620 category => $last->category,
622 (defined $version ? (version => $version, range => '>=') : ()),
623 ebuild => $last->ebuild,
634 my $stat = $self->status;
635 my $conf = $self->parent->parent->configure_object;
637 my $sudo = $conf->get_program('sudo');
638 my @cmd = ('emerge', '=' . $stat->ebuild_name . '-' . $stat->ebuild_version);
639 unshift @cmd, $sudo if $sudo;
641 my $success = $self->_run(\@cmd, 1);
642 $stat->installed($success);
649 my $stat = $self->status;
650 my $conf = $self->parent->parent->configure_object;
652 my $sudo = $conf->get_program('sudo');
653 my @cmd = ('emerge', '-C', '=' . $stat->ebuild_name . '-' . $stat->ebuild_version);
654 unshift @cmd, $sudo if $sudo;
656 my $success = $self->_run(\@cmd, 1);
657 $stat->uninstalled($success);
663 my ($self, $cmd, $verbose) = @_;
664 my $stat = $self->status;
666 my ($success, $errmsg, $output) = do {
667 local $ENV{PORTDIR_OVERLAY} = join ' ', @{$stat->portdir_overlay};
668 local $ENV{PORTAGE_RO_DISTDIRS} = $stat->distdir;
676 $self->_abort($errmsg);
677 if (not $verbose and defined $output and $stat->verbose) {
678 my $msg = join '', @$output;
680 CPANPLUS::Error::error($msg);
690 CPANPLUS::Error::error("@_ -- aborting");
698 CPANPLUS::Error::msg("@_");
703 sub _skip { shift->_notify(@_, '-- skipping') }
707 Gentoo (L<http://gentoo.org>).
709 L<CPANPLUS>, L<IPC::Cmd> (core modules since 5.9.5), L<Parse::CPAN::Meta> (since 5.10.1).
711 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).
717 L<CPANPLUS::Dist::Base>, L<CPANPLUS::Dist::Deb>, L<CPANPLUS::Dist::Mdv>.
721 Vincent Pit, C<< <perl at profvince.com> >>, L<http://www.profvince.com>.
723 You can contact me by mail or on C<irc.perl.org> (vincent).
727 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>.
728 I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
732 You can find documentation for this module with the perldoc command.
734 perldoc CPANPLUS::Dist::Gentoo
736 =head1 ACKNOWLEDGEMENTS
738 The module was inspired by L<CPANPLUS::Dist::Deb> and L<CPANPLUS::Dist::Mdv>.
740 Kent Fredric, for testing and suggesting improvements.
742 =head1 COPYRIGHT & LICENSE
744 Copyright 2008,2009,2010 Vincent Pit, all rights reserved.
746 This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
750 1; # End of CPANPLUS::Dist::Gentoo