From: Jo-Philipp Wich Date: Thu, 24 Jan 2019 09:31:26 +0000 (+0100) Subject: add a Perl script to check for ABI_VERSION related things in packages X-Git-Url: http://git.cdn.openwrt.org/?a=commitdiff_plain;h=2894525d1ad429bb436e6cdca8e28af54f20da26;p=maintainer-tools.git add a Perl script to check for ABI_VERSION related things in packages Signed-off-by: Jo-Philipp Wich --- diff --git a/check-abi-versions.pl b/check-abi-versions.pl new file mode 100755 index 0000000..6214c52 --- /dev/null +++ b/check-abi-versions.pl @@ -0,0 +1,195 @@ +#!/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}); +}