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;
149 my $format_available;
151 sub format_available {
152 return $format_available if defined $format_available;
154 for my $prog (qw/emerge ebuild/) {
155 unless (IPC::Cmd::can_run($prog)) {
156 __PACKAGE__->_abort("$prog is required to write ebuilds");
157 return $format_available = 0;
161 if (IPC::Cmd->can_capture_buffer) {
163 my ($success, $errmsg) = IPC::Cmd::run(
164 command => [ qw/emerge --info/ ],
169 if ($buffers =~ /^PORTDIR_OVERLAY=(.*)$/m) {
170 $overlays = [ map Cwd::abs_path($_), split ' ', $unquote->($1) ];
172 if ($buffers =~ /^ACCEPT_KEYWORDS=(.*)$/m) {
173 $default_keywords = [ split ' ', $unquote->($1) ];
175 if ($buffers =~ /^DISTDIR=(.*)$/m) {
176 $default_distdir = Cwd::abs_path($unquote->($1));
178 if ($buffers =~ /^PORTDIR=(.*)$/m) {
179 $main_portdir = Cwd::abs_path($unquote->($1));
182 __PACKAGE__->_abort($errmsg);
186 $default_keywords = [ 'x86' ] unless defined $default_keywords;
187 $default_distdir = '/usr/portage/distfiles' unless defined $default_distdir;
189 return $format_available = 1;
194 my $stat = $self->status;
195 my $conf = $self->parent->parent->configure_object;
197 $stat->mk_accessors(qw/name version author distribution desc uri src license
200 requires configure_requires recursive_requires
201 ebuild_name ebuild_version ebuild_dir ebuild_file
203 overlay distdir keywords do_manifest header footer
206 $stat->force($conf->get_conf('force'));
207 $stat->verbose($conf->get_conf('verbose'));
212 my $filter_prereqs = sub {
213 my ($int, $prereqs) = @_;
216 for my $prereq (sort keys %$prereqs) {
217 next if $prereq =~ /^perl(?:-|\z)/;
219 my $obj = $int->module_tree($prereq);
220 next unless $obj; # Not in the module tree (e.g. Config)
221 next if $obj->package_is_perl_core;
223 my $version = $prereqs->{$prereq} || undef;
225 push @requires, [ $obj->package_name, $version ];
233 my $mod = $self->parent;
234 my $stat = $self->status;
235 my $int = $mod->parent;
236 my $conf = $int->configure_object;
240 my $OK = sub { $stat->prepared(1); 1 };
241 my $FAIL = sub { $stat->prepared(0); $self->_abort(@_) if @_; 0 };
242 my $SKIP = sub { $stat->prepared(1); $stat->created(1); $self->_skip(@_) if @_; 1 };
244 my $keywords = delete $opts{keywords};
245 if (defined $keywords) {
246 $keywords = [ split ' ', $keywords ];
248 $keywords = $default_keywords;
250 $stat->keywords($keywords);
252 my $manifest = delete $opts{manifest};
253 $manifest = 1 unless defined $manifest;
254 $manifest = 0 if $manifest =~ /^\s*no?\s*$/i;
255 $stat->do_manifest($manifest);
257 my $header = delete $opts{header};
258 if (defined $header) {
259 1 while chomp $header;
262 my $year = (localtime)[5] + 1900;
263 $header = <<" DEF_HEADER";
264 # Copyright 1999-$year Gentoo Foundation
265 # Distributed under the terms of the GNU General Public License v2
269 $stat->header($header);
271 my $footer = delete $opts{footer};
272 if (defined $footer) {
273 $footer = "\n" . $footer;
277 $stat->footer($footer);
279 my $overlay = delete $opts{overlay};
280 $overlay = (defined $overlay) ? Cwd::abs_path($overlay) : '/usr/local/portage';
281 $stat->overlay($overlay);
283 my $distdir = delete $opts{distdir};
284 $distdir = (defined $distdir) ? Cwd::abs_path($distdir) : $default_distdir;
285 $stat->distdir($distdir);
287 return $FAIL->("distdir isn't writable") if $stat->do_manifest && !-w $distdir;
289 $stat->fetched_arch($mod->status->fetch);
291 my $cur = File::Spec->curdir();
294 if ($_ eq $overlay or File::Spec->abs2rel($overlay, $_) eq $cur) {
295 $portdir_overlay = [ @$overlays ];
299 $portdir_overlay = [ @$overlays, $overlay ] unless $portdir_overlay;
300 $stat->portdir_overlay($portdir_overlay);
302 my $name = $mod->package_name;
305 my $version = $mod->package_version;
306 $stat->version($version);
308 my $author = $mod->author->cpanid;
309 $stat->author($author);
311 $stat->distribution($name . '-' . $version);
313 $stat->ebuild_version(CPANPLUS::Dist::Gentoo::Maps::version_c2g($name, $version));
315 $stat->ebuild_name(CPANPLUS::Dist::Gentoo::Maps::name_c2g($name));
317 $stat->ebuild_dir(File::Spec->catdir(
323 my $file = File::Spec->catfile(
325 $stat->ebuild_name . '-' . $stat->ebuild_version . '.ebuild',
327 $stat->ebuild_file($file);
330 # Always generate an ebuild in our category when forcing
331 if ($forced{$file}) {
333 return $SKIP->('Ebuild already forced for', $stat->distribution);
339 return $SKIP->("Can't force rewriting of $file");
341 1 while unlink $file;
344 if (my $atom = $self->_cpan2portage($name, $version)) {
345 $stat->dist($atom->ebuild);
346 return $SKIP->('Ebuild already generated for', $stat->distribution);
352 $self->SUPER::prepare(@_);
354 return $FAIL->() unless $stat->prepared;
356 my $desc = $mod->description;
357 $desc = $mod->comment unless $desc;
358 $desc = "$name Perl distribution (provides " . $mod->module . ')'
360 $desc = substr($desc, 0, 77) . '...' if length $desc > 80;
363 $stat->uri('http://search.cpan.org/dist/' . $name);
365 $author =~ /^(.)(.)/ or return $FAIL->('Wrong author name');
366 $stat->src("mirror://cpan/modules/by-authors/id/$1/$1$2/$author/" . $mod->package);
368 $stat->license($self->intuit_license);
370 my $mstat = $mod->status;
371 $stat->configure_requires($int->$filter_prereqs($mstat->configure_requires));
372 $stat->requires($int->$filter_prereqs($mstat->requires));
373 $stat->recursive_requires([ ]);
375 $dependencies{$name} = [ map $_->[0], @{ $stat->requires } ];
377 my $meta = $self->meta;
378 $stat->min_perl(CPANPLUS::Dist::Gentoo::Maps::perl_version_c2g(
379 $meta->{requires}->{perl},
387 Returns the contents of the F<META.yml> or F<META.json> files as parsed by L<Parse::CPAN::Meta>.
393 my $mod = $self->parent;
394 my $stat = $self->status;
396 my $meta = $stat->meta;
397 return $meta if defined $meta;
399 my $extract_dir = $mod->status->extract;
401 for my $name (qw/META.json META.yml/) {
402 my $meta_file = File::Spec->catdir($extract_dir, $name);
403 next unless -e $meta_file;
406 my $meta = eval { Parse::CPAN::Meta::LoadFile($meta_file) };
416 =head2 C<intuit_license>
418 Returns an array reference to a list of Gentoo licences identifiers under which the current distribution is released.
422 my %dslip_license = (
433 my $mod = $self->parent;
435 my $dslip = $mod->dslip;
436 if (defined $dslip and $dslip =~ /\S{4}(\S)/) {
437 my @licenses = CPANPLUS::Dist::Gentoo::Maps::license_c2g($dslip_license{$1});
438 return \@licenses if @licenses;
441 my $meta = $self->meta;
442 my $license = $meta->{license};
443 if (defined $license) {
444 my @licenses = CPANPLUS::Dist::Gentoo::Maps::license_c2g($license);
445 return \@licenses if @licenses;
448 return [ CPANPLUS::Dist::Gentoo::Maps::license_c2g('perl') ];
453 my $stat = $self->status;
457 my $guard = CPANPLUS::Dist::Gentoo::Guard->new(sub {
458 if (defined $file and -e $file and -w _) {
459 1 while unlink $file;
463 my $SIG_INT = $SIG{INT};
464 local $SIG{INT} = sub {
467 eval { $SIG_INT->() };
476 $stat->dist($file) if defined $file;
483 $self->_abort(@_) if @_;
487 unless ($stat->prepared) {
489 'Can\'t create', $stat->distribution, 'since it was never prepared'
493 if ($stat->created) {
494 $self->_skip($stat->distribution, 'was already created');
495 $file = $stat->dist; # Keep the existing one.
499 my $dir = $stat->ebuild_dir;
501 eval { File::Path::mkpath($dir) };
502 return $FAIL->("mkpath($dir): $@") if $@;
505 $file = $stat->ebuild_file;
507 # Create a placeholder ebuild to prevent recursion with circular dependencies.
509 open my $eb, '>', $file or return $FAIL->("open($file): $!");
510 print $eb "PLACEHOLDER\n";
516 $self->SUPER::create(@_);
518 return $FAIL->() unless $stat->created;
521 open my $eb, '>', $file or return $FAIL->("open($file): $!");
522 my $source = $self->ebuild_source;
523 return $FAIL->() unless defined $source;
527 return $FAIL->() if $stat->do_manifest and not $self->update_manifest;
532 =head2 C<update_manifest>
534 Updates the F<Manifest> file for the ebuild associated to the current dist object.
538 sub update_manifest {
540 my $stat = $self->status;
542 my $file = $stat->ebuild_file;
543 unless (defined $file and -e $file) {
544 return $self->_abort('The ebuild file is invalid or does not exist');
547 unless (File::Copy::copy($stat->fetched_arch => $stat->distdir)) {
548 return $self->_abort("Couldn\'t copy the distribution file to distdir ($!)");
551 $self->_notify('Adding Manifest entry for', $stat->distribution);
553 return $self->_run([ 'ebuild', $file, 'manifest' ], 0);
556 =head2 C<ebuild_source>
558 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.
562 my $dep_tree_contains;
566 $dep_tree_contains = sub {
567 my ($dist, $target) = @_;
569 return 0 if $seen{$dist};
570 local $seen{$dist} = 1;
572 for my $kid (@{ $dependencies{$dist} }) {
573 return 1 if $kid eq $target
574 or $dep_tree_contains->($kid, $target);
583 my $stat = $self->status;
586 my $name = $stat->name;
587 my %recursive_kids = map { $_ => 1 }
588 grep $dep_tree_contains->($_, $name),
589 @{ $dependencies{$name} };
590 if (%recursive_kids) {
591 my (@requires, @recursive_requires);
592 for (@{ $stat->requires }) {
593 if ($recursive_kids{$_->[0]}) {
594 push @recursive_requires, $_;
599 $stat->requires(\@requires);
600 $stat->recursive_requires(\@recursive_requires);
604 # We must resolve the deps now and not inside prepare because _cpan2portage
605 # has to see the ebuilds already generated for the dependencies of the current
608 my (@configure_requires, @requires, @recursive_requires);
611 [ configure_requires => \@configure_requires ],
612 [ requires => \@requires ],
613 [ recursive_requires => \@recursive_requires ],
616 push @requires, CPANPLUS::Dist::Gentoo::Atom->new(
617 category => 'dev-lang',
619 version => $stat->min_perl,
623 my ($phase, $list) = @$_;
625 for (@{ $stat->$phase }) {
626 my $atom = $self->_cpan2portage(@$_);
627 unless (defined $atom) {
629 "Couldn't find an appropriate ebuild for $_->[0] in the portage tree"
637 @$list = CPANPLUS::Dist::Gentoo::Atom->fold(@$list);
640 my $d = $stat->header;
641 $d .= "# Generated by CPANPLUS::Dist::Gentoo version $VERSION\n\n";
642 $d .= 'MODULE_AUTHOR="' . $stat->author . "\"\ninherit perl-module\n\n";
643 $d .= 'S="${WORKDIR}/' . $stat->distribution . "\"\n";
644 $d .= 'DESCRIPTION="' . $stat->desc . "\"\n";
645 $d .= 'HOMEPAGE="' . $stat->uri . "\"\n";
646 $d .= 'SRC_URI="' . $stat->src . "\"\n";
647 $d .= "SLOT=\"0\"\n";
648 $d .= 'LICENSE="|| ( ' . join(' ', sort @{$stat->license}) . " )\"\n";
649 $d .= 'KEYWORDS="' . join(' ', sort @{$stat->keywords}) . "\"\n";
650 $d .= 'RDEPEND="' . join("\n", sort @requires) . "\"\n" if @requires;
651 $d .= 'PDEPEND="' . join("\n", sort @recursive_requires) . "\"\n"
652 if @recursive_requires;
653 $d .= 'DEPEND="' . join("\n", '${RDEPEND}', sort @configure_requires) . "\"\n";
654 $d .= "SRC_TEST=\"do\"\n";
661 my ($self, $dist_name, $dist_version) = @_;
663 my $name = CPANPLUS::Dist::Gentoo::Maps::name_c2g($dist_name);
664 my $version = CPANPLUS::Dist::Gentoo::Maps::version_c2g($dist_name, $dist_version);
666 my @portdirs = ($main_portdir, @{$self->status->portdir_overlay});
668 for my $category (qw/virtual perl-core dev-perl perl-gcpan/, CATEGORY) {
669 my $name = ($category eq 'virtual' ? 'perl-' : '') . $name;
671 for my $portdir (@portdirs) {
672 my @ebuilds = glob File::Spec->catfile(
679 my $last = reduce { $a < $b ? $b : $a } # handles overloading
680 map CPANPLUS::Dist::Gentoo::Atom->new_from_ebuild($_),
682 next if defined $version and $last < $version;
684 return CPANPLUS::Dist::Gentoo::Atom->new(
685 category => $last->category,
688 ebuild => $last->ebuild,
699 my $stat = $self->status;
700 my $conf = $self->parent->parent->configure_object;
702 my $sudo = $conf->get_program('sudo');
703 my @cmd = ('emerge', '=' . $stat->ebuild_name . '-' . $stat->ebuild_version);
704 unshift @cmd, $sudo if $sudo;
706 my $success = $self->_run(\@cmd, 1);
707 $stat->installed($success);
714 my $stat = $self->status;
715 my $conf = $self->parent->parent->configure_object;
717 my $sudo = $conf->get_program('sudo');
718 my @cmd = ('emerge', '-C', '=' . $stat->ebuild_name . '-' . $stat->ebuild_version);
719 unshift @cmd, $sudo if $sudo;
721 my $success = $self->_run(\@cmd, 1);
722 $stat->uninstalled($success);
728 my ($self, $cmd, $verbose) = @_;
729 my $stat = $self->status;
731 my ($success, $errmsg, $output) = do {
732 local $ENV{PORTDIR_OVERLAY} = join ' ', @{$stat->portdir_overlay};
733 local $ENV{PORTAGE_RO_DISTDIRS} = $stat->distdir;
741 $self->_abort($errmsg);
742 if (not $verbose and defined $output and $stat->verbose) {
743 my $msg = join '', @$output;
745 CPANPLUS::Error::error($msg);
755 CPANPLUS::Error::error("@_ -- aborting");
763 CPANPLUS::Error::msg("@_");
768 sub _skip { shift->_notify(@_, '-- skipping') }
772 Gentoo (L<http://gentoo.org>).
774 L<CPANPLUS>, L<IPC::Cmd> (core modules since 5.9.5), L<Parse::CPAN::Meta> (since 5.10.1).
776 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).
782 L<CPANPLUS::Dist::Base>, L<CPANPLUS::Dist::Deb>, L<CPANPLUS::Dist::Mdv>.
786 Vincent Pit, C<< <perl at profvince.com> >>, L<http://www.profvince.com>.
788 You can contact me by mail or on C<irc.perl.org> (vincent).
792 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>.
793 I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
797 You can find documentation for this module with the perldoc command.
799 perldoc CPANPLUS::Dist::Gentoo
801 =head1 ACKNOWLEDGEMENTS
803 The module was inspired by L<CPANPLUS::Dist::Deb> and L<CPANPLUS::Dist::Mdv>.
805 Kent Fredric, for testing and suggesting improvements.
807 =head1 COPYRIGHT & LICENSE
809 Copyright 2008,2009,2010 Vincent Pit, all rights reserved.
811 This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
815 1; # End of CPANPLUS::Dist::Gentoo