github-merge-pr: fix loading .config if symbolic link is used
[maintainer-tools.git] / check-abi-versions.pl
1 #!/usr/bin/env perl
2
3 use strict;
4 use warnings;
5 use File::Temp 'tempfile';
6
7 $ENV{'LC_ALL'} = 'C';
8
9 sub version_cmp($$) {
10 my ($a, $b) = @_;
11
12 my $x = join '', map { sprintf "%04s", $_ } split /\./, $a;
13 my $y = join '', map { sprintf "%04s", $_ } split /\./, $b;
14
15 return ($x cmp $y);
16 }
17
18 sub print_diag($$) {
19 my ($source, $pkgs) = @_;
20 my $issues = 0;
21 my @messages;
22
23 foreach my $pkg (@$pkgs) {
24 my (@pkgissues, %abi_versions);
25
26 next if !defined($pkg->{'libs'}) || @{$pkg->{'libs'}} == 0;
27
28 foreach my $lib (@{$pkg->{'libs'}}) {
29 next unless defined $lib->{'soname'};
30
31 if ($lib->{'soname'} =~ m!^.+\.so\.(.+?)$!) {
32 $abi_versions{$1}++;
33 }
34 }
35
36 if (keys(%abi_versions) > 1) {
37 push @pkgissues, "bundles multiple libraries with different SONAME versions,\n".
38 " consider splitting into multiple packages:";
39
40 foreach my $lib (@{$pkg->{'libs'}}) {
41 next unless defined $lib->{'soname'};
42
43 $pkgissues[-1] .= sprintf "\n - define Package/lib%s (%s)",
44 $lib->{'name'}, $lib->{'soname'};
45 }
46 }
47
48 my ($highest_version) = sort version_cmp keys %abi_versions;
49
50 if (defined($highest_version) && !defined($pkg->{'abiversion'})) {
51 push @pkgissues, sprintf "should specify ABI_VERSION:=%s", $highest_version;
52 }
53 elsif (defined($highest_version) && defined($pkg->{'abiversion'}) &&
54 !exists($abi_versions{$pkg->{'abiversion'}})) {
55 push @pkgissues,
56 sprintf "specifies ABI_VERSION:=%s but none of the libary sonames matches, " .
57 "consider changing to ABI_VERSION:=%s",
58 $pkg->{'abiversion'}, $highest_version;
59 }
60
61 foreach my $lib (@{$pkg->{'libs'}}) {
62 next unless defined $lib->{'soname'};
63
64 if ($lib->{'soname'} =~ m!\.so(?:\.[0-9a-zA-Z]+)+$! && $lib->{'unversioned_symlink'}) {
65 push @pkgissues,
66 sprintf "should not package unversioned %s symlink",
67 $lib->{'unversioned_symlink'};
68 }
69 }
70
71 if (@pkgissues > 0) {
72 push @messages,
73 sprintf " Package %s (define Package/%s)\n",
74 $pkg->{'name'}, $pkg->{'name'};
75
76 foreach my $issue (@pkgissues) {
77 push @messages,
78 sprintf " [-] %s\n", $issue;
79 }
80
81 $issues += @pkgissues;
82 }
83 }
84
85 if ($issues) {
86 printf "Source %s/Makefile\n", $source;
87 print @messages;
88 }
89 }
90
91 sub analyze_ipk($) {
92 my $ipk = shift;
93 my (%info, $lib);
94
95 $ipk =~ s/'/'"'"'/g;
96
97 if (open my $control, '-|', "tar -Ozxf '$ipk' ./control.tar.gz | tar -Ozx ./control") {
98 while (defined(my $line = readline $control)) {
99 chomp $line;
100
101 if ($line =~ m!^Package: *(\S+)$!) {
102 $info{'name'} = $1;
103 }
104 elsif ($line =~ m!^Source: *(\S+)$!) {
105 $info{'source'} = $1;
106 }
107 elsif ($line =~ m!^SourceName: *(\S+)$!) {
108 my $abiv = substr $info{'name'}, length $1;
109
110 $info{'name'} = $1;
111 $abiv =~ s/^-//;
112 $info{'abiversion'} = $abiv if length $abiv;
113 }
114 }
115
116 close $control;
117 }
118
119 if (open my $listing, '-|', "tar -Ozxf '$ipk' ./data.tar.gz | tar -tz | sort") {
120 while (defined(my $entry = readline $listing)) {
121 chomp $entry; $entry =~ s/'/'"'"'/g;
122
123 if ($entry =~ m!.+/lib/lib(\S+)\.so((?:\.[0-9a-zA-Z]+)+)?$!) {
124 my ($fd, $fname) = tempfile('/tmp/libfile.so.XXXXXXX', 'UNLINK' => 1);
125 my ($libname, $libversion) = ($1, $2);
126
127 if (!$lib || $lib->{'name'} ne $libname) {
128 $lib = { 'name' => $libname };
129 push @{$info{'libs'}}, $lib;
130 }
131
132 if (open my $extract, '-|', "tar -Ozxf '$ipk' ./data.tar.gz | tar -Ozx '$entry'") {
133 while (read $extract, my $buf, 1024) {
134 print $fd $buf;
135 }
136
137 close $extract;
138 }
139
140 if (tell($fd) > 0) {
141 if (open my $readelf, '-|', 'readelf', '-d', $fname) {
142 while (defined(my $line = readline $readelf)) {
143 chomp $line;
144
145 if ($line =~ m!^ 0x[0-9a-f]{8,16} \(SONAME\) +Library soname: \[(.+)\]$!) {
146 $lib->{'soname'} = $1;
147 last;
148 }
149 }
150
151 close $readelf;
152 }
153 else {
154 warn "Failed to execute readelf: $!\n";
155 }
156 }
157 elsif ($libversion) {
158 $lib->{'versioned_symlink'} = $entry;
159 }
160 else {
161 $lib->{'unversioned_symlink'} = $entry;
162 }
163
164 unlink $fname;
165 close $fd;
166 }
167 }
168
169 close $listing;
170 }
171
172 return \%info;
173 }
174
175 @ARGV >= 1 || die "Usage: $0 <.ipk directory> [<.ipk directory>...]\n";
176
177 my %sources;
178
179 foreach my $dir (@ARGV) {
180 if (open my $find, '-|', 'find', $dir, '-type', 'f', '-name', 'lib*.ipk') {
181 while (defined(my $ipk = readline $find)) {
182 chomp $ipk;
183 my $pkg = analyze_ipk($ipk);
184 if (defined($pkg) && defined($pkg->{'source'})) {
185 push @{$sources{$pkg->{'source'}}}, $pkg;
186 }
187 }
188
189 close $find;
190 }
191 }
192
193 foreach my $source (sort keys %sources) {
194 print_diag($source, $sources{$source});
195 }