8 use File::Copy qw<copy>;
9 use List::Util qw<max reduce>;
13 use CPAN::DistnameInfo 0.11;
15 use Capture::Tiny qw<capture>;
17 use Parse::CPAN::Packages::Fast;
20 use CPANPLUS::Dist::Gentoo::Atom;
21 use CPANPLUS::Dist::Gentoo::Maps;
23 use constant PACKAGES => File::Spec->catdir(
24 $ENV{HOME}, '.cpanplus', '02packages.details.txt.gz'
26 use constant CPAN_MIRROR => 'http://www.cpan.org/';
27 use constant PORTAGE => '/usr/portage';
28 use constant TARGET => 'lib/CPANPLUS/Dist/Gentoo/Maps.pm';
29 use constant BACKUP => TARGET . '.bak';
30 use constant DATA_FILE => 'gentooisms.data.sto';
31 use constant STATE_FILE => 'gentooisms.state.sto';
34 'Audio-CD-disc-cover' => 0,
35 'Video-Frequencies' => 0,
37 'WattsUp-Daemon' => 1,
41 my ($indent, $fmt, @args) = @_;
42 $fmt = (' ' x ($indent * 3)) . $fmt;
46 my $timestamp = CPANPLUS::Dist::Gentoo::Maps::get_portage_timestamp(PORTAGE);
47 die 'Could not read the timestamp from the portage tree ' . PORTAGE . "\n"
48 unless defined $timestamp;
56 unless (defined $ua) {
57 $ua = LWP::UserAgent->new;
58 $ua->agent('CPANPLUS::Dist::Gentoo gentooisms generator/1.0');
61 my $r = $ua->head(CPAN_MIRROR . $path);
63 return $r && $r->code == 200;
68 my (@not_on_cpan, @unfindable, @missing, %name_mismatch, %version);
70 sub parse_portage_tree {
71 my $pcp = Parse::CPAN::Packages::Fast->new(PACKAGES);
73 for my $category (qw<perl-core dev-perl>) {
74 p(0, "Browsing the $category category.\n");
76 my $cat_dir = File::Spec->catdir(PORTAGE, $category);
78 for my $pkg_dir (glob File::Spec->catdir($cat_dir, '*')) {
79 next unless -d $pkg_dir;
81 my $pkg_name = (File::Spec->splitdir($pkg_dir))[-1];
83 my $last = reduce { $a->[1] > $b->[1] ? $a : $b }
85 map [ $_, CPANPLUS::Dist::Gentoo::Atom->new_from_ebuild($_) ],
86 glob File::Spec->catfile($pkg_dir, "$pkg_name-*");
87 my ($ebuild, $atom) = @$last;
88 p(1, "%s/%s-%s\n", map $atom->$_, qw<category name version>);
90 if (exists $is_on_cpan{$pkg_name} and not $is_on_cpan{$pkg_name}) {
91 p(2, colored("$pkg_name is not a CPAN distribution (forced)", 'bright_red')
93 push @not_on_cpan, "$category/$pkg_name";
98 if (exists $fetched_uri{$ebuild}) {
99 $uri = $fetched_uri{$ebuild};
101 my @cmd = ('ebuild', $ebuild, 'help', '--debug');
103 (undef, my $err) = capture {
104 $ret = system { $cmd[0] } @cmd;
107 if ($ret != 0 or $code == -1 or $code & 127 or $code >> 8) {
108 die "system(\"@cmd\") returned $ret and/or failed with status $code";
112 while ($err =~ /([a-zA-Z0-9_]+)=((['"]).*?\3|\S+)/gs) {
115 $val =~ s{^(['"])(.*?)\1$}{$2}s;
119 $uri = $map{SRC_URI};
120 unless (defined $uri) {
121 my $author = $map{MODULE_AUTHOR};
122 if (defined $author) {
123 my ($au, $a) = $author =~ /^((.).)/;
124 my $dist_version = $map{MODULE_VERSION};
125 $dist_version = $last->[1] unless defined $dist_version;
126 $uri = "mirror://cpan/$a/$au/$author/$pkg_name/$dist_version.tar.gz";
130 $fetched_uri{$ebuild} = $uri;
137 my ($fqn_dist, $path);
139 if ($uri =~ m{cpan.*?/id/(\S+)}) {
141 $path = "authors/id/$fqn_dist";
142 } elsif ($uri =~ m{mirror://cpan/(\S+)}) {
144 } elsif ($uri =~ m{/([^/\s]+)(?:\s|$)}) {
146 my ($top_level) = $archive =~ /^([^-]+)/;
147 $path = "modules/by-module/$top_level/$archive";
151 unless (defined $path) {
152 p(2, "doesn't seem to be fetching its tarball from a CPAN mirror\n");
153 p(2, colored("$pkg_name is not a CPAN distribution", 'bright_red') . "\n");
154 push @not_on_cpan, "$category/$pkg_name";
159 if (defined $fqn_dist) {
160 p(2, "is $fqn_dist indexed on the CPAN... ");
163 eval { $pcp->distribution($fqn_dist) }
165 print defined $dist ? "yes\n" : "no\n";
168 unless (defined $dist) {
169 p(2, "can $path be found on a CPAN mirror... ");
170 if (cpan_http_test($path)) {
172 $dist = CPAN::DistnameInfo->new($path);
178 my ($pseudo_dist, $latest_dist);
180 unless (defined $dist) {
181 $path =~ m{([^/\s]+)$} or die 'Could not get the last part of the path';
183 $pseudo_dist = CPAN::DistnameInfo->new($archive);
185 p(2, 'is ' . $pseudo_dist->dist . ' the name of a CPAN distribution... ');
188 eval { $pcp->latest_distribution($pseudo_dist->dist) };
191 unless (defined $latest_dist) {
193 (my $mod_name = $pkg_name) =~ s/-/::/g;
194 p(2, "is $mod_name indexed in another CPAN distribution... ");
198 my $module = $pcp->package($mod_name);
199 defined $module ? $module->distribution : undef;
202 if (defined $latest_dist) {
203 # Re-forge the pseudo dist so that it will pick up the correct dist
204 # name when looking for a mismatch.
205 $pseudo_dist = CPAN::DistnameInfo->new(
206 $latest_dist->dist . '-' . $pseudo_dist->version
207 . '.' . $pseudo_dist->extension
212 my ($latest_file, $latest_author);
213 if (defined $latest_dist) {
214 $latest_file = $latest_dist->filename;
215 $latest_author = $latest_dist->cpanid;
216 printf "yes, in %s by %s\n",
218 (defined $latest_author ? $latest_author : 'unknown');
223 if (defined $latest_author) {
224 my ($au, $a) = $latest_author =~ /^((.).)/ or die 'Author name too short';
225 p(2, "is $archive in that author\'s CPAN directory... ");
226 my $alternate_path = "authors/id/$a/$au/$latest_author/$archive";
227 if ($alternate_path eq $path) {
228 print "already checked\n";
229 } elsif (cpan_http_test($alternate_path)) {
230 $dist = CPAN::DistnameInfo->new($alternate_path);
235 unless (defined $dist) {
237 "$category/$pkg_name (latest is $latest_file by $latest_author)";
242 unless (defined $dist) {
243 if ($latest_dist or $is_on_cpan{$pkg_name}) {
244 $dist = $pseudo_dist;
245 p(2, "seems to be a CPAN distribution");
249 # Implies $is_on_cpan{$pkg_name}
251 push @unfindable, "$category/$pkg_name";
254 p(2, colored("$pkg_name is not a CPAN distribution", 'bright_red') . "\n");
255 push @not_on_cpan, "$category/$pkg_name";
260 my $dist_name = $dist->dist;
261 if ($dist_name ne $pkg_name) {
262 p(2, colored("$dist_name => $pkg_name", 'bright_yellow') . "\n");
263 $name_mismatch{$dist_name} = $pkg_name;
266 my $pkg_version = $atom->version . '';
267 $pkg_version =~ s/-r\d+$//;
268 my $dist_version = $dist->version;
269 my $mapped_version = CPANPLUS::Dist::Gentoo::Maps::version_c2g(
270 undef, # default conversion
273 if ($mapped_version ne $pkg_version) {
274 my $str = "$dist_version => $mapped_version != $pkg_version";
275 p(2, colored($str, 'bright_cyan') . "\n");
277 $version{$dist_name} = [ $dist_version => $pkg_version ];
282 my $already_parsed = 0;
285 my $state = Storable::retrieve(STATE_FILE);
286 if ($state->[0] == $timestamp) {
287 printf "State retrieved from %s\n", STATE_FILE;
288 @not_on_cpan = @{ $state->[1] };
289 @unfindable = @{ $state->[2] };
290 @missing = @{ $state->[3] };
291 %name_mismatch = %{ $state->[4] };
292 %version = %{ $state->[5] };
295 printf "Obsolete state file %s, regenerating\n", STATE_FILE;
296 1 while unlink STATE_FILE;
300 unless ($already_parsed) {
302 my $data = Storable::retrieve(DATA_FILE);
303 if ($data->[0] == $timestamp) {
304 printf "Data retrieved from %s\n", DATA_FILE;
305 %fetched_uri = %{ $data->[1] };
307 printf "Obsolete data file %s, regenerating\n", DATA_FILE;
308 1 while unlink DATA_FILE;
312 parse_portage_tree();
323 printf "State stored to %s\n", STATE_FILE;
329 p(1, "Not on the CPAN:\n");
330 p(2, "$_\n") for @not_on_cpan;
332 p(1, "Alleged to be on the CPAN, but unfindable:\n");
333 p(2, "$_\n") for @unfindable;
335 p(1, "Only a different version is on the CPAN:\n");
336 p(2, "$_\n") for @missing;
338 p(1, "Name mismatch:\n");
339 for my $dist_name (sort keys %name_mismatch) {
340 my $pkg_name = $name_mismatch{$dist_name};
341 my $mapped_name = CPANPLUS::Dist::Gentoo::Maps::name_c2g($dist_name);
343 my $fixed = $mapped_name eq $pkg_name;
344 my $eq = $fixed ? '==' : '!=';
346 "$dist_name => $mapped_name $eq $pkg_name",
347 $fixed ? 'bright_green' : 'bright_red'
352 p(1, "Version mismatch:\n");
353 for (sort keys %version) {
354 my ($dist_version, $pkg_version) = @{$version{$_}};
355 my $default_mapped_version = CPANPLUS::Dist::Gentoo::Maps::version_c2g(
359 my $mapped_version = CPANPLUS::Dist::Gentoo::Maps::version_c2g(
363 if ($default_mapped_version ne $pkg_version) {
364 my $fixed = $mapped_version eq $pkg_version;
365 my $eq = $fixed ? '==' : '!=';
367 "$dist_version => $mapped_version $eq $pkg_version",
368 $fixed ? 'bright_green' : 'bright_red'
374 copy TARGET, BACKUP or die "copy failed: $!";
376 open my $src, '<', BACKUP;
377 open my $dst, '>', TARGET;
379 my $max = max map length, keys %name_mismatch;
381 SRC: while (<$src>) {
382 if (/^sub TIMESTAMP/) {
383 print $dst "sub TIMESTAMP () { $timestamp }\n";
384 } elsif (/^__DATA__$/) {
385 print $dst "__DATA__\n";
386 printf $dst "%s%s %s\n", $_, (' ' x ($max - length)), $name_mismatch{$_}
387 for sort keys %name_mismatch;
394 print "\n" . +(keys %name_mismatch) . " name mismatches found\n";