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 After installing this module, you should append C<perl-gcpanp> to your F</etc/portage/categories> file.
59 This module inherits all the methods from L<CPANPLUS::Dist::Base>.
60 Please refer to its documentation for precise information on what's done at each step.
64 use constant CATEGORY => 'perl-gcpanp';
82 sub format_available {
83 return $format_available if defined $format_available;
85 for my $prog (qw/emerge ebuild/) {
86 unless (can_run($prog)) {
87 __PACKAGE__->_abort("$prog is required to write ebuilds");
88 return $format_available = 0;
92 if (IPC::Cmd->can_capture_buffer) {
94 my ($success, $errmsg) = run command => [ qw/emerge --info/ ],
98 if ($buffers =~ /^PORTDIR_OVERLAY=(.*)$/m) {
99 $overlays = [ map abs_path($_), split ' ', $unquote->($1) ];
101 if ($buffers =~ /^ACCEPT_KEYWORDS=(.*)$/m) {
102 $default_keywords = [ split ' ', $unquote->($1) ];
104 if ($buffers =~ /^DISTDIR=(.*)$/m) {
105 $default_distdir = abs_path($unquote->($1));
107 if ($buffers =~ /^PORTDIR=(.*)$/m) {
108 $main_portdir = abs_path($unquote->($1));
111 __PACKAGE__->_abort($errmsg);
115 $default_keywords = [ 'x86' ] unless defined $default_keywords;
116 $default_distdir = '/usr/portage/distfiles' unless defined $default_distdir;
118 return $format_available = 1;
123 my $stat = $self->status;
124 my $conf = $self->parent->parent->configure_object;
126 $stat->mk_accessors(qw/name version author distribution desc uri src license
128 ebuild_name ebuild_version ebuild_dir ebuild_file
130 overlay distdir keywords do_manifest header footer
133 $stat->force($conf->get_conf('force'));
134 $stat->verbose($conf->get_conf('verbose'));
141 my $mod = $self->parent;
142 my $stat = $self->status;
143 my $int = $mod->parent;
144 my $conf = $int->configure_object;
148 my $OK = sub { $stat->prepared(1); 1 };
149 my $FAIL = sub { $stat->prepared(0); $self->_abort(@_) if @_; 0 };
150 my $SKIP = sub { $stat->prepared(1); $stat->created(1); $self->_skip(@_) if @_; 1 };
152 my $keywords = delete $opts{keywords};
153 if (defined $keywords) {
154 $keywords = [ split ' ', $keywords ];
156 $keywords = $default_keywords;
158 $stat->keywords($keywords);
160 my $manifest = delete $opts{manifest};
161 $manifest = 1 unless defined $manifest;
162 $manifest = 0 if $manifest =~ /^\s*no?\s*$/i;
163 $stat->do_manifest($manifest);
165 my $header = delete $opts{header};
166 if (defined $header) {
167 1 while chomp $header;
172 $stat->header($header);
174 my $footer = delete $opts{footer};
175 if (defined $footer) {
176 $footer = "\n" . $footer;
180 $stat->footer($footer);
182 my $overlay = delete $opts{overlay};
183 $overlay = (defined $overlay) ? abs_path $overlay : '/usr/local/portage';
184 $stat->overlay($overlay);
186 my $distdir = delete $opts{distdir};
187 $distdir = (defined $distdir) ? abs_path $distdir : $default_distdir;
188 $stat->distdir($distdir);
190 return $FAIL->("distdir isn't writable") if $stat->do_manifest && !-w $distdir;
192 $stat->fetched_arch($mod->status->fetch);
194 my $cur = File::Spec->curdir();
197 if ($_ eq $overlay or File::Spec->abs2rel($overlay, $_) eq $cur) {
198 $portdir_overlay = [ @$overlays ];
202 $portdir_overlay = [ @$overlays, $overlay ] unless $portdir_overlay;
203 $stat->portdir_overlay($portdir_overlay);
205 my $name = $mod->package_name;
208 my $version = $mod->package_version;
209 $stat->version($version);
211 my $author = $mod->author->cpanid;
212 $stat->author($author);
214 $stat->distribution($name . '-' . $version);
216 $stat->ebuild_version(CPANPLUS::Dist::Gentoo::Maps::version_c2g($version));
218 $stat->ebuild_name(CPANPLUS::Dist::Gentoo::Maps::name_c2g($name));
220 $stat->ebuild_dir(File::Spec->catdir(
226 my $file = File::Spec->catfile(
228 $stat->ebuild_name . '-' . $stat->ebuild_version . '.ebuild',
230 $stat->ebuild_file($file);
233 # Always generate an ebuild in our category when forcing
234 if ($forced{$file}) {
236 return $SKIP->('Ebuild already forced for', $stat->distribution);
242 return $SKIP->("Can't force rewriting of $file");
244 1 while unlink $file;
247 if (my @match = $self->_cpan2portage($name, $version)) {
248 $stat->dist($match[1]);
249 return $SKIP->('Ebuild already generated for', $stat->distribution);
255 $self->SUPER::prepare(%opts);
257 return $FAIL->() unless $stat->prepared;
259 my $desc = $mod->description;
260 ($desc = $name) =~ s/-+/::/g unless $desc;
263 $stat->uri('http://search.cpan.org/dist/' . $name);
265 $author =~ /^(.)(.)/ or return $FAIL->('Wrong author name');
266 $stat->src("mirror://cpan/modules/by-authors/id/$1/$1$2/$author/" . $mod->package);
268 $stat->license($self->intuit_license);
270 my $prereqs = $mod->status->prereqs;
272 for my $prereq (sort keys %$prereqs) {
273 next if $prereq =~ /^perl(?:-|\z)/;
274 my $obj = $int->module_tree($prereq);
275 next unless $obj; # Not in the module tree (e.g. Config)
276 next if $obj->package_is_perl_core;
279 if ($prereqs->{$prereq}) {
280 if ($obj->installed_version && $obj->installed_version < $obj->version) {
281 $version = $obj->installed_version;
283 $version = $obj->package_version;
286 push @depends, [ $obj->package_name, $version ];
289 $stat->deps(\@depends);
294 =head2 C<intuit_license>
296 Returns an array reference to a list of Gentoo licences identifiers under which the current distribution is released.
300 my %dslip_license = (
311 my $mod = $self->parent;
313 my $dslip = $mod->dslip;
314 if (defined $dslip and $dslip =~ /\S{4}(\S)/) {
315 my @licenses = CPANPLUS::Dist::Gentoo::Maps::license_c2g($dslip_license{$1});
316 return \@licenses if @licenses;
319 my $extract_dir = $mod->status->extract;
321 for my $meta_file (qw/META.json META.yml/) {
323 Parse::CPAN::Meta::LoadFile(File::Spec->catdir(
328 my $license = $meta->{license};
329 if (defined $license) {
330 my @licenses = CPANPLUS::Dist::Gentoo::Maps::license_c2g($license);
331 return \@licenses if @licenses;
335 return [ CPANPLUS::Dist::Gentoo::Maps::license_c2g('perl') ];
340 my $stat = $self->status;
346 $stat->dist($file) if defined $file;
353 $self->_abort(@_) if @_;
354 if (defined $file and -f $file) {
355 1 while unlink $file;
360 unless ($stat->prepared) {
362 'Can\'t create', $stat->distribution, 'since it was never prepared'
366 if ($stat->created) {
367 $self->_skip($stat->distribution, 'was already created');
368 $file = $stat->dist; # Keep the existing one.
372 my $dir = $stat->ebuild_dir;
374 eval { File::Path::mkpath($dir) };
375 return $FAIL->("mkpath($dir): $@") if $@;
378 $file = $stat->ebuild_file;
380 # Create a placeholder ebuild to prevent recursion with circular dependencies.
382 open my $eb, '>', $file or return $FAIL->("open($file): $!");
383 print $eb "PLACEHOLDER\n";
389 $self->SUPER::create(@_);
391 return $FAIL->() unless $stat->created;
394 open my $eb, '>', $file or return $FAIL->("open($file): $!");
395 my $source = $self->ebuild_source;
396 return $FAIL->() unless defined $source;
400 return $FAIL->() if $stat->do_manifest and not $self->update_manifest;
405 =head2 C<update_manifest>
407 Updates the F<Manifest> file for the ebuild associated to the current dist object.
411 sub update_manifest {
413 my $stat = $self->status;
415 my $file = $stat->ebuild_file;
416 unless (defined $file and -e $file) {
417 return $self->_abort('The ebuild file is invalid or does not exist');
420 unless (File::Copy::copy($stat->fetched_arch => $stat->distdir)) {
421 return $self->_abort("Couldn\'t copy the distribution file to distdir ($!)");
424 $self->_notify('Adding Manifest entry for', $stat->distribution);
426 return $self->_run([ 'ebuild', $file, 'manifest' ], 0);
429 =head2 C<ebuild_source>
431 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.
437 my $stat = $self->status;
439 # We must resolve the deps now and not inside prepare because _cpan2portage
440 # has to see the ebuilds already generated for the dependencies of the current
443 for (@{$stat->deps}) {
444 my $dep = $self->_cpan2portage(@$_);
445 unless (defined $dep) {
447 "Couldn't find an appropriate ebuild for $_->[0] in the portage tree"
454 @deps = do { my %seen; sort grep !$seen{$_}++, 'dev-lang/perl', @deps };
456 my $d = $stat->header;
457 $d .= "# Generated by CPANPLUS::Dist::Gentoo version $VERSION\n\n";
458 $d .= 'MODULE_AUTHOR="' . $stat->author . "\"\ninherit perl-module\n\n";
459 $d .= 'S="${WORKDIR}/' . $stat->distribution . "\"\n";
460 $d .= 'DESCRIPTION="' . $stat->desc . "\"\n";
461 $d .= 'HOMEPAGE="' . $stat->uri . "\"\n";
462 $d .= 'SRC_URI="' . $stat->src . "\"\n";
463 $d .= "SLOT=\"0\"\n";
464 $d .= 'LICENSE="|| ( ' . join(' ', sort @{$stat->license}) . " )\"\n";
465 $d .= 'KEYWORDS="' . join(' ', sort @{$stat->keywords}) . "\"\n";
466 $d .= 'RDEPEND="' . join("\n", @deps) . "\"\n";
467 $d .= "DEPEND=\"\${RDEPEND}\"\n";
468 $d .= "SRC_TEST=\"do\"\n";
475 my ($self, $name, $version) = @_;
477 $name = CPANPLUS::Dist::Gentoo::Maps::name_c2g($name);
479 $ver = CPANPLUS::Dist::Gentoo::Maps::version_c2g($version) if defined $version;
481 my @portdirs = ($main_portdir, @{$self->status->portdir_overlay});
483 for my $category (qw/virtual perl-core dev-perl perl-gcpan/, CATEGORY) {
484 my $atom = ($category eq 'virtual' ? 'perl-' : '') . $name;
486 for my $portdir (@portdirs) {
487 my @ebuilds = glob File::Spec->catfile(
495 CPANPLUS::Dist::Gentoo::Maps::version_gcmp($b->[1], $a->[1]) >= 0 ? $b : $a
496 } map [ $_, /\Q$atom\E-v?([\d._pr-]+).*?\.ebuild$/ ? $1 : 0 ], @ebuilds;
499 if (defined $ver) { # implies that $version is defined
501 CPANPLUS::Dist::Gentoo::Maps::version_gcmp($last->[1], $ver) >= 0;
502 $dep = ">=$category/$atom-$ver";
504 $dep = "$category/$atom";
507 return wantarray ? ($dep, $last->[0]) : $dep;
517 my $stat = $self->status;
518 my $conf = $self->parent->parent->configure_object;
520 my $sudo = $conf->get_program('sudo');
521 my @cmd = ('emerge', '=' . $stat->ebuild_name . '-' . $stat->ebuild_version);
522 unshift @cmd, $sudo if $sudo;
524 my $success = $self->_run(\@cmd, 1);
525 $stat->installed($success);
532 my $stat = $self->status;
533 my $conf = $self->parent->parent->configure_object;
535 my $sudo = $conf->get_program('sudo');
536 my @cmd = ('emerge', '-C', '=' . $stat->ebuild_name . '-' . $stat->ebuild_version);
537 unshift @cmd, $sudo if $sudo;
539 my $success = $self->_run(\@cmd, 1);
540 $stat->uninstalled($success);
546 my ($self, $cmd, $verbose) = @_;
547 my $stat = $self->status;
549 my ($success, $errmsg, $output) = do {
550 local $ENV{PORTDIR_OVERLAY} = join ' ', @{$stat->portdir_overlay};
551 local $ENV{PORTAGE_RO_DISTDIRS} = $stat->distdir;
552 run command => $cmd, verbose => $verbose;
556 $self->_abort($errmsg);
557 if (not $verbose and defined $output and $stat->verbose) {
558 my $msg = join '', @$output;
560 CPANPLUS::Error::error($msg);
570 CPANPLUS::Error::error("@_ -- aborting");
578 CPANPLUS::Error::msg("@_");
583 sub _skip { shift->_notify(@_, '-- skipping') }
587 Gentoo (L<http://gentoo.org>).
589 L<CPANPLUS>, L<IPC::Cmd> (core modules since 5.9.5), L<Parse::CPAN::Meta> (since 5.10.1).
591 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).
597 L<CPANPLUS::Dist::Base>, L<CPANPLUS::Dist::Deb>, L<CPANPLUS::Dist::Mdv>.
601 Vincent Pit, C<< <perl at profvince.com> >>, L<http://www.profvince.com>.
603 You can contact me by mail or on C<irc.perl.org> (vincent).
607 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>.
608 I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
612 You can find documentation for this module with the perldoc command.
614 perldoc CPANPLUS::Dist::Gentoo
616 =head1 ACKNOWLEDGEMENTS
618 The module was inspired by L<CPANPLUS::Dist::Deb> and L<CPANPLUS::Dist::Mdv>.
620 Kent Fredric, for testing and suggesting improvements.
622 =head1 COPYRIGHT & LICENSE
624 Copyright 2008-2009 Vincent Pit, all rights reserved.
626 This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
630 1; # End of CPANPLUS::Dist::Gentoo