1 package CPANPLUS::Dist::Gentoo;
11 use IPC::Cmd qw/run can_run/;
12 use Parse::CPAN::Meta ();
14 use CPANPLUS::Error ();
16 use base qw/CPANPLUS::Dist::Base/;
18 use CPANPLUS::Dist::Gentoo::Maps;
22 CPANPLUS::Dist::Gentoo - CPANPLUS backend generating Gentoo ebuilds.
30 our $VERSION = '0.07';
34 cpan2dist --format=CPANPLUS::Dist::Gentoo \
35 --dist-opts overlay=/usr/local/portage \
36 --dist-opts distdir=/usr/portage/distfiles \
37 --dist-opts manifest=yes \
38 --dist-opts keywords=x86 \
39 --dist-opts header="# Copyright 1999-2008 Gentoo Foundation" \
40 --dist-opts footer="# End" \
45 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.
46 You need write permissions on the directory where Gentoo fetches its source files (usually F</usr/portage/distfiles>).
47 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.
49 The generated ebuilds are placed into the C<perl-gcpanp> category.
50 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>.
54 After installing this module, you should append C<perl-gcpanp> to your F</etc/portage/categories> file.
58 This module inherits all the methods from L<CPANPLUS::Dist::Base>.
59 Please refer to its documentation for precise information on what's done at each step.
63 use constant CATEGORY => 'perl-gcpanp';
81 sub format_available {
82 return $format_available if defined $format_available;
84 for my $prog (qw/emerge ebuild/) {
85 unless (can_run($prog)) {
86 __PACKAGE__->_abort("$prog is required to write ebuilds");
87 return $format_available = 0;
91 if (IPC::Cmd->can_capture_buffer) {
93 my ($success, $errmsg) = run command => [ qw/emerge --info/ ],
97 if ($buffers =~ /^PORTDIR_OVERLAY=(.*)$/m) {
98 $overlays = [ map abs_path($_), split ' ', $unquote->($1) ];
100 if ($buffers =~ /^ACCEPT_KEYWORDS=(.*)$/m) {
101 $default_keywords = [ split ' ', $unquote->($1) ];
103 if ($buffers =~ /^DISTDIR=(.*)$/m) {
104 $default_distdir = abs_path($unquote->($1));
106 if ($buffers =~ /^PORTDIR=(.*)$/m) {
107 $main_portdir = abs_path($unquote->($1));
110 __PACKAGE__->_abort($errmsg);
114 $default_keywords = [ 'x86' ] unless defined $default_keywords;
115 $default_distdir = '/usr/portage/distfiles' unless defined $default_distdir;
117 return $format_available = 1;
122 my $stat = $self->status;
123 my $conf = $self->parent->parent->configure_object;
125 $stat->mk_accessors(qw/name version author distribution desc uri src license
127 ebuild_name ebuild_version ebuild_dir ebuild_file
129 overlay distdir keywords do_manifest header footer
132 $stat->force($conf->get_conf('force'));
133 $stat->verbose($conf->get_conf('verbose'));
140 my $mod = $self->parent;
141 my $stat = $self->status;
142 my $int = $mod->parent;
143 my $conf = $int->configure_object;
147 my $OK = sub { $stat->prepared(1); 1 };
148 my $FAIL = sub { $stat->prepared(0); $self->_abort(@_) if @_; 0 };
150 my $keywords = delete $opts{keywords};
151 if (defined $keywords) {
152 $keywords = [ split ' ', $keywords ];
154 $keywords = $default_keywords;
156 $stat->keywords($keywords);
158 my $manifest = delete $opts{manifest};
159 $manifest = 1 unless defined $manifest;
160 $manifest = 0 if $manifest =~ /^\s*no?\s*$/i;
161 $stat->do_manifest($manifest);
163 my $header = delete $opts{header};
164 if (defined $header) {
165 1 while chomp $header;
170 $stat->header($header);
172 my $footer = delete $opts{footer};
173 if (defined $footer) {
174 $footer = "\n" . $footer;
178 $stat->footer($footer);
180 my $overlay = delete $opts{overlay};
181 $overlay = (defined $overlay) ? abs_path $overlay : '/usr/local/portage';
182 $stat->overlay($overlay);
184 my $distdir = delete $opts{distdir};
185 $distdir = (defined $distdir) ? abs_path $distdir : $default_distdir;
186 $stat->distdir($distdir);
188 return $FAIL->("distdir isn't writable") if $stat->do_manifest && !-w $distdir;
190 $stat->fetched_arch($mod->status->fetch);
192 my $cur = File::Spec->curdir();
195 if ($_ eq $overlay or File::Spec->abs2rel($overlay, $_) eq $cur) {
196 $portdir_overlay = [ @$overlays ];
200 $portdir_overlay = [ @$overlays, $overlay ] unless $portdir_overlay;
201 $stat->portdir_overlay($portdir_overlay);
203 my $name = $mod->package_name;
206 my $version = $mod->package_version;
207 $stat->version($version);
209 my $author = $mod->author->cpanid;
210 $stat->author($author);
212 $stat->distribution($name . '-' . $version);
214 $stat->ebuild_version(CPANPLUS::Dist::Gentoo::Maps::version_c2g($version));
216 $stat->ebuild_name(CPANPLUS::Dist::Gentoo::Maps::name_c2g($name));
218 $stat->ebuild_dir(File::Spec->catdir(
224 my $file = File::Spec->catfile(
226 $stat->ebuild_name . '-' . $stat->ebuild_version . '.ebuild',
228 $stat->ebuild_file($file);
232 if ($stat->force and not $forced{$file}) {
234 1 while unlink $file;
238 $self->_skip("Can't force rewriting of $file");
241 $self->_skip('Ebuild already generated for', $stat->distribution);
253 $self->SUPER::prepare(%opts);
255 return $FAIL->() unless $stat->prepared;
257 my $desc = $mod->description;
258 ($desc = $name) =~ s/-+/::/g unless $desc;
261 $stat->uri('http://search.cpan.org/dist/' . $name);
263 $author =~ /^(.)(.)/ or return $FAIL->('Wrong author name');
264 $stat->src("mirror://cpan/modules/by-authors/id/$1/$1$2/$author/" . $mod->package);
266 $stat->license($self->intuit_license);
268 my $prereqs = $mod->status->prereqs;
270 for my $prereq (sort keys %$prereqs) {
271 next if $prereq =~ /^perl(?:-|\z)/;
272 my $obj = $int->module_tree($prereq);
273 next unless $obj; # Not in the module tree (e.g. Config)
274 next if $obj->package_is_perl_core;
277 if ($prereqs->{$prereq}) {
278 if ($obj->installed_version && $obj->installed_version < $obj->version) {
279 $version = $obj->installed_version;
281 $version = $obj->package_version;
284 push @depends, [ $obj->package_name, $version ];
287 $stat->deps(\@depends);
292 =head2 C<intuit_license>
294 Returns an array reference to a list of Gentoo licences identifiers under which the current distribution is released.
298 my %dslip_license = (
309 my $mod = $self->parent;
311 my $dslip = $mod->dslip;
312 if (defined $dslip and $dslip =~ /\S{4}(\S)/) {
313 my @licenses = CPANPLUS::Dist::Gentoo::Maps::license_c2g($dslip_license{$1});
314 return \@licenses if @licenses;
317 my $extract_dir = $mod->status->extract;
319 for my $meta_file (qw/META.json META.yml/) {
321 Parse::CPAN::Meta::LoadFile(File::Spec->catdir(
326 my $license = $meta->{license};
327 if (defined $license) {
328 my @licenses = CPANPLUS::Dist::Gentoo::Maps::license_c2g($license);
329 return \@licenses if @licenses;
333 return [ CPANPLUS::Dist::Gentoo::Maps::license_c2g('perl') ];
338 my $stat = $self->status;
351 $self->_abort(@_) if @_;
352 if ($file and -f $file) {
353 1 while unlink $file;
358 unless ($stat->prepared) {
360 'Can\'t create', $stat->distribution, 'since it was never prepared'
364 if ($stat->created) {
365 $self->_skip($stat->distribution, 'was already created');
369 my $dir = $stat->ebuild_dir;
371 eval { File::Path::mkpath($dir) };
372 return $FAIL->("mkpath($dir): $@") if $@;
375 $file = $stat->ebuild_file;
377 # Create a placeholder ebuild to prevent recursion with circular dependencies.
379 open my $eb, '>', $file or return $FAIL->("open($file): $!");
380 print $eb "PLACEHOLDER\n";
386 $self->SUPER::create(@_);
388 return $FAIL->() unless $stat->created;
391 open my $eb, '>', $file or return $FAIL->("open($file): $!");
392 my $source = $self->ebuild_source;
393 return $FAIL->() unless defined $source;
397 return $FAIL->() if $stat->do_manifest and not $self->update_manifest;
402 =head2 C<update_manifest>
404 Updates the F<Manifest> file for the ebuild associated to the current dist object.
408 sub update_manifest {
410 my $stat = $self->status;
412 my $file = $stat->ebuild_file;
413 unless ($file and -e $file) {
414 return $self->_abort('The ebuild file is invalid or does not exist');
417 unless (File::Copy::copy($stat->fetched_arch => $stat->distdir)) {
418 return $self->_abort("Couldn\'t copy the distribution file to distdir ($!)");
421 $self->_notify('Adding Manifest entry for', $stat->distribution);
423 return $self->_run([ 'ebuild', $stat->ebuild_file, 'manifest' ], 0);
426 =head2 C<ebuild_source>
428 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.
434 my $stat = $self->status;
436 # We must resolve the deps now and not inside prepare because _cpan2portage
437 # has to see the ebuilds already generated for the dependencies of the current
440 for (@{$stat->deps}) {
441 my $dep = $self->_cpan2portage(@$_);
442 return unless defined $dep;
446 @deps = do { my %seen; sort grep !$seen{$_}++, 'dev-lang/perl', @deps };
448 my $d = $stat->header;
449 $d .= "# Generated by CPANPLUS::Dist::Gentoo version $VERSION\n\n";
450 $d .= 'MODULE_AUTHOR="' . $stat->author . "\"\ninherit perl-module\n\n";
451 $d .= 'S="${WORKDIR}/' . $stat->distribution . "\"\n";
452 $d .= 'DESCRIPTION="' . $stat->desc . "\"\n";
453 $d .= 'HOMEPAGE="' . $stat->uri . "\"\n";
454 $d .= 'SRC_URI="' . $stat->src . "\"\n";
455 $d .= "SLOT=\"0\"\n";
456 $d .= 'LICENSE="|| ( ' . join(' ', sort @{$stat->license}) . " )\"\n";
457 $d .= 'KEYWORDS="' . join(' ', sort @{$stat->keywords}) . "\"\n";
458 $d .= 'DEPEND="' . join("\n", @deps) . "\"\n";
459 $d .= "SRC_TEST=\"do\"\n";
466 my ($self, $name, $version) = @_;
468 $name = CPANPLUS::Dist::Gentoo::Maps::name_c2g($name);
470 $ver = CPANPLUS::Dist::Gentoo::Maps::version_c2g($version) if defined $version;
472 my @portdirs = ($main_portdir, @{$self->status->portdir_overlay});
474 for my $category (qw/virtual perl-core dev-perl perl-gcpan/, CATEGORY) {
475 my $atom = ($category eq 'virtual' ? 'perl-' : '') . $name;
477 for my $portdir (@portdirs) {
478 my @ebuilds = glob File::Spec->catfile(
485 if (defined $ver) { # implies that $version is defined
487 my ($eb_ver) = /\Q$atom\E-v?([\d._pr-]+).*?\.ebuild$/;
488 return ">=$category/$atom-$ver"
490 and CPANPLUS::Dist::Gentoo::Maps::version_gcmp($eb_ver, $ver) >= 0;
493 return "$category/$atom";
501 "Couldn't find an appropriate ebuild for $name in the portage tree"
509 my $stat = $self->status;
510 my $conf = $self->parent->parent->configure_object;
512 my $sudo = $conf->get_program('sudo');
513 my @cmd = ('emerge', '=' . $stat->ebuild_name . '-' . $stat->ebuild_version);
514 unshift @cmd, $sudo if $sudo;
516 my $success = $self->_run(\@cmd, 1);
517 $stat->installed($success);
524 my $stat = $self->status;
525 my $conf = $self->parent->parent->configure_object;
527 my $sudo = $conf->get_program('sudo');
528 my @cmd = ('emerge', '-C', '=' . $stat->ebuild_name . '-' . $stat->ebuild_version);
529 unshift @cmd, $sudo if $sudo;
531 my $success = $self->_run(\@cmd, 1);
532 $stat->uninstalled($success);
538 my ($self, $cmd, $verbose) = @_;
539 my $stat = $self->status;
541 my ($success, $errmsg, $output) = do {
542 local $ENV{PORTDIR_OVERLAY} = join ' ', @{$stat->portdir_overlay};
543 local $ENV{PORTAGE_RO_DISTDIRS} = $stat->distdir;
544 run command => $cmd, verbose => $verbose;
548 $self->_abort($errmsg);
549 if (not $verbose and defined $output and $stat->verbose) {
550 my $msg = join '', @$output;
552 CPANPLUS::Error::error($msg);
562 CPANPLUS::Error::error("@_ -- aborting");
570 CPANPLUS::Error::msg("@_");
575 sub _skip { shift->_notify(@_, '-- skipping') }
579 Gentoo (L<http://gentoo.org>).
581 L<CPANPLUS>, L<IPC::Cmd> (core modules since 5.9.5), L<Parse::CPAN::Meta> (since 5.10.1).
583 L<Cwd>, L<Carp> (since perl 5), L<File::Path> (5.001), L<File::Copy> (5.002), L<File::Spec> (5.00405).
589 L<CPANPLUS::Dist::Base>, L<CPANPLUS::Dist::Deb>, L<CPANPLUS::Dist::Mdv>.
593 Vincent Pit, C<< <perl at profvince.com> >>, L<http://www.profvince.com>.
595 You can contact me by mail or on C<irc.perl.org> (vincent).
599 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>.
600 I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
604 You can find documentation for this module with the perldoc command.
606 perldoc CPANPLUS::Dist::Gentoo
608 =head1 ACKNOWLEDGEMENTS
610 The module was inspired by L<CPANPLUS::Dist::Deb> and L<CPANPLUS::Dist::Mdv>.
612 Kent Fredric, for testing and suggesting improvements.
614 =head1 COPYRIGHT & LICENSE
616 Copyright 2008-2009 Vincent Pit, all rights reserved.
618 This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
622 1; # End of CPANPLUS::Dist::Gentoo