1 package CPANPLUS::Dist::Gentoo;
7 use List::Util qw/reduce/;
12 use IPC::Cmd qw/run can_run/;
13 use Parse::CPAN::Meta ();
15 use CPANPLUS::Error ();
17 use base qw/CPANPLUS::Dist::Base/;
19 use CPANPLUS::Dist::Gentoo::Maps;
23 CPANPLUS::Dist::Gentoo - CPANPLUS backend generating Gentoo ebuilds.
31 our $VERSION = '0.08';
35 cpan2dist --format=CPANPLUS::Dist::Gentoo \
36 --dist-opts overlay=/usr/local/portage \
37 --dist-opts distdir=/usr/portage/distfiles \
38 --dist-opts manifest=yes \
39 --dist-opts keywords=x86 \
40 --dist-opts header="# Copyright 1999-2008 Gentoo Foundation" \
41 --dist-opts footer="# End" \
46 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.
47 You need write permissions on the directory where Gentoo fetches its source files (usually F</usr/portage/distfiles>).
48 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 The generated ebuilds are placed into the C<perl-gcpanp> category.
51 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>.
55 Before installing this module, you should append C<perl-gcpanp> to your F</etc/portage/categories> file.
57 You have two ways for installing this module :
63 Use the perl overlay located at L<http://git.overlays.gentoo.org/gitweb/?p=proj/perl-overlay.git>.
64 It contains an ebuild for L<CPANPLUS::Dist::Gentoo>.
68 Bootstrap an ebuild for L<CPANPLUS::Dist::Gentoo> using itself.
69 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.
70 So you need to bootstrap them as well.
72 First, fetch tarballs for L<CPANPLUS> and L<CPANPLUS::Dist::Gentoo> :
75 $ wget http://search.cpan.org/CPAN/authors/id/K/KA/KANE/CPANPLUS-0.88.tar.gz
76 $ wget http://search.cpan.org/CPAN/authors/id/V/VP/VPIT/CPANPLUS-Dist-Gentoo-0.08.tar.gz
78 Log in as root and unpack them in e.g. your home directory :
81 # tar xzf /tmp/CPANPLUS-0.88.tar.gz
82 # tar xzf /tmp/CPANPLUS-Dist-Gentoo-0.08.tar.gz
84 Set up environment variables so that the toolchain is temporarily available :
86 # export OLDPATH=$PATH
87 # export PATH=/root/CPANPLUS-Dist-Gentoo-0.08/bin:$PATH
88 # export PERL5LIB=/root/CPANPLUS-Dist-Gentoo-0.08/blib/lib:/root/CPANPLUS-0.88/lib:/root/CPANPLUS-0.88/inc/bundle
90 Make sure you don't have an old C<.cpanplus> configuration visible :
92 # [ -d /root/.cpanplus ] && mv /root/.cpanplus{,.bak}
94 Bootstrap L<CPANPLUS> :
96 # cd /root/CPANPLUS-Dist-Gentoo-0.08
97 # samples/g-cpanp CPANPLUS
99 Reset the environment :
101 # export PATH=$OLDPATH
102 # unset PERL5LIB OLDPATH
104 Emerge L<CPANPLUS> with the ebuilds you've just generated :
106 # emerge -tv CPANPLUS
108 As of september 2009, C<podlators> and C<ExtUtils-MakeMaker> may fail to emerge due to collisions.
109 You can work around this by disabling the C<protect-owned> C<FEATURE> for them :
111 # FEATURES="-protect-owned" emerge podlators
112 # FEATURES="-protect-owned" emerge ExtUtils-MakeMaker
114 You may need to run each of these commands two times for them to succeed.
116 At this point, you can bootstrap L<CPANPLUS::Dist::Gentoo> using the system L<CPANPLUS> :
118 # PERL5LIB=/root/CPANPLUS-Dist-Gentoo-0.08/blib/lib samples/g-cpanp CPANPLUS::Dist::Gentoo
119 # emerge -tv CPANPLUS-Dist-Gentoo
125 This module inherits all the methods from L<CPANPLUS::Dist::Base>.
126 Please refer to its documentation for precise information on what's done at each step.
130 use constant CATEGORY => 'perl-gcpanp';
133 my $default_keywords;
146 my $format_available;
148 sub format_available {
149 return $format_available if defined $format_available;
151 for my $prog (qw/emerge ebuild/) {
152 unless (can_run($prog)) {
153 __PACKAGE__->_abort("$prog is required to write ebuilds");
154 return $format_available = 0;
158 if (IPC::Cmd->can_capture_buffer) {
160 my ($success, $errmsg) = run command => [ qw/emerge --info/ ],
164 if ($buffers =~ /^PORTDIR_OVERLAY=(.*)$/m) {
165 $overlays = [ map abs_path($_), split ' ', $unquote->($1) ];
167 if ($buffers =~ /^ACCEPT_KEYWORDS=(.*)$/m) {
168 $default_keywords = [ split ' ', $unquote->($1) ];
170 if ($buffers =~ /^DISTDIR=(.*)$/m) {
171 $default_distdir = abs_path($unquote->($1));
173 if ($buffers =~ /^PORTDIR=(.*)$/m) {
174 $main_portdir = abs_path($unquote->($1));
177 __PACKAGE__->_abort($errmsg);
181 $default_keywords = [ 'x86' ] unless defined $default_keywords;
182 $default_distdir = '/usr/portage/distfiles' unless defined $default_distdir;
184 return $format_available = 1;
189 my $stat = $self->status;
190 my $conf = $self->parent->parent->configure_object;
192 $stat->mk_accessors(qw/name version author distribution desc uri src license
193 fetched_arch requires
194 ebuild_name ebuild_version ebuild_dir ebuild_file
196 overlay distdir keywords do_manifest header footer
199 $stat->force($conf->get_conf('force'));
200 $stat->verbose($conf->get_conf('verbose'));
207 my $mod = $self->parent;
208 my $stat = $self->status;
209 my $int = $mod->parent;
210 my $conf = $int->configure_object;
214 my $OK = sub { $stat->prepared(1); 1 };
215 my $FAIL = sub { $stat->prepared(0); $self->_abort(@_) if @_; 0 };
216 my $SKIP = sub { $stat->prepared(1); $stat->created(1); $self->_skip(@_) if @_; 1 };
218 my $keywords = delete $opts{keywords};
219 if (defined $keywords) {
220 $keywords = [ split ' ', $keywords ];
222 $keywords = $default_keywords;
224 $stat->keywords($keywords);
226 my $manifest = delete $opts{manifest};
227 $manifest = 1 unless defined $manifest;
228 $manifest = 0 if $manifest =~ /^\s*no?\s*$/i;
229 $stat->do_manifest($manifest);
231 my $header = delete $opts{header};
232 if (defined $header) {
233 1 while chomp $header;
236 my $year = (localtime)[5] + 1900;
237 $header = <<" DEF_HEADER";
238 # Copyright 1999-$year Gentoo Foundation
239 # Distributed under the terms of the GNU General Public License v2
243 $stat->header($header);
245 my $footer = delete $opts{footer};
246 if (defined $footer) {
247 $footer = "\n" . $footer;
251 $stat->footer($footer);
253 my $overlay = delete $opts{overlay};
254 $overlay = (defined $overlay) ? abs_path $overlay : '/usr/local/portage';
255 $stat->overlay($overlay);
257 my $distdir = delete $opts{distdir};
258 $distdir = (defined $distdir) ? abs_path $distdir : $default_distdir;
259 $stat->distdir($distdir);
261 return $FAIL->("distdir isn't writable") if $stat->do_manifest && !-w $distdir;
263 $stat->fetched_arch($mod->status->fetch);
265 my $cur = File::Spec->curdir();
268 if ($_ eq $overlay or File::Spec->abs2rel($overlay, $_) eq $cur) {
269 $portdir_overlay = [ @$overlays ];
273 $portdir_overlay = [ @$overlays, $overlay ] unless $portdir_overlay;
274 $stat->portdir_overlay($portdir_overlay);
276 my $name = $mod->package_name;
279 my $version = $mod->package_version;
280 $stat->version($version);
282 my $author = $mod->author->cpanid;
283 $stat->author($author);
285 $stat->distribution($name . '-' . $version);
287 $stat->ebuild_version(CPANPLUS::Dist::Gentoo::Maps::version_c2g($version));
289 $stat->ebuild_name(CPANPLUS::Dist::Gentoo::Maps::name_c2g($name));
291 $stat->ebuild_dir(File::Spec->catdir(
297 my $file = File::Spec->catfile(
299 $stat->ebuild_name . '-' . $stat->ebuild_version . '.ebuild',
301 $stat->ebuild_file($file);
304 # Always generate an ebuild in our category when forcing
305 if ($forced{$file}) {
307 return $SKIP->('Ebuild already forced for', $stat->distribution);
313 return $SKIP->("Can't force rewriting of $file");
315 1 while unlink $file;
318 if (my @match = $self->_cpan2portage($name, $version)) {
319 $stat->dist($match[1]);
320 return $SKIP->('Ebuild already generated for', $stat->distribution);
326 $self->SUPER::prepare(%opts);
328 return $FAIL->() unless $stat->prepared;
330 my $desc = $mod->description;
331 ($desc = $name) =~ s/-+/::/g unless $desc;
334 $stat->uri('http://search.cpan.org/dist/' . $name);
336 $author =~ /^(.)(.)/ or return $FAIL->('Wrong author name');
337 $stat->src("mirror://cpan/modules/by-authors/id/$1/$1$2/$author/" . $mod->package);
339 $stat->license($self->intuit_license);
341 my $prereqs = $mod->status->requires;
343 for my $prereq (sort keys %$prereqs) {
344 next if $prereq =~ /^perl(?:-|\z)/;
345 my $obj = $int->module_tree($prereq);
346 next unless $obj; # Not in the module tree (e.g. Config)
347 next if $obj->package_is_perl_core;
350 if ($prereqs->{$prereq}) {
351 if ($obj->installed_version && $obj->installed_version < $obj->version) {
352 $version = $obj->installed_version;
354 $version = $obj->package_version;
357 push @requires, [ $obj->package_name, $version ];
360 $stat->requires(\@requires);
365 =head2 C<intuit_license>
367 Returns an array reference to a list of Gentoo licences identifiers under which the current distribution is released.
371 my %dslip_license = (
382 my $mod = $self->parent;
384 my $dslip = $mod->dslip;
385 if (defined $dslip and $dslip =~ /\S{4}(\S)/) {
386 my @licenses = CPANPLUS::Dist::Gentoo::Maps::license_c2g($dslip_license{$1});
387 return \@licenses if @licenses;
390 my $extract_dir = $mod->status->extract;
392 for my $meta_file (qw/META.json META.yml/) {
394 Parse::CPAN::Meta::LoadFile(File::Spec->catdir(
399 my $license = $meta->{license};
400 if (defined $license) {
401 my @licenses = CPANPLUS::Dist::Gentoo::Maps::license_c2g($license);
402 return \@licenses if @licenses;
406 return [ CPANPLUS::Dist::Gentoo::Maps::license_c2g('perl') ];
411 my $stat = $self->status;
417 $stat->dist($file) if defined $file;
424 $self->_abort(@_) if @_;
425 if (defined $file and -f $file) {
426 1 while unlink $file;
431 unless ($stat->prepared) {
433 'Can\'t create', $stat->distribution, 'since it was never prepared'
437 if ($stat->created) {
438 $self->_skip($stat->distribution, 'was already created');
439 $file = $stat->dist; # Keep the existing one.
443 my $dir = $stat->ebuild_dir;
445 eval { File::Path::mkpath($dir) };
446 return $FAIL->("mkpath($dir): $@") if $@;
449 $file = $stat->ebuild_file;
451 # Create a placeholder ebuild to prevent recursion with circular dependencies.
453 open my $eb, '>', $file or return $FAIL->("open($file): $!");
454 print $eb "PLACEHOLDER\n";
460 $self->SUPER::create(@_);
462 return $FAIL->() unless $stat->created;
465 open my $eb, '>', $file or return $FAIL->("open($file): $!");
466 my $source = $self->ebuild_source;
467 return $FAIL->() unless defined $source;
471 return $FAIL->() if $stat->do_manifest and not $self->update_manifest;
476 =head2 C<update_manifest>
478 Updates the F<Manifest> file for the ebuild associated to the current dist object.
482 sub update_manifest {
484 my $stat = $self->status;
486 my $file = $stat->ebuild_file;
487 unless (defined $file and -e $file) {
488 return $self->_abort('The ebuild file is invalid or does not exist');
491 unless (File::Copy::copy($stat->fetched_arch => $stat->distdir)) {
492 return $self->_abort("Couldn\'t copy the distribution file to distdir ($!)");
495 $self->_notify('Adding Manifest entry for', $stat->distribution);
497 return $self->_run([ 'ebuild', $file, 'manifest' ], 0);
500 =head2 C<ebuild_source>
502 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.
508 my $stat = $self->status;
510 # We must resolve the deps now and not inside prepare because _cpan2portage
511 # has to see the ebuilds already generated for the dependencies of the current
514 for (@{$stat->requires}) {
515 my $dep = $self->_cpan2portage(@$_);
516 unless (defined $dep) {
518 "Couldn't find an appropriate ebuild for $_->[0] in the portage tree"
522 push @requires, $dep;
525 @requires = do { my %seen; sort grep !$seen{$_}++, 'dev-lang/perl',@requires };
527 my $d = $stat->header;
528 $d .= "# Generated by CPANPLUS::Dist::Gentoo version $VERSION\n\n";
529 $d .= 'MODULE_AUTHOR="' . $stat->author . "\"\ninherit perl-module\n\n";
530 $d .= 'S="${WORKDIR}/' . $stat->distribution . "\"\n";
531 $d .= 'DESCRIPTION="' . $stat->desc . "\"\n";
532 $d .= 'HOMEPAGE="' . $stat->uri . "\"\n";
533 $d .= 'SRC_URI="' . $stat->src . "\"\n";
534 $d .= "SLOT=\"0\"\n";
535 $d .= 'LICENSE="|| ( ' . join(' ', sort @{$stat->license}) . " )\"\n";
536 $d .= 'KEYWORDS="' . join(' ', sort @{$stat->keywords}) . "\"\n";
537 $d .= 'RDEPEND="' . join("\n", @requires) . "\"\n";
538 $d .= "DEPEND=\"\${RDEPEND}\"\n";
539 $d .= "SRC_TEST=\"do\"\n";
546 my ($self, $name, $version) = @_;
548 $name = CPANPLUS::Dist::Gentoo::Maps::name_c2g($name);
550 $ver = CPANPLUS::Dist::Gentoo::Maps::version_c2g($version) if defined $version;
552 my @portdirs = ($main_portdir, @{$self->status->portdir_overlay});
554 for my $category (qw/virtual perl-core dev-perl perl-gcpan/, CATEGORY) {
555 my $atom = ($category eq 'virtual' ? 'perl-' : '') . $name;
557 for my $portdir (@portdirs) {
558 my @ebuilds = glob File::Spec->catfile(
566 CPANPLUS::Dist::Gentoo::Maps::version_gcmp($b->[1], $a->[1]) >= 0 ? $b : $a
567 } map [ $_, /\Q$atom\E-v?([\d._pr-]+).*?\.ebuild$/ ? $1 : 0 ], @ebuilds;
570 if (defined $ver) { # implies that $version is defined
572 CPANPLUS::Dist::Gentoo::Maps::version_gcmp($last->[1], $ver) >= 0;
573 $dep = ">=$category/$atom-$ver";
575 $dep = "$category/$atom";
578 return wantarray ? ($dep, $last->[0]) : $dep;
588 my $stat = $self->status;
589 my $conf = $self->parent->parent->configure_object;
591 my $sudo = $conf->get_program('sudo');
592 my @cmd = ('emerge', '=' . $stat->ebuild_name . '-' . $stat->ebuild_version);
593 unshift @cmd, $sudo if $sudo;
595 my $success = $self->_run(\@cmd, 1);
596 $stat->installed($success);
603 my $stat = $self->status;
604 my $conf = $self->parent->parent->configure_object;
606 my $sudo = $conf->get_program('sudo');
607 my @cmd = ('emerge', '-C', '=' . $stat->ebuild_name . '-' . $stat->ebuild_version);
608 unshift @cmd, $sudo if $sudo;
610 my $success = $self->_run(\@cmd, 1);
611 $stat->uninstalled($success);
617 my ($self, $cmd, $verbose) = @_;
618 my $stat = $self->status;
620 my ($success, $errmsg, $output) = do {
621 local $ENV{PORTDIR_OVERLAY} = join ' ', @{$stat->portdir_overlay};
622 local $ENV{PORTAGE_RO_DISTDIRS} = $stat->distdir;
623 run command => $cmd, verbose => $verbose;
627 $self->_abort($errmsg);
628 if (not $verbose and defined $output and $stat->verbose) {
629 my $msg = join '', @$output;
631 CPANPLUS::Error::error($msg);
641 CPANPLUS::Error::error("@_ -- aborting");
649 CPANPLUS::Error::msg("@_");
654 sub _skip { shift->_notify(@_, '-- skipping') }
658 Gentoo (L<http://gentoo.org>).
660 L<CPANPLUS>, L<IPC::Cmd> (core modules since 5.9.5), L<Parse::CPAN::Meta> (since 5.10.1).
662 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).
668 L<CPANPLUS::Dist::Base>, L<CPANPLUS::Dist::Deb>, L<CPANPLUS::Dist::Mdv>.
672 Vincent Pit, C<< <perl at profvince.com> >>, L<http://www.profvince.com>.
674 You can contact me by mail or on C<irc.perl.org> (vincent).
678 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>.
679 I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
683 You can find documentation for this module with the perldoc command.
685 perldoc CPANPLUS::Dist::Gentoo
687 =head1 ACKNOWLEDGEMENTS
689 The module was inspired by L<CPANPLUS::Dist::Deb> and L<CPANPLUS::Dist::Mdv>.
691 Kent Fredric, for testing and suggesting improvements.
693 =head1 COPYRIGHT & LICENSE
695 Copyright 2008-2009 Vincent Pit, all rights reserved.
697 This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
701 1; # End of CPANPLUS::Dist::Gentoo