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> which will most likely be up-to-date given the reactivity of Gentoo's Perl herd.
70 Bootstrap an ebuild for L<CPANPLUS::Dist::Gentoo> using itself.
72 First, make sure your system C<perl> is C<5.10> or greater, so that the L<CPANPLUS> toolchain is available.
75 This is perl 5, version 12, subversion 2 (v5.12.2) built for x86_64-linux
78 C<perl> C<5.12> is the current stable Perl version in Gentoo.
79 If you still have C<perl> C<5.8.x>, you can upgrade it by running the following commands as root :
81 # emerge -tv ">=dev-lang/perl-5.10"
84 Then, fetch the L<CPANPLUS::Dist::Gentoo> tarball :
87 $ wget http://search.cpan.org/CPAN/authors/id/V/VP/VPIT/CPANPLUS-Dist-Gentoo-0.10.tar.gz
89 Log in as root and unpack it in e.g. your home directory :
92 # tar xzf /tmp/CPANPLUS-Dist-Gentoo-0.10.tar.gz
93 # cd CPANPLUS-Dist-Gentoo-0.10
95 Bootstrap L<CPANPLUS::Dist::Gentoo> using the bundled shell script C<g-cpanp> :
97 # PERL5LIB=blib/lib samples/g-cpanp CPANPLUS::Dist::Gentoo
99 Finally, emerge the C<CPANPLUS-Dist-Gentoo> ebuild you've just generated :
101 # emerge -tv CPANPLUS-Dist-Gentoo
107 This module inherits all the methods from L<CPANPLUS::Dist::Base>.
108 Please refer to its documentation for precise information on what's done at each step.
112 use constant CATEGORY => 'perl-gcpanp';
115 my $default_keywords;
129 my $format_available;
131 sub format_available {
132 return $format_available if defined $format_available;
134 for my $prog (qw<emerge ebuild>) {
135 unless (IPC::Cmd::can_run($prog)) {
136 __PACKAGE__->_abort("$prog is required to write ebuilds");
137 return $format_available = 0;
141 if (IPC::Cmd->can_capture_buffer) {
143 my ($success, $errmsg) = IPC::Cmd::run(
144 command => [ qw<emerge --info> ],
149 if ($buffers =~ /^PORTDIR_OVERLAY=(.*)$/m) {
150 $overlays = [ map Cwd::abs_path($_), split ' ', $unquote->($1) ];
152 if ($buffers =~ /^ACCEPT_KEYWORDS=(.*)$/m) {
153 $default_keywords = [ split ' ', $unquote->($1) ];
155 if ($buffers =~ /^DISTDIR=(.*)$/m) {
156 $default_distdir = Cwd::abs_path($unquote->($1));
158 if ($buffers =~ /^PORTDIR=(.*)$/m) {
159 $main_portdir = Cwd::abs_path($unquote->($1));
162 __PACKAGE__->_abort($errmsg);
166 $default_keywords = [ 'x86' ] unless defined $default_keywords;
167 $default_distdir = '/usr/portage/distfiles' unless defined $default_distdir;
169 return $format_available = 1;
174 my $stat = $self->status;
175 my $conf = $self->parent->parent->configure_object;
177 $stat->mk_accessors(qw<
178 name version author distribution desc uri src license
181 requires configure_requires recursive_requires
182 ebuild_name ebuild_version ebuild_dir ebuild_file
183 portdir_overlay overlay distdir keywords do_manifest header footer
187 $stat->force($conf->get_conf('force'));
188 $stat->verbose($conf->get_conf('verbose'));
193 my $filter_prereqs = sub {
194 my ($int, $prereqs) = @_;
197 for my $prereq (sort keys %$prereqs) {
198 next if $prereq =~ /^perl(?:-|\z)/;
200 my $obj = $int->module_tree($prereq);
201 next unless $obj; # Not in the module tree (e.g. Config)
202 next if $obj->package_is_perl_core;
204 my $version = $prereqs->{$prereq} || undef;
206 push @requires, [ $obj->package_name, $version ];
214 my $mod = $self->parent;
215 my $stat = $self->status;
216 my $int = $mod->parent;
217 my $conf = $int->configure_object;
221 my $OK = sub { $stat->prepared(1); 1 };
222 my $FAIL = sub { $stat->prepared(0); $self->_abort(@_) if @_; 0 };
223 my $SKIP = sub { $stat->prepared(1); $stat->created(1); $self->_skip(@_) if @_; 1 };
225 my $keywords = delete $opts{keywords};
226 if (defined $keywords) {
227 $keywords = [ split ' ', $keywords ];
229 $keywords = $default_keywords;
231 $stat->keywords($keywords);
233 my $manifest = delete $opts{manifest};
234 $manifest = 1 unless defined $manifest;
235 $manifest = 0 if $manifest =~ /^\s*no?\s*$/i;
236 $stat->do_manifest($manifest);
238 my $header = delete $opts{header};
239 if (defined $header) {
240 1 while chomp $header;
243 my $year = (localtime)[5] + 1900;
244 $header = <<" DEF_HEADER";
245 # Copyright 1999-$year Gentoo Foundation
246 # Distributed under the terms of the GNU General Public License v2
250 $stat->header($header);
252 my $footer = delete $opts{footer};
253 if (defined $footer) {
254 $footer = "\n" . $footer;
258 $stat->footer($footer);
260 my $overlay = delete $opts{overlay};
261 $overlay = (defined $overlay) ? Cwd::abs_path($overlay) : '/usr/local/portage';
262 $stat->overlay($overlay);
264 my $distdir = delete $opts{distdir};
265 $distdir = (defined $distdir) ? Cwd::abs_path($distdir) : $default_distdir;
266 $stat->distdir($distdir);
268 return $FAIL->("distdir isn't writable") if $stat->do_manifest && !-w $distdir;
270 $stat->fetched_arch($mod->status->fetch);
272 my $cur = File::Spec->curdir();
275 if ($_ eq $overlay or File::Spec->abs2rel($overlay, $_) eq $cur) {
276 $portdir_overlay = [ @$overlays ];
280 $portdir_overlay = [ @$overlays, $overlay ] unless $portdir_overlay;
281 $stat->portdir_overlay($portdir_overlay);
283 my $name = $mod->package_name;
286 my $version = $mod->package_version;
287 $stat->version($version);
289 my $author = $mod->author->cpanid;
290 $stat->author($author);
292 $stat->distribution($name . '-' . $version);
294 $stat->ebuild_version(CPANPLUS::Dist::Gentoo::Maps::version_c2g($name, $version));
296 $stat->ebuild_name(CPANPLUS::Dist::Gentoo::Maps::name_c2g($name));
298 $stat->ebuild_dir(File::Spec->catdir(
304 my $file = File::Spec->catfile(
306 $stat->ebuild_name . '-' . $stat->ebuild_version . '.ebuild',
308 $stat->ebuild_file($file);
311 # Always generate an ebuild in our category when forcing
312 if ($forced{$file}) {
314 return $SKIP->('Ebuild already forced for', $stat->distribution);
320 return $SKIP->("Can't force rewriting of $file");
322 1 while unlink $file;
325 if (my $atom = $self->_cpan2portage($name, $version)) {
326 $stat->dist($atom->ebuild);
327 return $SKIP->('Ebuild already generated for', $stat->distribution);
333 $self->SUPER::prepare(@_);
335 return $FAIL->() unless $stat->prepared;
337 my $desc = $mod->description;
338 $desc = $mod->comment unless $desc;
339 $desc = "$name Perl distribution (provides " . $mod->module . ')'
341 $desc = substr($desc, 0, 77) . '...' if length $desc > 80;
344 $stat->uri('http://search.cpan.org/dist/' . $name);
346 $author =~ /^(.)(.)/ or return $FAIL->('Wrong author name');
347 $stat->src("mirror://cpan/modules/by-authors/id/$1/$1$2/$author/" . $mod->package);
349 $stat->license($self->intuit_license);
351 my $mstat = $mod->status;
352 $stat->configure_requires($int->$filter_prereqs($mstat->configure_requires));
353 $stat->requires($int->$filter_prereqs($mstat->requires));
354 $stat->recursive_requires([ ]);
356 $dependencies{$name} = [ map $_->[0], @{ $stat->requires } ];
358 my $meta = $self->meta;
359 $stat->min_perl(CPANPLUS::Dist::Gentoo::Maps::perl_version_c2g(
360 $meta->{requires}->{perl},
368 Returns the contents of the F<META.yml> or F<META.json> files as parsed by L<Parse::CPAN::Meta>.
374 my $mod = $self->parent;
375 my $stat = $self->status;
377 my $meta = $stat->meta;
378 return $meta if defined $meta;
380 my $extract_dir = $mod->status->extract;
382 for my $name (qw<META.json META.yml>) {
383 my $meta_file = File::Spec->catdir($extract_dir, $name);
384 next unless -e $meta_file;
387 my $meta = eval { Parse::CPAN::Meta::LoadFile($meta_file) };
397 =head2 C<intuit_license>
399 Returns an array reference to a list of Gentoo licences identifiers under which the current distribution is released.
403 my %dslip_license = (
414 my $mod = $self->parent;
416 my $dslip = $mod->dslip;
417 if (defined $dslip and $dslip =~ /\S{4}(\S)/) {
418 my @licenses = CPANPLUS::Dist::Gentoo::Maps::license_c2g($dslip_license{$1});
419 return \@licenses if @licenses;
422 my $meta = $self->meta;
423 my $license = $meta->{license};
424 if (defined $license) {
425 my @licenses = CPANPLUS::Dist::Gentoo::Maps::license_c2g($license);
426 return \@licenses if @licenses;
429 return [ CPANPLUS::Dist::Gentoo::Maps::license_c2g('perl') ];
434 my $stat = $self->status;
438 my $guard = CPANPLUS::Dist::Gentoo::Guard->new(sub {
439 if (defined $file and -e $file and -w _) {
440 1 while unlink $file;
444 my $SIG_INT = $SIG{INT};
445 local $SIG{INT} = sub {
448 eval { $SIG_INT->() };
457 $stat->dist($file) if defined $file;
464 $self->_abort(@_) if @_;
468 unless ($stat->prepared) {
470 'Can\'t create', $stat->distribution, 'since it was never prepared'
474 if ($stat->created) {
475 $self->_skip($stat->distribution, 'was already created');
476 $file = $stat->dist; # Keep the existing one.
480 my $dir = $stat->ebuild_dir;
482 eval { File::Path::mkpath($dir) };
483 return $FAIL->("mkpath($dir): $@") if $@;
486 $file = $stat->ebuild_file;
488 # Create a placeholder ebuild to prevent recursion with circular dependencies.
490 open my $eb, '>', $file or return $FAIL->("open($file): $!");
491 print $eb "PLACEHOLDER\n";
497 $self->SUPER::create(@_);
499 return $FAIL->() unless $stat->created;
502 open my $eb, '>', $file or return $FAIL->("open($file): $!");
503 my $source = $self->ebuild_source;
504 return $FAIL->() unless defined $source;
508 return $FAIL->() if $stat->do_manifest and not $self->update_manifest;
513 =head2 C<update_manifest>
515 Updates the F<Manifest> file for the ebuild associated to the current dist object.
519 sub update_manifest {
521 my $stat = $self->status;
523 my $file = $stat->ebuild_file;
524 unless (defined $file and -e $file) {
525 return $self->_abort('The ebuild file is invalid or does not exist');
528 unless (File::Copy::copy($stat->fetched_arch => $stat->distdir)) {
529 return $self->_abort("Couldn\'t copy the distribution file to distdir ($!)");
532 $self->_notify('Adding Manifest entry for', $stat->distribution);
534 return $self->_run([ 'ebuild', $file, 'manifest' ], 0);
537 =head2 C<ebuild_source>
539 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.
543 my $dep_tree_contains;
547 $dep_tree_contains = sub {
548 my ($dist, $target) = @_;
550 return 0 if $seen{$dist};
551 local $seen{$dist} = 1;
553 for my $kid (@{ $dependencies{$dist} }) {
554 return 1 if $kid eq $target
555 or $dep_tree_contains->($kid, $target);
564 my $stat = $self->status;
567 my $name = $stat->name;
568 my %recursive_kids = map { $_ => 1 }
569 grep $dep_tree_contains->($_, $name),
570 @{ $dependencies{$name} };
571 if (%recursive_kids) {
572 my (@requires, @recursive_requires);
573 for (@{ $stat->requires }) {
574 if ($recursive_kids{$_->[0]}) {
575 push @recursive_requires, $_;
580 $stat->requires(\@requires);
581 $stat->recursive_requires(\@recursive_requires);
585 # We must resolve the deps now and not inside prepare because _cpan2portage
586 # has to see the ebuilds already generated for the dependencies of the current
589 my (@configure_requires, @requires, @recursive_requires);
592 [ configure_requires => \@configure_requires ],
593 [ requires => \@requires ],
594 [ recursive_requires => \@recursive_requires ],
597 push @requires, CPANPLUS::Dist::Gentoo::Atom->new(
598 category => 'dev-lang',
600 version => $stat->min_perl,
604 my ($phase, $list) = @$_;
606 for (@{ $stat->$phase }) {
607 my $atom = $self->_cpan2portage(@$_);
608 unless (defined $atom) {
610 "Couldn't find an appropriate ebuild for $_->[0] in the portage tree"
618 @$list = CPANPLUS::Dist::Gentoo::Atom->fold(@$list);
621 my $d = $stat->header;
622 $d .= "# Generated by CPANPLUS::Dist::Gentoo version $VERSION\n\n";
623 $d .= 'MODULE_AUTHOR="' . $stat->author . "\"\ninherit perl-module\n\n";
624 $d .= 'S="${WORKDIR}/' . $stat->distribution . "\"\n";
625 $d .= 'DESCRIPTION="' . $stat->desc . "\"\n";
626 $d .= 'HOMEPAGE="' . $stat->uri . "\"\n";
627 $d .= 'SRC_URI="' . $stat->src . "\"\n";
628 $d .= "SLOT=\"0\"\n";
629 $d .= 'LICENSE="|| ( ' . join(' ', sort @{$stat->license}) . " )\"\n";
630 $d .= 'KEYWORDS="' . join(' ', sort @{$stat->keywords}) . "\"\n";
631 $d .= 'RDEPEND="' . join("\n", sort @requires) . "\"\n" if @requires;
632 $d .= 'PDEPEND="' . join("\n", sort @recursive_requires) . "\"\n"
633 if @recursive_requires;
634 $d .= 'DEPEND="' . join("\n", '${RDEPEND}', sort @configure_requires) . "\"\n";
635 $d .= "SRC_TEST=\"do\"\n";
642 my ($self, $dist_name, $dist_version) = @_;
644 my $name = CPANPLUS::Dist::Gentoo::Maps::name_c2g($dist_name);
645 my $version = CPANPLUS::Dist::Gentoo::Maps::version_c2g($dist_name, $dist_version);
647 my @portdirs = ($main_portdir, @{$self->status->portdir_overlay});
649 for my $category (qw<virtual perl-core dev-perl perl-gcpan>, CATEGORY) {
650 my $name = ($category eq 'virtual' ? 'perl-' : '') . $name;
652 for my $portdir (@portdirs) {
653 my @ebuilds = glob File::Spec->catfile(
660 my $last = reduce { $a < $b ? $b : $a } # handles overloading
661 map CPANPLUS::Dist::Gentoo::Atom->new_from_ebuild($_),
663 next if defined $version and $last < $version;
665 return CPANPLUS::Dist::Gentoo::Atom->new(
666 category => $last->category,
669 ebuild => $last->ebuild,
680 my $stat = $self->status;
681 my $conf = $self->parent->parent->configure_object;
683 my $sudo = $conf->get_program('sudo');
684 my @cmd = ('emerge', '=' . $stat->ebuild_name . '-' . $stat->ebuild_version);
685 unshift @cmd, $sudo if $sudo;
687 my $success = $self->_run(\@cmd, 1);
688 $stat->installed($success);
695 my $stat = $self->status;
696 my $conf = $self->parent->parent->configure_object;
698 my $sudo = $conf->get_program('sudo');
699 my @cmd = ('emerge', '-C', '=' . $stat->ebuild_name . '-' . $stat->ebuild_version);
700 unshift @cmd, $sudo if $sudo;
702 my $success = $self->_run(\@cmd, 1);
703 $stat->uninstalled($success);
709 my ($self, $cmd, $verbose) = @_;
710 my $stat = $self->status;
712 my ($success, $errmsg, $output) = do {
713 local $ENV{PORTDIR_OVERLAY} = join ' ', @{$stat->portdir_overlay};
714 local $ENV{PORTAGE_RO_DISTDIRS} = $stat->distdir;
722 $self->_abort($errmsg);
723 if (not $verbose and defined $output and $stat->verbose) {
724 my $msg = join '', @$output;
726 CPANPLUS::Error::error($msg);
736 CPANPLUS::Error::error("@_ -- aborting");
744 CPANPLUS::Error::msg("@_");
749 sub _skip { shift->_notify(@_, '-- skipping') }
753 Gentoo (L<http://gentoo.org>).
755 L<CPANPLUS>, L<IPC::Cmd> (core modules since 5.9.5), L<Parse::CPAN::Meta> (since 5.10.1).
757 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).
763 L<CPANPLUS::Dist::Base>, L<CPANPLUS::Dist::Deb>, L<CPANPLUS::Dist::Mdv>.
767 Vincent Pit, C<< <perl at profvince.com> >>, L<http://www.profvince.com>.
769 You can contact me by mail or on C<irc.perl.org> (vincent).
773 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>.
774 I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
778 You can find documentation for this module with the perldoc command.
780 perldoc CPANPLUS::Dist::Gentoo
782 =head1 ACKNOWLEDGEMENTS
784 The module was inspired by L<CPANPLUS::Dist::Deb> and L<CPANPLUS::Dist::Mdv>.
786 Kent Fredric, for testing and suggesting improvements.
788 =head1 COPYRIGHT & LICENSE
790 Copyright 2008,2009,2010 Vincent Pit, all rights reserved.
792 This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
796 1; # End of CPANPLUS::Dist::Gentoo