--- /dev/null
+#!/usr/bin/env perl
+
+use strict;
+use warnings;
+use File::Temp 'tempfile';
+
+$ENV{'LC_ALL'} = 'C';
+
+sub version_cmp($$) {
+ my ($a, $b) = @_;
+
+ my $x = join '', map { sprintf "%04s", $_ } split /\./, $a;
+ my $y = join '', map { sprintf "%04s", $_ } split /\./, $b;
+
+ return ($x cmp $y);
+}
+
+sub print_diag($$) {
+ my ($source, $pkgs) = @_;
+ my $issues = 0;
+ my @messages;
+
+ foreach my $pkg (@$pkgs) {
+ my (@pkgissues, %abi_versions);
+
+ next if !defined($pkg->{'libs'}) || @{$pkg->{'libs'}} == 0;
+
+ foreach my $lib (@{$pkg->{'libs'}}) {
+ next unless defined $lib->{'soname'};
+
+ if ($lib->{'soname'} =~ m!^.+\.so\.(.+?)$!) {
+ $abi_versions{$1}++;
+ }
+ }
+
+ if (keys(%abi_versions) > 1) {
+ push @pkgissues, "bundles multiple libraries with different SONAME versions,\n".
+ " consider splitting into multiple packages:";
+
+ foreach my $lib (@{$pkg->{'libs'}}) {
+ next unless defined $lib->{'soname'};
+
+ $pkgissues[-1] .= sprintf "\n - define Package/lib%s (%s)",
+ $lib->{'name'}, $lib->{'soname'};
+ }
+ }
+
+ my ($highest_version) = sort version_cmp keys %abi_versions;
+
+ if (defined($highest_version) && !defined($pkg->{'abiversion'})) {
+ push @pkgissues, sprintf "should specify ABI_VERSION:=%s", $highest_version;
+ }
+ elsif (defined($highest_version) && defined($pkg->{'abiversion'}) &&
+ !exists($abi_versions{$pkg->{'abiversion'}})) {
+ push @pkgissues,
+ sprintf "specifies ABI_VERSION:=%s but none of the libary sonames matches, " .
+ "consider changing to ABI_VERSION:=%s",
+ $pkg->{'abiversion'}, $highest_version;
+ }
+
+ foreach my $lib (@{$pkg->{'libs'}}) {
+ next unless defined $lib->{'soname'};
+
+ if ($lib->{'soname'} =~ m!\.so(?:\.[0-9a-zA-Z]+)+$! && $lib->{'unversioned_symlink'}) {
+ push @pkgissues,
+ sprintf "should not package unversioned %s symlink",
+ $lib->{'unversioned_symlink'};
+ }
+ }
+
+ if (@pkgissues > 0) {
+ push @messages,
+ sprintf " Package %s (define Package/%s)\n",
+ $pkg->{'name'}, $pkg->{'name'};
+
+ foreach my $issue (@pkgissues) {
+ push @messages,
+ sprintf " [-] %s\n", $issue;
+ }
+
+ $issues += @pkgissues;
+ }
+ }
+
+ if ($issues) {
+ printf "Source %s/Makefile\n", $source;
+ print @messages;
+ }
+}
+
+sub analyze_ipk($) {
+ my $ipk = shift;
+ my (%info, $lib);
+
+ $ipk =~ s/'/'"'"'/g;
+
+ if (open my $control, '-|', "tar -Ozxf '$ipk' ./control.tar.gz | tar -Ozx ./control") {
+ while (defined(my $line = readline $control)) {
+ chomp $line;
+
+ if ($line =~ m!^Package: *(\S+)$!) {
+ $info{'name'} = $1;
+ }
+ elsif ($line =~ m!^Source: *(\S+)$!) {
+ $info{'source'} = $1;
+ }
+ elsif ($line =~ m!^SourceName: *(\S+)$!) {
+ my $abiv = substr $info{'name'}, length $1;
+
+ $info{'name'} = $1;
+ $abiv =~ s/^-//;
+ $info{'abiversion'} = $abiv if length $abiv;
+ }
+ }
+
+ close $control;
+ }
+
+ if (open my $listing, '-|', "tar -Ozxf '$ipk' ./data.tar.gz | tar -tz | sort") {
+ while (defined(my $entry = readline $listing)) {
+ chomp $entry; $entry =~ s/'/'"'"'/g;
+
+ if ($entry =~ m!.+/lib/lib(\S+)\.so((?:\.[0-9a-zA-Z]+)+)?$!) {
+ my ($fd, $fname) = tempfile('/tmp/libfile.so.XXXXXXX', 'UNLINK' => 1);
+ my ($libname, $libversion) = ($1, $2);
+
+ if (!$lib || $lib->{'name'} ne $libname) {
+ $lib = { 'name' => $libname };
+ push @{$info{'libs'}}, $lib;
+ }
+
+ if (open my $extract, '-|', "tar -Ozxf '$ipk' ./data.tar.gz | tar -Ozx '$entry'") {
+ while (read $extract, my $buf, 1024) {
+ print $fd $buf;
+ }
+
+ close $extract;
+ }
+
+ if (tell($fd) > 0) {
+ if (open my $readelf, '-|', 'readelf', '-d', $fname) {
+ while (defined(my $line = readline $readelf)) {
+ chomp $line;
+
+ if ($line =~ m!^ 0x[0-9a-f]{8,16} \(SONAME\) +Library soname: \[(.+)\]$!) {
+ $lib->{'soname'} = $1;
+ last;
+ }
+ }
+
+ close $readelf;
+ }
+ else {
+ warn "Failed to execute readelf: $!\n";
+ }
+ }
+ elsif ($libversion) {
+ $lib->{'versioned_symlink'} = $entry;
+ }
+ else {
+ $lib->{'unversioned_symlink'} = $entry;
+ }
+
+ unlink $fname;
+ close $fd;
+ }
+ }
+
+ close $listing;
+ }
+
+ return \%info;
+}
+
+@ARGV >= 1 || die "Usage: $0 <.ipk directory> [<.ipk directory>...]\n";
+
+my %sources;
+
+foreach my $dir (@ARGV) {
+ if (open my $find, '-|', 'find', $dir, '-type', 'f', '-name', 'lib*.ipk') {
+ while (defined(my $ipk = readline $find)) {
+ chomp $ipk;
+ my $pkg = analyze_ipk($ipk);
+ if (defined($pkg) && defined($pkg->{'source'})) {
+ push @{$sources{$pkg->{'source'}}}, $pkg;
+ }
+ }
+
+ close $find;
+ }
+}
+
+foreach my $source (sort keys %sources) {
+ print_diag($source, $sources{$source});
+}