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::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/K/KA/KANE/CPANPLUS-0.88.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.88.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.88/bin:$PATH
89 # export PERL5LIB=/root/CPANPLUS-Dist-Gentoo-0.09/blib/lib:/root/CPANPLUS-0.88/lib:/root/CPANPLUS-0.88/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 (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) = run command => [ qw/emerge --info/ ],
165 if ($buffers =~ /^PORTDIR_OVERLAY=(.*)$/m) {
166 $overlays = [ map abs_path($_), split ' ', $unquote->($1) ];
168 if ($buffers =~ /^ACCEPT_KEYWORDS=(.*)$/m) {
169 $default_keywords = [ split ' ', $unquote->($1) ];
171 if ($buffers =~ /^DISTDIR=(.*)$/m) {
172 $default_distdir = abs_path($unquote->($1));
174 if ($buffers =~ /^PORTDIR=(.*)$/m) {
175 $main_portdir = abs_path($unquote->($1));
178 __PACKAGE__->_abort($errmsg);
182 $default_keywords = [ 'x86' ] unless defined $default_keywords;
183 $default_distdir = '/usr/portage/distfiles' unless defined $default_distdir;
185 return $format_available = 1;
190 my $stat = $self->status;
191 my $conf = $self->parent->parent->configure_object;
193 $stat->mk_accessors(qw/name version author distribution desc uri src license
194 fetched_arch requires
195 ebuild_name ebuild_version ebuild_dir ebuild_file
197 overlay distdir keywords do_manifest header footer
200 $stat->force($conf->get_conf('force'));
201 $stat->verbose($conf->get_conf('verbose'));
208 my $mod = $self->parent;
209 my $stat = $self->status;
210 my $int = $mod->parent;
211 my $conf = $int->configure_object;
215 my $OK = sub { $stat->prepared(1); 1 };
216 my $FAIL = sub { $stat->prepared(0); $self->_abort(@_) if @_; 0 };
217 my $SKIP = sub { $stat->prepared(1); $stat->created(1); $self->_skip(@_) if @_; 1 };
219 my $keywords = delete $opts{keywords};
220 if (defined $keywords) {
221 $keywords = [ split ' ', $keywords ];
223 $keywords = $default_keywords;
225 $stat->keywords($keywords);
227 my $manifest = delete $opts{manifest};
228 $manifest = 1 unless defined $manifest;
229 $manifest = 0 if $manifest =~ /^\s*no?\s*$/i;
230 $stat->do_manifest($manifest);
232 my $header = delete $opts{header};
233 if (defined $header) {
234 1 while chomp $header;
237 my $year = (localtime)[5] + 1900;
238 $header = <<" DEF_HEADER";
239 # Copyright 1999-$year Gentoo Foundation
240 # Distributed under the terms of the GNU General Public License v2
244 $stat->header($header);
246 my $footer = delete $opts{footer};
247 if (defined $footer) {
248 $footer = "\n" . $footer;
252 $stat->footer($footer);
254 my $overlay = delete $opts{overlay};
255 $overlay = (defined $overlay) ? abs_path $overlay : '/usr/local/portage';
256 $stat->overlay($overlay);
258 my $distdir = delete $opts{distdir};
259 $distdir = (defined $distdir) ? abs_path $distdir : $default_distdir;
260 $stat->distdir($distdir);
262 return $FAIL->("distdir isn't writable") if $stat->do_manifest && !-w $distdir;
264 $stat->fetched_arch($mod->status->fetch);
266 my $cur = File::Spec->curdir();
269 if ($_ eq $overlay or File::Spec->abs2rel($overlay, $_) eq $cur) {
270 $portdir_overlay = [ @$overlays ];
274 $portdir_overlay = [ @$overlays, $overlay ] unless $portdir_overlay;
275 $stat->portdir_overlay($portdir_overlay);
277 my $name = $mod->package_name;
280 my $version = $mod->package_version;
281 $stat->version($version);
283 my $author = $mod->author->cpanid;
284 $stat->author($author);
286 $stat->distribution($name . '-' . $version);
288 $stat->ebuild_version(CPANPLUS::Dist::Gentoo::Maps::version_c2g($version));
290 $stat->ebuild_name(CPANPLUS::Dist::Gentoo::Maps::name_c2g($name));
292 $stat->ebuild_dir(File::Spec->catdir(
298 my $file = File::Spec->catfile(
300 $stat->ebuild_name . '-' . $stat->ebuild_version . '.ebuild',
302 $stat->ebuild_file($file);
305 # Always generate an ebuild in our category when forcing
306 if ($forced{$file}) {
308 return $SKIP->('Ebuild already forced for', $stat->distribution);
314 return $SKIP->("Can't force rewriting of $file");
316 1 while unlink $file;
319 if (my $atom = $self->_cpan2portage($name, $version)) {
320 $stat->dist($atom->ebuild);
321 return $SKIP->('Ebuild already generated for', $stat->distribution);
327 $self->SUPER::prepare(@_);
329 return $FAIL->() unless $stat->prepared;
331 my $desc = $mod->description;
332 ($desc = $name) =~ s/-+/::/g unless $desc;
335 $stat->uri('http://search.cpan.org/dist/' . $name);
337 $author =~ /^(.)(.)/ or return $FAIL->('Wrong author name');
338 $stat->src("mirror://cpan/modules/by-authors/id/$1/$1$2/$author/" . $mod->package);
340 $stat->license($self->intuit_license);
342 my $prereqs = $mod->status->requires;
344 for my $prereq (sort keys %$prereqs) {
345 next if $prereq =~ /^perl(?:-|\z)/;
346 my $obj = $int->module_tree($prereq);
347 next unless $obj; # Not in the module tree (e.g. Config)
348 next if $obj->package_is_perl_core;
351 if ($prereqs->{$prereq}) {
352 if ($obj->installed_version && $obj->installed_version < $obj->version) {
353 $version = $obj->installed_version;
355 $version = $obj->package_version;
358 push @requires, [ $obj->package_name, $version ];
361 $stat->requires(\@requires);
366 =head2 C<intuit_license>
368 Returns an array reference to a list of Gentoo licences identifiers under which the current distribution is released.
372 my %dslip_license = (
383 my $mod = $self->parent;
385 my $dslip = $mod->dslip;
386 if (defined $dslip and $dslip =~ /\S{4}(\S)/) {
387 my @licenses = CPANPLUS::Dist::Gentoo::Maps::license_c2g($dslip_license{$1});
388 return \@licenses if @licenses;
391 my $extract_dir = $mod->status->extract;
393 for my $meta_file (qw/META.json META.yml/) {
395 Parse::CPAN::Meta::LoadFile(File::Spec->catdir(
400 my $license = $meta->{license};
401 if (defined $license) {
402 my @licenses = CPANPLUS::Dist::Gentoo::Maps::license_c2g($license);
403 return \@licenses if @licenses;
407 return [ CPANPLUS::Dist::Gentoo::Maps::license_c2g('perl') ];
412 my $stat = $self->status;
418 $stat->dist($file) if defined $file;
425 $self->_abort(@_) if @_;
426 if (defined $file and -f $file) {
427 1 while unlink $file;
432 unless ($stat->prepared) {
434 'Can\'t create', $stat->distribution, 'since it was never prepared'
438 if ($stat->created) {
439 $self->_skip($stat->distribution, 'was already created');
440 $file = $stat->dist; # Keep the existing one.
444 my $dir = $stat->ebuild_dir;
446 eval { File::Path::mkpath($dir) };
447 return $FAIL->("mkpath($dir): $@") if $@;
450 $file = $stat->ebuild_file;
452 # Create a placeholder ebuild to prevent recursion with circular dependencies.
454 open my $eb, '>', $file or return $FAIL->("open($file): $!");
455 print $eb "PLACEHOLDER\n";
461 $self->SUPER::create(@_);
463 return $FAIL->() unless $stat->created;
466 open my $eb, '>', $file or return $FAIL->("open($file): $!");
467 my $source = $self->ebuild_source;
468 return $FAIL->() unless defined $source;
472 return $FAIL->() if $stat->do_manifest and not $self->update_manifest;
477 =head2 C<update_manifest>
479 Updates the F<Manifest> file for the ebuild associated to the current dist object.
483 sub update_manifest {
485 my $stat = $self->status;
487 my $file = $stat->ebuild_file;
488 unless (defined $file and -e $file) {
489 return $self->_abort('The ebuild file is invalid or does not exist');
492 unless (File::Copy::copy($stat->fetched_arch => $stat->distdir)) {
493 return $self->_abort("Couldn\'t copy the distribution file to distdir ($!)");
496 $self->_notify('Adding Manifest entry for', $stat->distribution);
498 return $self->_run([ 'ebuild', $file, 'manifest' ], 0);
501 =head2 C<ebuild_source>
503 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.
509 my $stat = $self->status;
511 # We must resolve the deps now and not inside prepare because _cpan2portage
512 # has to see the ebuilds already generated for the dependencies of the current
515 for (@{$stat->requires}) {
516 my $atom = $self->_cpan2portage(@$_);
517 unless (defined $atom) {
519 "Couldn't find an appropriate ebuild for $_->[0] in the portage tree"
523 push @requires, $atom;
526 my $perl = CPANPLUS::Dist::Gentoo::Atom->new(
527 category => 'dev-lang',
530 @requires = CPANPLUS::Dist::Gentoo::Atom->fold($perl, @requires);
532 my $d = $stat->header;
533 $d .= "# Generated by CPANPLUS::Dist::Gentoo version $VERSION\n\n";
534 $d .= 'MODULE_AUTHOR="' . $stat->author . "\"\ninherit perl-module\n\n";
535 $d .= 'S="${WORKDIR}/' . $stat->distribution . "\"\n";
536 $d .= 'DESCRIPTION="' . $stat->desc . "\"\n";
537 $d .= 'HOMEPAGE="' . $stat->uri . "\"\n";
538 $d .= 'SRC_URI="' . $stat->src . "\"\n";
539 $d .= "SLOT=\"0\"\n";
540 $d .= 'LICENSE="|| ( ' . join(' ', sort @{$stat->license}) . " )\"\n";
541 $d .= 'KEYWORDS="' . join(' ', sort @{$stat->keywords}) . "\"\n";
542 $d .= 'RDEPEND="' . join("\n", sort @requires) . "\"\n";
543 $d .= "DEPEND=\"\${RDEPEND}\"\n";
544 $d .= "SRC_TEST=\"do\"\n";
551 my ($self, $name, $version) = @_;
553 $name = CPANPLUS::Dist::Gentoo::Maps::name_c2g($name);
554 $version = CPANPLUS::Dist::Gentoo::Maps::version_c2g($version);
556 my @portdirs = ($main_portdir, @{$self->status->portdir_overlay});
558 for my $category (qw/virtual perl-core dev-perl perl-gcpan/, CATEGORY) {
559 my $name = ($category eq 'virtual' ? 'perl-' : '') . $name;
561 for my $portdir (@portdirs) {
562 my @ebuilds = glob File::Spec->catfile(
569 my $last = reduce { $a < $b ? $b : $a } # handles overloading
570 map CPANPLUS::Dist::Gentoo::Atom->new_from_ebuild($_),
572 next if defined $version and $last < $version;
574 return CPANPLUS::Dist::Gentoo::Atom->new(
575 category => $last->category,
577 (defined $version ? (version => $version, range => '>=') : ()),
578 ebuild => $last->ebuild,
589 my $stat = $self->status;
590 my $conf = $self->parent->parent->configure_object;
592 my $sudo = $conf->get_program('sudo');
593 my @cmd = ('emerge', '=' . $stat->ebuild_name . '-' . $stat->ebuild_version);
594 unshift @cmd, $sudo if $sudo;
596 my $success = $self->_run(\@cmd, 1);
597 $stat->installed($success);
604 my $stat = $self->status;
605 my $conf = $self->parent->parent->configure_object;
607 my $sudo = $conf->get_program('sudo');
608 my @cmd = ('emerge', '-C', '=' . $stat->ebuild_name . '-' . $stat->ebuild_version);
609 unshift @cmd, $sudo if $sudo;
611 my $success = $self->_run(\@cmd, 1);
612 $stat->uninstalled($success);
618 my ($self, $cmd, $verbose) = @_;
619 my $stat = $self->status;
621 my ($success, $errmsg, $output) = do {
622 local $ENV{PORTDIR_OVERLAY} = join ' ', @{$stat->portdir_overlay};
623 local $ENV{PORTAGE_RO_DISTDIRS} = $stat->distdir;
624 run command => $cmd, verbose => $verbose;
628 $self->_abort($errmsg);
629 if (not $verbose and defined $output and $stat->verbose) {
630 my $msg = join '', @$output;
632 CPANPLUS::Error::error($msg);
642 CPANPLUS::Error::error("@_ -- aborting");
650 CPANPLUS::Error::msg("@_");
655 sub _skip { shift->_notify(@_, '-- skipping') }
659 Gentoo (L<http://gentoo.org>).
661 L<CPANPLUS>, L<IPC::Cmd> (core modules since 5.9.5), L<Parse::CPAN::Meta> (since 5.10.1).
663 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).
669 L<CPANPLUS::Dist::Base>, L<CPANPLUS::Dist::Deb>, L<CPANPLUS::Dist::Mdv>.
673 Vincent Pit, C<< <perl at profvince.com> >>, L<http://www.profvince.com>.
675 You can contact me by mail or on C<irc.perl.org> (vincent).
679 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>.
680 I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
684 You can find documentation for this module with the perldoc command.
686 perldoc CPANPLUS::Dist::Gentoo
688 =head1 ACKNOWLEDGEMENTS
690 The module was inspired by L<CPANPLUS::Dist::Deb> and L<CPANPLUS::Dist::Mdv>.
692 Kent Fredric, for testing and suggesting improvements.
694 =head1 COPYRIGHT & LICENSE
696 Copyright 2008-2009 Vincent Pit, all rights reserved.
698 This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
702 1; # End of CPANPLUS::Dist::Gentoo