xref: /petsc/lib/petsc/bin/maint/abi-compliance-checker/modules/Internals/SysCheck.pm (revision e8b6250908b962c387f7ab2e7b38caaa661b5fa1)
1###########################################################################
2# A module to compare operating systems
3#
4# Copyright (C) 2009-2011 Institute for System Programming, RAS
5# Copyright (C) 2011-2012 Nokia Corporation and/or its subsidiary(-ies)
6# Copyright (C) 2012-2018 Andrey Ponomarenko's ABI Laboratory
7#
8# Written by Andrey Ponomarenko
9#
10# This library is free software; you can redistribute it and/or
11# modify it under the terms of the GNU Lesser General Public
12# License as published by the Free Software Foundation; either
13# version 2.1 of the License, or (at your option) any later version.
14#
15# This library is distributed in the hope that it will be useful,
16# but WITHOUT ANY WARRANTY; without even the implied warranty of
17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18# Lesser General Public License for more details.
19#
20# You should have received a copy of the GNU Lesser General Public
21# License along with this library; if not, write to the Free Software
22# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
23# MA  02110-1301 USA
24###########################################################################
25use strict;
26use File::Temp qw(tempdir);
27use Cwd qw(abs_path cwd);
28
29loadModule("ElfTools");
30loadModule("ABIDump");
31
32my %SysDesc;
33my %NonPrefix;
34
35sub cmpSystems($$)
36{ # -cmp-systems option handler
37  # should be used with -d1 and -d2 options
38    my ($SPath1, $SPath2) = @_;
39
40    if(not $SPath1) {
41        exitStatus("Error", "the option -d1 should be specified");
42    }
43    elsif(not -d $SPath1) {
44        exitStatus("Access_Error", "can't access directory \'".$SPath1."\'");
45    }
46    elsif(not -d $SPath1."/abi_dumps") {
47        exitStatus("Access_Error", "can't access directory \'".$SPath1."/abi_dumps\'");
48    }
49    if(not $SPath2) {
50        exitStatus("Error", "the option -d2 should be specified");
51    }
52    elsif(not -d $SPath2) {
53        exitStatus("Access_Error", "can't access directory \'".$SPath2."\'");
54    }
55    elsif(not -d $SPath2."/abi_dumps") {
56        exitStatus("Access_Error", "can't access directory \'".$SPath2."/abi_dumps\'");
57    }
58    # sys_dumps/<System>/<Arch>/...
59    my $SystemName1 = getFilename(getDirname($SPath1));
60    my $SystemName2 = getFilename(getDirname($SPath2));
61
62    my $SystemName1_P = $SystemName1;
63    my $SystemName2_P = $SystemName2;
64
65    $SystemName1=~s/_/ /g;
66    $SystemName2=~s/_/ /g;
67
68    # sys_dumps/<System>/<Arch>/...
69    my $ArchName = getFilename($SPath1);
70    if($ArchName ne getFilename($SPath2)) {
71        exitStatus("Error", "can't compare systems of different CPU architecture");
72    }
73
74    my $TmpDir = $In::Opt{"Tmp"};
75
76    if(my $OStarget_Dump = readFile($SPath1."/target.txt"))
77    { # change target
78        setTarget($OStarget_Dump);
79    }
80
81    my $GroupByHeaders = 0;
82    if(my $Mode = readFile($SPath1."/mode.txt"))
83    { # change mode
84        if($Mode eq "headers-only")
85        { # -headers-only mode
86            $In::Opt{"CheckHeadersOnly"} = 1;
87            $GroupByHeaders = 1;
88        }
89        if($Mode eq "group-by-headers") {
90            $GroupByHeaders = 1;
91        }
92    }
93    my $SYS_REPORT_PATH = "sys_compat_reports/".$SystemName1_P."_to_".$SystemName2_P."/$ArchName";
94    rmtree($SYS_REPORT_PATH);
95    my (%LibSoname1, %LibSoname2) = ();
96    foreach (split(/\n/, readFile($SPath1."/sonames.txt")))
97    {
98        if(my ($LFName, $Soname) = split(/;/, $_))
99        {
100            if($In::Opt{"Target"} eq "symbian") {
101                $Soname=~s/\{.+\}//;
102            }
103            $LibSoname1{$LFName} = $Soname;
104        }
105    }
106    foreach (split(/\n/, readFile($SPath2."/sonames.txt")))
107    {
108        if(my ($LFName, $Soname) = split(/;/, $_))
109        {
110            if($In::Opt{"Target"} eq "symbian") {
111                $Soname=~s/\{.+\}//;
112            }
113            $LibSoname2{$LFName} = $Soname;
114        }
115    }
116    my (%LibV1, %LibV2) = ();
117    foreach (split(/\n/, readFile($SPath1."/versions.txt")))
118    {
119        if(my ($LFName, $V) = split(/;/, $_)) {
120            $LibV1{$LFName} = $V;
121        }
122    }
123    foreach (split(/\n/, readFile($SPath2."/versions.txt")))
124    {
125        if(my ($LFName, $V) = split(/;/, $_)) {
126            $LibV2{$LFName} = $V;
127        }
128    }
129    my @Dumps1 = cmdFind($SPath1."/abi_dumps","f","*.abi",1);
130    my @Dumps2 = cmdFind($SPath2."/abi_dumps","f","*.abi",1);
131
132    my (%LibVers1, %LibVers2) = ();
133    my (%ShortNames1, %ShortNames2) = ();
134    foreach my $DPath (@Dumps1)
135    {
136        if(my $Name = isDump($DPath))
137        {
138            my ($Soname, $V) = ($LibSoname1{$Name}, $LibV1{$Name});
139            if(not $V) {
140                $V = libPart($Name, "version");
141            }
142            if($GroupByHeaders) {
143                $Soname = $Name;
144            }
145            $LibVers1{$Soname}{$V} = $DPath;
146            $ShortNames1{libPart($Soname, "short")}{$Soname} = 1;
147        }
148    }
149    foreach my $DPath (@Dumps2)
150    {
151        if(my $Name = isDump($DPath))
152        {
153            my ($Soname, $V) = ($LibSoname2{$Name}, $LibV2{$Name});
154            if(not $V) {
155                $V = libPart($Name, "version");
156            }
157            if($GroupByHeaders) {
158                $Soname = $Name;
159            }
160            $LibVers2{$Soname}{$V} = $DPath;
161            $ShortNames2{libPart($Soname, "short")}{$Soname} = 1;
162        }
163    }
164    my (%Added, %Removed) = ();
165    my (%ChangedSoname, %TestResults) = ();
166    my (%AddedShort, %RemovedShort) = ();
167    if(not $GroupByHeaders)
168    {
169        my %ChangedSoname_Safe = ();
170        foreach my $LName (sort keys(%LibSoname2))
171        { # libcurl.so.3 -> libcurl.so.4 (search for SONAME by the file name)
172          # OS #1 => OS #2
173            if(defined $LibVers2{$LName})
174            { # already registered
175                next;
176            }
177            my $Soname = $LibSoname2{$LName};
178            if(defined $LibVers2{$Soname}
179            and defined $LibVers1{$LName})
180            {
181                $LibVers2{$LName} = $LibVers2{$Soname};
182                $ChangedSoname_Safe{$Soname}=$LName;
183            }
184        }
185        foreach my $LName (sort keys(%LibSoname1))
186        { # libcurl.so.3 -> libcurl.so.4 (search for SONAME by the file name)
187          # OS #1 <= OS #2
188            if(defined $LibVers1{$LName})
189            { # already registered
190                next;
191            }
192            my $Soname = $LibSoname1{$LName};
193            if(defined $LibVers1{$Soname}
194            and defined $LibVers2{$LName}) {
195                $LibVers1{$LName} = $LibVers1{$Soname};
196            }
197        }
198        if(not $GroupByHeaders) {
199            printMsg("INFO", "Checking added/removed libs");
200        }
201        foreach my $LName (sort {lc($a) cmp lc($b)} keys(%LibVers1))
202        { # removed libs
203            if(not isTargetLib($LName)) {
204                next;
205            }
206            if(not defined $LibVers1{$LName}) {
207                next;
208            }
209            my @Versions1 = keys(%{$LibVers1{$LName}});
210            if($#Versions1>=1)
211            { # should be only one version
212                next;
213            }
214            if(not defined $LibVers2{$LName}
215            or not keys(%{$LibVers2{$LName}}))
216            { # removed library
217                if(not $LibSoname2{$LName})
218                {
219                    my $LSName = libPart($LName, "short");
220                    $RemovedShort{$LSName}{$LName} = 1;
221                    my $V = $Versions1[0];
222                    $Removed{$LName}{"version"} = $V;
223
224                    my $ListPath = "info/$LName/symbols.html";
225                    my $FV = $SystemName1;
226                    if($V) {
227                        $FV = $V."-".$FV;
228                    }
229                    createSymbolsList($LibVers1{$LName}{$V},
230                    $SYS_REPORT_PATH."/".$ListPath, $LName, $FV, $ArchName);
231                    $Removed{$LName}{"list"} = $ListPath;
232                }
233            }
234        }
235        foreach my $LName (sort {lc($a) cmp lc($b)} keys(%LibVers2))
236        { # added libs
237            if(not isTargetLib($LName)) {
238                next;
239            }
240            if(not defined $LibVers2{$LName}) {
241                next;
242            }
243            my @Versions2 = keys(%{$LibVers2{$LName}});
244            if($#Versions2>=1)
245            { # should be only one version
246                next;
247            }
248            if($ChangedSoname_Safe{$LName})
249            { # changed soname but added the symbolic link for old-version library
250                next;
251            }
252            if(not defined $LibVers1{$LName}
253            or not keys(%{$LibVers1{$LName}}))
254            { # added library
255                if(not $LibSoname1{$LName})
256                {
257                    my $LSName = libPart($LName, "short");
258                    $AddedShort{$LSName}{$LName} = 1;
259                    my $V = $Versions2[0];
260                    $Added{$LName}{"version"} = $V;
261
262                    my $ListPath = "info/$LName/symbols.html";
263                    my $FV = $SystemName2;
264                    if($V) {
265                        $FV = $V."-".$FV;
266                    }
267                    createSymbolsList($LibVers2{$LName}{$V},
268                    $SYS_REPORT_PATH."/".$ListPath, $LName, $FV, $ArchName);
269                    $Added{$LName}{"list"} = $ListPath;
270                }
271            }
272        }
273        foreach my $LSName (keys(%AddedShort))
274        { # changed SONAME
275            my @AddedSonames = sort keys(%{$AddedShort{$LSName}});
276            next if($#AddedSonames!=0);
277
278            if(defined $RemovedShort{$LSName})
279            { # removed old soname
280                my @RemovedSonames = sort keys(%{$RemovedShort{$LSName}});
281                $ChangedSoname{$AddedSonames[0]} = $RemovedSonames[0];
282                $ChangedSoname{$RemovedSonames[0]} = $AddedSonames[0];
283            }
284            elsif(defined $ShortNames1{$LSName})
285            { # saved old soname
286                my @Sonames = sort keys(%{$ShortNames1{$LSName}});
287                $ChangedSoname{$AddedSonames[0]} = $Sonames[0];
288                $ChangedSoname{$Sonames[0]} = $AddedSonames[0];
289            }
290        }
291    }
292
293    my %SONAME_Changed = ();
294    my %SONAME_Added = ();
295
296    foreach my $LName (sort {lc($a) cmp lc($b)} keys(%LibVers1))
297    {
298        if(not isTargetLib($LName)) {
299            next;
300        }
301        my @Versions1 = keys(%{$LibVers1{$LName}});
302        if(not @Versions1 or $#Versions1>=1)
303        { # should be only one version
304            next;
305        }
306        my $LV1 = $Versions1[0];
307        my $DPath1 = $LibVers1{$LName}{$LV1};
308        my @Versions2 = keys(%{$LibVers2{$LName}});
309        if($#Versions2>=1)
310        { # should be only one version
311            next;
312        }
313        my ($LV2, $LName2, $DPath2) = ();
314        my $LName_Short = libPart($LName, "name+ext");
315        if($LName2 = $ChangedSoname{$LName})
316        { # changed SONAME
317            @Versions2 = keys(%{$LibVers2{$LName2}});
318            if(not @Versions2 or $#Versions2>=1) {
319                next;
320            }
321            $LV2 = $Versions2[0];
322            $DPath2 = $LibVers2{$LName2}{$LV2};
323
324            if(defined $LibVers2{$LName})
325            { # show old soname in the table
326                $TestResults{$LName}{"v1"} = $LV1;
327                $TestResults{$LName}{"v2"} = $LV1;
328            }
329
330            if(defined $LibVers2{$LName})
331            { # do not count results
332                $SONAME_Added{$LName_Short} = 1;
333            }
334            $SONAME_Changed{$LName_Short} = 1;
335            $LName = $LName_Short;
336        }
337        elsif(@Versions2)
338        {
339            $LV2 = $Versions2[0];
340            $DPath2 = $LibVers2{$LName}{$LV2};
341        }
342        else
343        { # removed
344            next;
345        }
346        my $ACC_compare = "perl $0 -l $LName -d1 \"$DPath1\" -d2 \"$DPath2\"";
347
348        my $BinReportPath = "compat_reports/$LName/abi_compat_report.html";
349        my $SrcReportPath = "compat_reports/$LName/src_compat_report.html";
350        my $BinReportPath_Full = $SYS_REPORT_PATH."/".$BinReportPath;
351        my $SrcReportPath_Full = $SYS_REPORT_PATH."/".$SrcReportPath;
352
353        if($In::Opt{"BinOnly"})
354        {
355            $ACC_compare .= " -binary";
356            $ACC_compare .= " -bin-report-path \"$BinReportPath_Full\"";
357        }
358        if($In::Opt{"SrcOnly"})
359        {
360            $ACC_compare .= " -source";
361            $ACC_compare .= " -src-report-path \"$SrcReportPath_Full\"";
362        }
363
364        if($In::Opt{"CheckHeadersOnly"}) {
365            $ACC_compare .= " -headers-only";
366        }
367        if($GroupByHeaders) {
368            $ACC_compare .= " -component header";
369        }
370
371        if($In::Opt{"DisableConstantsCheck"}) {
372            $ACC_compare .= " -disable-constants-check";
373        }
374
375        $ACC_compare .= " -skip-added-constants";
376        $ACC_compare .= " -skip-removed-constants";
377
378        if($In::Opt{"Quiet"})
379        { # quiet mode
380            $ACC_compare .= " -quiet";
381        }
382        if($In::Opt{"LogMode"} eq "n") {
383            $ACC_compare .= " -logging-mode n";
384        }
385        elsif($In::Opt{"Quiet"}) {
386            $ACC_compare .= " -logging-mode a";
387        }
388        if($In::Opt{"Debug"})
389        { # debug mode
390            $ACC_compare .= " -debug";
391            printMsg("INFO", "$ACC_compare");
392        }
393        printMsg("INFO_C", "Checking $LName: ");
394        system($ACC_compare." 1>$TmpDir/null 2>$TmpDir/$LName.stderr");
395        if(-s "$TmpDir/$LName.stderr")
396        {
397            my $ErrorLog = readFile("$TmpDir/$LName.stderr");
398            chomp($ErrorLog);
399            printMsg("INFO", "Failed ($ErrorLog)");
400        }
401        else
402        {
403            printMsg("INFO", "Ok");
404            if($In::Opt{"BinOnly"})
405            {
406                $TestResults{$LName}{"Binary"} = readAttributes($BinReportPath_Full, 0);
407                $TestResults{$LName}{"Binary"}{"path"} = $BinReportPath;
408            }
409            if($In::Opt{"SrcOnly"})
410            {
411                $TestResults{$LName}{"Source"} = readAttributes($SrcReportPath_Full, 0);
412                $TestResults{$LName}{"Source"}{"path"} = $SrcReportPath;
413            }
414            $TestResults{$LName}{"v1"} = $LV1;
415            $TestResults{$LName}{"v2"} = $LV2;
416        }
417
418        my $HP1 = $SPath1."/headers/".$LName;
419        my $HP2 = $SPath2."/headers/".$LName;
420
421        if(-d $HP1
422        and -d $HP2
423        and my $RfcDiff = getCmdPath("rfcdiff"))
424        {
425            my @Headers1 = cmdFind($HP1,"f");
426            my @Headers2 = cmdFind($HP2,"f");
427
428            my (%Files1, %Files2) = ();
429
430            foreach my $P (@Headers1) {
431                $Files1{getFilename($P)} = $P;
432            }
433
434            foreach my $P (@Headers2) {
435                $Files2{getFilename($P)} = $P;
436            }
437
438            my $Diff = "";
439            foreach my $N (sort {lc($a) cmp lc($b)} keys(%Files1))
440            {
441                my $Path1 = $Files1{$N};
442                my $Path2 = undef;
443
444                if(defined $Files2{$N}) {
445                    $Path2 = $Files2{$N};
446                }
447                else {
448                    next;
449                }
450
451                if(-s $Path1 == -s $Path2)
452                {
453                    if(readFile($Path1) eq readFile($Path2)) {
454                        next;
455                    }
456                }
457
458                my $DiffOut = $TmpDir."/rfcdiff";
459
460                if(-e $DiffOut) {
461                    unlink($DiffOut);
462                }
463
464                my $Cmd_R = $RfcDiff." --width 80 --stdout \"$Path1\" \"$Path2\" >$DiffOut 2>/dev/null";
465                qx/$Cmd_R/; # execute
466
467                if(-s $DiffOut)
468                {
469                    my $Content = readFile($DiffOut);
470                    if(length($Content)<3500 and $Content=~/The files are identical|No changes|Failed to create/i) {
471                        next;
472                    }
473
474                    $Content=~s/<\!--(.|\n)+?-->\s*//g;
475                    $Content=~s/\A((.|\n)+<body\s*>)((.|\n)+)(<\/body>(.|\n)+)\Z/$3/;
476                    $Content=~s/(<td colspan=\"5\"[^>]*>)(.+)(<\/td>)/$1$3/;
477                    $Content=~s/(<table) /$1 class='diff_tbl' /g;
478
479                    $Content=~s&<td class="lineno" valign="top"></td>&&g;
480                    $Content=~s&<td class="lineno"></td>&&g;
481                    $Content=~s&<th></th>&&g;
482                    $Content=~s&<td></td>&&g;
483
484                    $Content=~s/(\Q$N\E)(&nbsp;)/$1 ($LV1-$SystemName1)$2/;
485                    $Content=~s/(\Q$N\E)(&nbsp;)/$1 ($LV2-$SystemName2)$2/;
486
487                    if($Diff) {
488                        $Diff .= "<br/><br/>\n";
489                    }
490                    $Diff .= $Content;
491                }
492            }
493
494            if($Diff)
495            {
496                my $Title = $LName.": headers diff between $LV1-$SystemName1 and $LV2-$SystemName2 versions";
497                my $Keywords = $LName.", header, diff";
498                my $Description = "Diff for header files between $LV1-$SystemName1 and $LV2-$SystemName2 versions of $LName";
499                my $Styles = readModule("Styles", "HeadersDiff.css");
500
501                my $Link = "This html diff was produced by <a href='http://tools.ietf.org/tools/rfcdiff/'>rfcdiff</a> 1.41.";
502
503                $Diff .= "<br/>";
504                $Diff .= "<div style='width:100%;' align='left'>$Link</div>\n";
505
506                $Diff = "<h1>Headers diff for <span style='color:Blue;'>$LName</span> between <span style='color:Red;'>$LV1-$SystemName1</span> and <span style='color:Red;'>$LV2-$SystemName2</span> versions</h1><br/><br/>".$Diff;
507
508                $Diff = "<table width='100%' cellpadding='0' cellspacing='0'><tr><td>$Diff</td></tr></table>";
509
510                $Diff = composeHTML_Head($Title, $Keywords, $Description, $Styles, "", 1)."\n<body>\n$Diff\n</body>\n</html>\n";
511
512                my $Output = $SYS_REPORT_PATH."/headers_diff/$LName";
513                writeFile($Output."/diff.html", $Diff);
514            }
515        }
516    }
517
518    my %TOTAL = ();
519    foreach my $LName (keys(%TestResults))
520    {
521        if($SONAME_Changed{$LName}) {
522            next;
523        }
524        foreach my $Comp ("Binary", "Source")
525        {
526            if(not defined $TestResults{$LName}{$Comp}) {
527                next;
528            }
529            foreach my $Kind (keys(%{$TestResults{$LName}{$Comp}}))
530            {
531                if($Kind=~/_problems_(high|medium|low)/) {
532                    $TOTAL{$LName}{$Comp} += $TestResults{$LName}{$Comp}{$Kind};
533                }
534            }
535        }
536    }
537
538    my %META_DATA = ();
539    my %STAT = ();
540    foreach my $Comp ("Binary", "Source")
541    {
542        $STAT{$Comp}{"total"} = keys(%TestResults) - keys(%SONAME_Changed);
543        $STAT{$Comp}{"added"} = keys(%Added);
544        $STAT{$Comp}{"removed"} = keys(%Removed);
545
546        foreach ("added", "removed")
547        {
548            my $Kind = $_."_interfaces";
549            foreach my $LName (keys(%TestResults))
550            {
551                next if($SONAME_Changed{$LName});
552                $STAT{$Comp}{$Kind} += $TestResults{$LName}{$Comp}{$_};
553            }
554            push(@{$META_DATA{$Comp}}, $Kind.":".$STAT{$Comp}{$Kind});
555        }
556        foreach my $T ("type", "interface")
557        {
558            foreach my $S ("high", "medium", "low")
559            {
560                my $Kind = $T."_problems_".$S;
561                foreach my $LName (keys(%TestResults))
562                {
563                    next if($SONAME_Changed{$LName});
564                    $STAT{$Comp}{$Kind} += $TestResults{$LName}{$Comp}{$Kind};
565                }
566                push(@{$META_DATA{$Comp}}, $Kind.":".$STAT{$Comp}{$Kind});
567            }
568        }
569        foreach my $LName (keys(%TestResults))
570        {
571            next if($SONAME_Changed{$LName});
572            foreach ("affected", "changed_constants") {
573                $STAT{$Comp}{$_} += $TestResults{$LName}{$Comp}{$_};
574            }
575            if(not defined $STAT{$Comp}{"verdict"}
576            and $TestResults{$LName}{$Comp}{"verdict"} eq "incompatible") {
577                $STAT{$Comp}{"verdict"} = "incompatible";
578            }
579        }
580        if(not defined $STAT{$Comp}{"verdict"}) {
581            $STAT{$Comp}{"verdict"} = "compatible";
582        }
583        if($STAT{$Comp}{"total"}) {
584            $STAT{$Comp}{"affected"} /= $STAT{$Comp}{"total"};
585        }
586        else {
587            $STAT{$Comp}{"affected"} = 0;
588        }
589        $STAT{$Comp}{"affected"} = showNum($STAT{$Comp}{"affected"});
590        if($STAT{$Comp}{"verdict"}>1) {
591            $STAT{$Comp}{"verdict"} = 1;
592        }
593        push(@{$META_DATA{$Comp}}, "changed_constants:".$STAT{$Comp}{"changed_constants"});
594        push(@{$META_DATA{$Comp}}, "tool_version:".dumpVersion("perl $0"));
595        foreach ("removed", "added", "total", "affected", "verdict") {
596            @{$META_DATA{$Comp}} = ($_.":".$STAT{$Comp}{$_}, @{$META_DATA{$Comp}});
597        }
598    }
599
600    my $SONAME_Title = "SONAME";
601    if($In::Opt{"Target"} eq "windows") {
602        $SONAME_Title = "DLL";
603    }
604    elsif($In::Opt{"Target"} eq "symbian") {
605        $SONAME_Title = "DSO";
606    }
607    if($GroupByHeaders)
608    { # show the list of headers
609        $SONAME_Title = "Header File";
610    }
611
612    my $SYS_REPORT = "<h1>";
613
614    if($In::Opt{"BinOnly"}
615    and $In::Opt{"SrcOnly"}) {
616        $SYS_REPORT .= "API compatibility";
617    }
618    elsif($In::Opt{"BinOnly"}) {
619        $SYS_REPORT .= "Binary compatibility";
620    }
621    elsif($In::Opt{"SrcOnly"}) {
622        $SYS_REPORT .= "Source compatibility";
623    }
624
625    $SYS_REPORT .= " report between <span style='color:Blue;'>$SystemName1</span> and <span style='color:Blue;'>$SystemName2</span>";
626    $SYS_REPORT .= " on <span style='color:Blue;'>".showArch($ArchName)."</span>\n";
627
628    $SYS_REPORT .= "</h1>";
629    $SYS_REPORT .= "<br/>\n";
630
631    # legend
632    my $LEGEND = "<table class='legend'><tr>\n";
633    $LEGEND .= "<td class='new' width='70px' style='text-align:left'>ADDED</td>\n";
634    $LEGEND .= "<td class='passed' width='70px' style='text-align:left'>COMPATIBLE</td>\n";
635    $LEGEND .= "</tr><tr>\n";
636    $LEGEND .= "<td class='warning' style='text-align:left'>WARNING</td>\n";
637    $LEGEND .= "<td class='failed' style='text-align:left'>INCOMPATIBLE</td>\n";
638    $LEGEND .= "</tr></table>\n";
639
640    $SYS_REPORT .= $LEGEND;
641    $SYS_REPORT .= "<br/>\n";
642
643    my $Columns = 2;
644
645    my $Total = (keys(%TestResults) + keys(%Added) + keys(%Removed) - keys(%SONAME_Changed));
646    my $HDiff = $SYS_REPORT_PATH."/headers_diff";
647
648    $SYS_REPORT .= "<table class='summary'>\n";
649    $SYS_REPORT .= "<tr>\n";
650    $SYS_REPORT .= "<th rowspan='2'>$SONAME_Title<sup>$Total</sup></th>\n";
651    if(not $GroupByHeaders) {
652        $SYS_REPORT .= "<th colspan='2'>Version</th>\n";
653    }
654    if($In::Opt{"BinOnly"}
655    and $In::Opt{"SrcOnly"}) {
656        $SYS_REPORT .= "<th colspan='2'>Compatibility</th>\n";
657    }
658    else {
659        $SYS_REPORT .= "<th rowspan='2'>Compatibility</th>\n";
660    }
661    $SYS_REPORT .= "<th rowspan='2'>Added<br/>Symbols</th>\n";
662    $SYS_REPORT .= "<th rowspan='2'>Removed<br/>Symbols</th>\n";
663    if(-d $HDiff)
664    {
665        $SYS_REPORT .= "<th rowspan='2'>Headers<br/>Diff</th>\n";
666        $Columns += 1;
667    }
668    $SYS_REPORT .= "</tr>\n";
669
670    $SYS_REPORT .= "<tr>\n";
671    if(not $GroupByHeaders) {
672        $SYS_REPORT .= "<th class='ver'>$SystemName1</th><th class='ver'>$SystemName2</th>\n";
673    }
674    if($In::Opt{"BinOnly"}
675    and $In::Opt{"SrcOnly"}) {
676        $SYS_REPORT .= "<th>Binary</th><th>Source</th>\n";
677    }
678    $SYS_REPORT .= "</tr>\n";
679    my %RegisteredPairs = ();
680
681    foreach my $LName (sort {lc($a) cmp lc($b)} (keys(%TestResults), keys(%Added), keys(%Removed)))
682    {
683        next if($SONAME_Changed{$LName});
684        my $LName_Short = libPart($LName, "name+ext");
685        my $Anchor = $LName;
686        $Anchor=~s/\+/p/g; # anchor for libFLAC++ is libFLACpp
687        $Anchor=~s/\~/-/g; # libqttracker.so.1~6
688
689        $SYS_REPORT .= "<tr>\n";
690        $SYS_REPORT .= "<td class='object'>$LName<a name=\'$Anchor\'></a></td>\n";
691        if(defined $Removed{$LName}) {
692            $SYS_REPORT .= "<td class='failed ver'>".printVer($Removed{$LName}{"version"})."</td>\n";
693        }
694        elsif(defined $Added{$LName}) {
695            $SYS_REPORT .= "<td class='new'><a href='".$Added{$LName}{"list"}."'>added</a></td>\n";
696        }
697        elsif(not $GroupByHeaders)
698        {
699            $SYS_REPORT .= "<td class='ver'>".printVer($TestResults{$LName}{"v1"})."</td>\n";
700        }
701        my $SONAME_report = "<td colspan=\'$Columns\' rowspan='2'>\n";
702        if($In::Opt{"BinOnly"}
703        and $In::Opt{"SrcOnly"}) {
704            $SONAME_report .= "SONAME has been changed (see <a href='".$TestResults{$LName_Short}{"Binary"}{"path"}."'>binary</a> and <a href='".$TestResults{$LName_Short}{"Source"}{"path"}."'>source</a> compatibility reports)\n";
705        }
706        elsif($In::Opt{"BinOnly"}) {
707            $SONAME_report .= "SONAME has been <a href='".$TestResults{$LName_Short}{"Binary"}{"path"}."'>changed</a>\n";
708        }
709        elsif($In::Opt{"SrcOnly"}) {
710            $SONAME_report .= "SONAME has been <a href='".$TestResults{$LName_Short}{"Source"}{"path"}."'>changed</a>\n";
711        }
712        $SONAME_report .= "</td>\n";
713
714        if(defined $Added{$LName})
715        { # added library
716            $SYS_REPORT .= "<td class='new ver'>".printVer($Added{$LName}{"version"})."</td>\n";
717            $SYS_REPORT .= "<td class='passed'>100%</td>\n" if($In::Opt{"BinOnly"});
718            $SYS_REPORT .= "<td class='passed'>100%</td>\n" if($In::Opt{"SrcOnly"});
719            if($RegisteredPairs{$LName}) {
720                # do nothing
721            }
722            elsif(my $To = $ChangedSoname{$LName})
723            {
724                $RegisteredPairs{$To}=1;
725                $SYS_REPORT .= $SONAME_report;
726            }
727            else
728            {
729                foreach (1 .. $Columns) {
730                    $SYS_REPORT .= "<td>N/A</td>\n"; # colspan='5'
731                }
732            }
733            $SYS_REPORT .= "</tr>\n";
734            next;
735        }
736        elsif(defined $Removed{$LName})
737        { # removed library
738            $SYS_REPORT .= "<td class='failed'><a href='".$Removed{$LName}{"list"}."'>removed</a></td>\n";
739            $SYS_REPORT .= "<td class='failed'>0%</td>\n" if($In::Opt{"BinOnly"});
740            $SYS_REPORT .= "<td class='failed'>0%</td>\n" if($In::Opt{"SrcOnly"});
741            if($RegisteredPairs{$LName}) {
742                # do nothing
743            }
744            elsif(my $To = $ChangedSoname{$LName})
745            {
746                $RegisteredPairs{$To}=1;
747                $SYS_REPORT .= $SONAME_report;
748            }
749            else
750            {
751                foreach (1 .. $Columns) {
752                    $SYS_REPORT .= "<td>N/A</td>\n"; # colspan='5'
753                }
754            }
755            $SYS_REPORT .= "</tr>\n";
756            next;
757        }
758        elsif(defined $ChangedSoname{$LName})
759        { # added library
760            $SYS_REPORT .= "<td class='ver'>".printVer($TestResults{$LName}{"v2"})."</td>\n";
761            $SYS_REPORT .= "<td class='passed'>100%</td>\n" if($In::Opt{"BinOnly"});
762            $SYS_REPORT .= "<td class='passed'>100%</td>\n" if($In::Opt{"SrcOnly"});
763            if($RegisteredPairs{$LName}) {
764                # do nothing
765            }
766            elsif(my $To = $ChangedSoname{$LName})
767            {
768                $RegisteredPairs{$To}=1;
769                $SYS_REPORT .= $SONAME_report;
770            }
771            else
772            {
773                foreach (1 .. $Columns) {
774                    $SYS_REPORT .= "<td>N/A</td>\n"; # colspan='5'
775                }
776            }
777            $SYS_REPORT .= "</tr>\n";
778            next;
779        }
780        elsif(not $GroupByHeaders)
781        {
782            $SYS_REPORT .= "<td class='ver'>".printVer($TestResults{$LName}{"v2"})."</td>\n";
783        }
784
785        my $BinCompatReport = $TestResults{$LName}{"Binary"}{"path"};
786        my $SrcCompatReport = $TestResults{$LName}{"Source"}{"path"};
787
788        if($In::Opt{"BinOnly"})
789        {
790            if($TestResults{$LName}{"Binary"}{"verdict"} eq "compatible")
791            {
792                my $Cl = "passed";
793                if($TOTAL{$LName}{"Binary"}) {
794                    $Cl = "warning";
795                }
796                $SYS_REPORT .= "<td class=\'$Cl\'><a href=\'$BinCompatReport\'>100%</a></td>\n";
797            }
798            else
799            {
800                my $Compatible = 100 - $TestResults{$LName}{"Binary"}{"affected"};
801                my $Cl = "incompatible";
802                if($Compatible>=90) {
803                    $Cl = "warning";
804                }
805                elsif($Compatible>=80) {
806                    $Cl = "almost_compatible";
807                }
808                $SYS_REPORT .= "<td class=\'$Cl\'><a href=\'$BinCompatReport\'>$Compatible%</a></td>\n";
809            }
810        }
811        if($In::Opt{"SrcOnly"})
812        {
813            if($TestResults{$LName}{"Source"}{"verdict"} eq "compatible")
814            {
815                my $Cl = "passed";
816                if($TOTAL{$LName}{"Source"}) {
817                    $Cl = "warning";
818                }
819                $SYS_REPORT .= "<td class=\'$Cl\'><a href=\'$SrcCompatReport\'>100%</a></td>\n";
820            }
821            else
822            {
823                my $Compatible = 100 - $TestResults{$LName}{"Source"}{"affected"};
824                my $Cl = "incompatible";
825                if($Compatible>=90) {
826                    $Cl = "warning";
827                }
828                elsif($Compatible>=80) {
829                    $Cl = "almost_compatible";
830                }
831                $SYS_REPORT .= "<td class=\'$Cl\'><a href=\'$SrcCompatReport\'>$Compatible%</a></td>\n";
832            }
833        }
834        if($In::Opt{"BinOnly"})
835        { # show added/removed symbols at binary level
836          # for joined and -binary-only reports
837            my $AddedSym="";
838            if(my $Count = $TestResults{$LName}{"Binary"}{"added"}) {
839                $AddedSym="<a href='$BinCompatReport\#Added'>$Count new</a>";
840            }
841            if($AddedSym) {
842                $SYS_REPORT.="<td class='new'>$AddedSym</td>\n";
843            }
844            else {
845                $SYS_REPORT.="<td class='passed'>0</td>\n";
846            }
847            my $RemovedSym="";
848            if(my $Count = $TestResults{$LName}{"Binary"}{"removed"}) {
849                $RemovedSym="<a href='$BinCompatReport\#Removed'>$Count removed</a>";
850            }
851            if($RemovedSym) {
852                $SYS_REPORT.="<td class='failed'>$RemovedSym</td>\n";
853            }
854            else {
855                $SYS_REPORT.="<td class='passed'>0</td>\n";
856            }
857        }
858        elsif($In::Opt{"SrcOnly"})
859        {
860            my $AddedSym="";
861            if(my $Count = $TestResults{$LName}{"Source"}{"added"}) {
862                $AddedSym="<a href='$SrcCompatReport\#Added'>$Count new</a>";
863            }
864            if($AddedSym) {
865                $SYS_REPORT.="<td class='new'>$AddedSym</td>\n";
866            }
867            else {
868                $SYS_REPORT.="<td class='passed'>0</td>\n";
869            }
870            my $RemovedSym="";
871            if(my $Count = $TestResults{$LName}{"Source"}{"removed"}) {
872                $RemovedSym="<a href='$SrcCompatReport\#Removed'>$Count removed</a>";
873            }
874            if($RemovedSym) {
875                $SYS_REPORT.="<td class='failed'>$RemovedSym</td>\n";
876            }
877            else {
878                $SYS_REPORT.="<td class='passed'>0</td>\n";
879            }
880        }
881
882        if(-d $HDiff)
883        {
884            if(-d $HDiff."/".$LName) {
885                $SYS_REPORT .= "<td><a href=\'headers_diff/$LName/diff.html\'>diff</a></td>\n";
886            }
887            elsif(defined $Added{$LName} or defined $Removed{$LName}) {
888                $SYS_REPORT .= "<td>N/A</td>\n";
889            }
890            else {
891                $SYS_REPORT .= "<td>Empty</td>\n";
892            }
893        }
894
895        $SYS_REPORT .= "</tr>\n";
896    }
897
898    $SYS_REPORT .= "</table>";
899
900    my $Title = "$SystemName1 vs $SystemName2 compatibility report";
901    my $Keywords = "compatibility, $SystemName1, $SystemName2, API, changes";
902    my $Description = "API compatibility report between $SystemName1 and $SystemName2 on ".showArch($ArchName);
903    my $Styles = readModule("Styles", "CmpSystems.css");
904
905    $SYS_REPORT = composeHTML_Head($Title, $Keywords, $Description, $Styles, "", 1)."\n<body>\n<div>".$SYS_REPORT."</div>\n";
906    $SYS_REPORT .= "<br/><br/>\n";
907    $SYS_REPORT .= getReportFooter();
908    $SYS_REPORT .= "</body></html>\n";
909
910    if($In::Opt{"SrcOnly"}) {
911        $SYS_REPORT = "<!-\- kind:source;".join(";", @{$META_DATA{"Source"}})." -\->\n".$SYS_REPORT;
912    }
913    if($In::Opt{"BinOnly"}) {
914        $SYS_REPORT = "<!-\- kind:binary;".join(";", @{$META_DATA{"Binary"}})." -\->\n".$SYS_REPORT;
915    }
916    my $REPORT_PATH = $SYS_REPORT_PATH."/";
917    if($In::Opt{"BinOnly"} and $In::Opt{"SrcOnly"}) {
918        $REPORT_PATH .= "compat_report.html";
919    }
920    elsif($In::Opt{"BinOnly"}) {
921        $REPORT_PATH .= "abi_compat_report.html";
922    }
923    elsif($In::Opt{"SrcOnly"}) {
924        $REPORT_PATH .= "src_compat_report.html";
925    }
926    writeFile($REPORT_PATH, $SYS_REPORT);
927    printMsg("INFO", "\nSee detailed report:\n  $REPORT_PATH");
928}
929
930sub printVer($)
931{
932    if($_[0] eq "") {
933        return 0;
934    }
935    return $_[0];
936}
937
938sub getPrefix_S($)
939{
940    my $Prefix = getPrefix($_[0]);
941    if(not $Prefix or defined $NonPrefix{lc($Prefix)}) {
942        return "NONE";
943    }
944    return $Prefix;
945}
946
947sub readSysDesc($)
948{
949    my $Content = $_[0];
950
951    $Content=~s/\/\*(.|\n)+?\*\///g;
952    $Content=~s/<\!--(.|\n)+?-->//g;
953
954    $SysDesc{"Name"} = parseTag(\$Content, "name");
955
956    if(not $SysDesc{"Name"}) {
957        exitStatus("Error", "system name is not specified (<name> section)");
958    }
959    foreach my $Path (split(/\s*\n\s*/, parseTag(\$Content, "libs")))
960    { # target libs
961        if(not -e $Path) {
962            exitStatus("Access_Error", "can't access \'$Path\'");
963        }
964        $Path = getAbsPath($Path);
965        $SysDesc{"Libs"}{$Path} = 1;
966    }
967    foreach my $Path (split(/\s*\n\s*/, parseTag(\$Content, "search_libs")))
968    { # target libs
969        if(not -d $Path) {
970            exitStatus("Access_Error", "can't access directory \'$Path\'");
971        }
972        $Path = getAbsPath($Path);
973        $SysDesc{"SearchLibs"}{$Path} = 1;
974    }
975    foreach my $Path (split(/\s*\n\s*/, parseTag(\$Content, "skip_libs")))
976    { # skip libs
977        $SysDesc{"SkipLibs"}{$Path} = 1;
978    }
979    foreach my $Path (split(/\s*\n\s*/, parseTag(\$Content, "headers")))
980    {
981        if(not -e $Path) {
982            exitStatus("Access_Error", "can't access \'$Path\'");
983        }
984        $Path = getAbsPath($Path);
985        $SysDesc{"Headers"}{$Path} = 1;
986    }
987    foreach my $Path (split(/\s*\n\s*/, parseTag(\$Content, "search_headers")))
988    {
989        if(not -d $Path) {
990            exitStatus("Access_Error", "can't access directory \'$Path\'");
991        }
992        $Path = getAbsPath($Path);
993        $SysDesc{"SearchHeaders"}{$Path} = 1;
994    }
995    foreach my $Path (split(/\s*\n\s*/, parseTag(\$Content, "tools")))
996    {
997        if(not -d $Path) {
998            exitStatus("Access_Error", "can't access directory \'$Path\'");
999        }
1000        $Path = getAbsPath($Path);
1001        $SysDesc{"Tools"}{$Path} = 1;
1002
1003        $In::Opt{"TargetTools"}{$Path} = 1;
1004        push_U($In::Opt{"SysPaths"}{"bin"}, $Path);
1005    }
1006    foreach my $Op (split(/\s*\n\s*/, parseTag(\$Content, "gcc_options"))) {
1007        $SysDesc{"GccOpts"}{$Op} = 1;
1008    }
1009    if($SysDesc{"CrossPrefix"} = parseTag(\$Content, "cross_prefix"))
1010    { # <cross_prefix> section of XML descriptor
1011        $In::Opt{"CrossPrefix"} = $SysDesc{"CrossPrefix"};
1012    }
1013    elsif($In::Opt{"CrossPrefix"})
1014    { # -cross-prefix option
1015        $SysDesc{"CrossPrefix"} = $In::Opt{"CrossPrefix"};
1016    }
1017    $SysDesc{"Defines"} = parseTag(\$Content, "defines");
1018    if($SysDesc{"Image"} = parseTag(\$Content, "image"))
1019    { # <image>
1020      # FIXME: isn't implemented yet
1021        if(not -f $SysDesc{"Image"}) {
1022            exitStatus("Access_Error", "can't access \'".$SysDesc{"Image"}."\'");
1023        }
1024    }
1025}
1026
1027sub readSysDesc_P($)
1028{
1029    my $Path = $_[0];
1030    my $Content = readFile($Path);
1031    my %Tags = (
1032        "headers" => "mf",
1033        "skip_headers" => "mf",
1034        "skip_including" => "mf",
1035        "skip_include_paths" => "mf",
1036        "skip_libs" => "mf",
1037        "include_preamble" => "mf",
1038        "add_include_paths" => "mf",
1039        "gcc_options" => "m",
1040        "skip_symbols" => "m",
1041        "skip_types" => "m",
1042        "ignore_symbols" => "h",
1043        "non_prefix" => "h",
1044        "defines" => "s",
1045        "cxx_incompatible" => "s"
1046    );
1047    my %DInfo = ();
1048    foreach my $Tag (keys(%Tags))
1049    {
1050        if(my $TContent = parseTag(\$Content, $Tag))
1051        {
1052            if($Tags{$Tag}=~/m/)
1053            { # multi-line (+order)
1054                my @Items = split(/\s*\n\s*/, $TContent);
1055                $DInfo{$Tag} = [];
1056                foreach my $Item (@Items)
1057                {
1058                    if($Tags{$Tag}=~/f/) {
1059                        $Item = pathFmt($Item);
1060                    }
1061                    push(@{$DInfo{$Tag}}, $Item);
1062                }
1063
1064            }
1065            elsif($Tags{$Tag}=~/s/)
1066            { # single element
1067                $DInfo{$Tag} = $TContent;
1068            }
1069            else
1070            { # hash array
1071                my @Items = split(/\s*\n\s*/, $TContent);
1072                foreach my $Item (@Items) {
1073                    $DInfo{$Tag}{$Item}=1;
1074                }
1075            }
1076        }
1077    }
1078
1079    if(defined $DInfo{"non_self_compiled"})
1080    { # support for old ABI dumps
1081        $DInfo{"skip_including"} = $DInfo{"non_self_compiled"};
1082    }
1083
1084    return \%DInfo;
1085}
1086
1087sub readSysInfo()
1088{
1089    my $TargetSysInfo = $In::Opt{"TargetSysInfo"};
1090
1091    # Library Specific Info
1092    my %SysInfo = ();
1093    if(-d $TargetSysInfo."/descriptors/")
1094    {
1095        foreach my $DPath (cmdFind($TargetSysInfo."/descriptors/","f","",1))
1096        {
1097            my $LSName = getFilename($DPath);
1098            $LSName=~s/\.xml\Z//;
1099            $SysInfo{$LSName} = readSysDesc_P($DPath);
1100        }
1101    }
1102    else {
1103        printMsg("WARNING", "can't find \'$TargetSysInfo/descriptors\'");
1104    }
1105
1106    # Exceptions
1107    if(checkGcc("4.4"))
1108    { # exception for libstdc++
1109        $SysInfo{"libstdc++"}{"gcc_options"} = ["-std=c++0x"];
1110    }
1111    if($In::Opt{"Target"} eq "symbian")
1112    { # exception for libstdcpp
1113        $SysInfo{"libstdcpp"}{"defines"} = "namespace std { struct nothrow_t {}; }";
1114    }
1115    if($SysDesc{"Name"}=~/maemo/i)
1116    { # GL/gl.h: No such file
1117        $SysInfo{"libSDL"}{"skip_headers"}=["SDL_opengl.h"];
1118    }
1119
1120    # Common Info
1121    my $SysCInfo = {};
1122    if(-f $TargetSysInfo."/common.xml") {
1123        $SysCInfo = readSysDesc_P($TargetSysInfo."/common.xml");
1124    }
1125    else {
1126        printMsg("Module_Error", "can't find \'$TargetSysInfo/common.xml\'");
1127    }
1128
1129    my @CompilerOpts = ();
1130    if($SysDesc{"Name"}=~/maemo|meego/i) {
1131        push(@CompilerOpts, "-DMAEMO_CHANGES", "-DM_APPLICATION_NAME=\\\"app\\\"");
1132    }
1133    if(my @Opts = keys(%{$SysDesc{"GccOpts"}})) {
1134        push(@CompilerOpts, @Opts);
1135    }
1136    if(@CompilerOpts)
1137    {
1138        if(not $SysCInfo->{"gcc_options"}) {
1139            $SysCInfo->{"gcc_options"} = [];
1140        }
1141        push(@{$SysCInfo->{"gcc_options"}}, @CompilerOpts);
1142    }
1143    return (\%SysInfo, $SysCInfo);
1144}
1145
1146sub dumpSystem()
1147{ # -dump-system option handler
1148  # should be used with -sysroot and -cross-gcc options
1149    my $TmpDir = $In::Opt{"Tmp"};
1150    my $LibExt = $In::Opt{"Ext"};
1151
1152    my $SysName_P = $SysDesc{"Name"};
1153    $SysName_P=~s/ /_/g;
1154
1155    my $SystemRoot = $In::Opt{"SystemRoot"};
1156
1157    my $SYS_DUMP_PATH = "sys_dumps/".$SysName_P."/".getArch_GCC(1);
1158    if(not $In::Opt{"TargetLib"}) {
1159        rmtree($SYS_DUMP_PATH);
1160    }
1161    my (@SystemLibs, @SysHeaders) = ();
1162
1163    foreach my $Path (keys(%{$SysDesc{"Libs"}}))
1164    {
1165        if(not -e $Path) {
1166            exitStatus("Access_Error", "can't access \'$Path\'");
1167        }
1168        if(-d $Path)
1169        {
1170            if(my @SubLibs = findLibs($Path,"",1)) {
1171                push(@SystemLibs, @SubLibs);
1172            }
1173            $SysDesc{"SearchLibs"}{$Path} = 1;
1174        }
1175        else
1176        { # single file
1177            push(@SystemLibs, $Path);
1178            $SysDesc{"SearchLibs"}{getDirname($Path)} = 1;
1179        }
1180    }
1181    foreach my $Path (keys(%{$SysDesc{"Headers"}}))
1182    {
1183        if(not -e $Path) {
1184            exitStatus("Access_Error", "can't access \'$Path\'");
1185        }
1186        if(-d $Path)
1187        {
1188            if(my @SubHeaders = cmdFind($Path,"f","","")) {
1189                push(@SysHeaders, @SubHeaders);
1190            }
1191            $SysDesc{"SearchHeaders"}{$Path}=1;
1192        }
1193        else
1194        { # single file
1195            push(@SysHeaders, $Path);
1196            $SysDesc{"SearchHeaders"}{getDirname($Path)} = 1;
1197        }
1198    }
1199    my $GroupByHeaders = 0;
1200    if($In::Opt{"CheckHeadersOnly"})
1201    { # -headers-only
1202        $GroupByHeaders = 1;
1203        # @SysHeaders = optimize_set(@SysHeaders);
1204    }
1205    elsif($SysDesc{"Image"})
1206    { # one big image
1207        $GroupByHeaders = 1;
1208        @SystemLibs = ($SysDesc{"Image"});
1209    }
1210    writeFile($SYS_DUMP_PATH."/target.txt", $In::Opt{"Target"});
1211    my (%SysLib_Symbols, %SymbolGroup, %Symbol_SysHeaders,
1212    %SysHeader_Symbols, %SysLib_SysHeaders) = ();
1213    my (%Skipped, %Failed) = ();
1214    my (%SysHeaderDir_Used, %SysHeaderDir_SysHeaders) = ();
1215    my (%SymbolCounter, %TotalLibs) = ();
1216    my (%PrefixToLib, %LibPrefix, %PrefixSymbols) = ();
1217
1218    my %Glibc = map {$_=>1} (
1219        "libc",
1220        "libpthread"
1221    );
1222    my ($SysInfo, $SysCInfo) = readSysInfo();
1223
1224    foreach (keys(%{$SysCInfo->{"non_prefix"}}))
1225    {
1226        $NonPrefix{$_} = 1;
1227        $NonPrefix{$_."_"} = 1;
1228        $NonPrefix{"_".$_} = 1;
1229        $NonPrefix{"_".$_."_"} = 1;
1230    }
1231
1232    if(not $GroupByHeaders)
1233    {
1234        if($In::Opt{"Debug"}) {
1235            printMsg("INFO", localtime(time));
1236        }
1237        printMsg("INFO", "Indexing sonames ...\n");
1238    }
1239    my (%LibSoname, %SysLibVersion) = ();
1240    my %DevelPaths = map {$_=>1} @SystemLibs;
1241    foreach my $Path (sort keys(%{$SysDesc{"SearchLibs"}}))
1242    {
1243        foreach my $LPath (findLibs($Path,"",1)) {
1244            $DevelPaths{$LPath} = 1;
1245        }
1246    }
1247    foreach my $LPath (keys(%DevelPaths))
1248    { # register SONAMEs
1249        my $LName = getFilename($LPath);
1250        if(not isTargetLib($LName)) {
1251            next;
1252        }
1253        if($In::Opt{"Target"}=~/\A(linux|macos|freebsd|solaris)\Z/
1254        and $LName!~/\Alib/) {
1255            next;
1256        }
1257        if(my $Soname = getSONAME($LPath))
1258        {
1259            if($In::Opt{"Target"} eq "symbian")
1260            {
1261                if($Soname=~/[\/\\]/)
1262                { # L://epoc32/release/armv5/lib/gfxtrans{000a0000}.dso
1263                    $Soname = getFilename($Soname);
1264                }
1265                $Soname = lc($Soname);
1266            }
1267            if(not defined $LibSoname{$LName}) {
1268                $LibSoname{$LName}=$Soname;
1269            }
1270            if(-l $LPath and my $Path = realpath_F($LPath))
1271            {
1272                my $Name = getFilename($Path);
1273                if(not defined $LibSoname{$Name}) {
1274                    $LibSoname{$Name}=$Soname;
1275                }
1276            }
1277        }
1278        else
1279        { # windows and others
1280            $LibSoname{$LName}=$LName;
1281        }
1282    }
1283    my $SONAMES = "";
1284    foreach (sort {lc($a) cmp lc($b)} keys(%LibSoname)) {
1285        $SONAMES .= $_.";".$LibSoname{$_}."\n";
1286    }
1287    if(not $GroupByHeaders) {
1288        writeFile($SYS_DUMP_PATH."/sonames.txt", $SONAMES);
1289    }
1290    foreach my $LPath (sort keys(%DevelPaths))
1291    { # register VERSIONs
1292        my $LName = getFilename($LPath);
1293        if(not isTargetLib($LName)
1294        and not isTargetLib($LibSoname{$LName})) {
1295            next;
1296        }
1297        if(my $BV = getBinVer($LPath))
1298        { # binary version
1299            $SysLibVersion{$LName} = $BV;
1300        }
1301        elsif(my $PV = libPart($LName, "version"))
1302        { # source version
1303            $SysLibVersion{$LName} = $PV;
1304        }
1305        elsif(my $SV = libPart(getSONAME($LPath), "version"))
1306        { # soname version
1307            $SysLibVersion{$LName} = $SV;
1308        }
1309        elsif($LName=~/(\d[\d\.\-\_]*)\.$LibExt\Z/)
1310        { # libfreebl3.so
1311            if($1 ne 32 and $1 ne 64) {
1312                $SysLibVersion{$LName} = $1;
1313            }
1314        }
1315    }
1316    my $VERSIONS = "";
1317    foreach (sort {lc($a) cmp lc($b)} keys(%SysLibVersion)) {
1318        $VERSIONS .= $_.";".$SysLibVersion{$_}."\n";
1319    }
1320    if(not $GroupByHeaders) {
1321        writeFile($SYS_DUMP_PATH."/versions.txt", $VERSIONS);
1322    }
1323
1324    # create target list
1325    my @SkipLibs = keys(%{$SysDesc{"SkipLibs"}});
1326    if(my $CSkip = $SysCInfo->{"skip_libs"}) {
1327        push(@SkipLibs, @{$CSkip});
1328    }
1329    if(@SkipLibs and not $In::Opt{"TargetLib"})
1330    {
1331        my %SkipLibs = map {$_ => 1} @SkipLibs;
1332        my @Target = ();
1333        foreach my $LPath (@SystemLibs)
1334        {
1335            my $LName = getFilename($LPath);
1336            my $LName_Short = libPart($LName, "name+ext");
1337            if(not defined $SkipLibs{$LName_Short}
1338            and not defined $SkipLibs{$LName}
1339            and not checkList($LPath, \@SkipLibs)) {
1340                push(@Target, $LName);
1341            }
1342        }
1343        addTargetLibs(\@Target);
1344    }
1345
1346    my %SysLibs = ();
1347    foreach my $LPath (sort @SystemLibs)
1348    {
1349        my $LName = getFilename($LPath);
1350        my $LSName = libPart($LName, "short");
1351        my $LRelPath = cutPrefix($LPath, $SystemRoot);
1352        if(not isTargetLib($LName)) {
1353            next;
1354        }
1355        if($In::Opt{"Target"}=~/\A(linux|macos|freebsd|solaris)\Z/
1356        and $LName!~/\Alib/) {
1357            next;
1358        }
1359        if($In::Opt{"Target"} eq "symbian")
1360        {
1361            if(my $V = libPart($LName, "version"))
1362            { # skip qtcore.dso
1363              # register qtcore{00040604}.dso
1364                delete($SysLibs{getDirname($LPath)."\\".$LSName.".".$LibExt});
1365                my $MV = libPart($LibSoname{$LSName.".".$LibExt}, "version");
1366                if($MV and $V ne $MV)
1367                { # skip other versions:
1368                  #  qtcore{00040700}.dso
1369                  #  qtcore{00040702}.dso
1370                    next;
1371                }
1372            }
1373        }
1374        if(-l $LPath)
1375        { # symlinks
1376            if(my $Path = realpath_F($LPath)) {
1377                $SysLibs{$Path} = 1;
1378            }
1379        }
1380        elsif(-f $LPath)
1381        {
1382            if($Glibc{$LSName}
1383            and -T $LPath)
1384            { # GNU ld scripts (libc.so, libpthread.so)
1385                my @Candidates = cmdFind($SystemRoot."/lib","",$LSName.".".$LibExt."*","1");
1386                if(@Candidates)
1387                {
1388                    my $Candidate = $Candidates[0];
1389                    if(-l $Candidate
1390                    and my $Path = realpath_F($Candidate)) {
1391                        $Candidate = $Path;
1392                    }
1393                    $SysLibs{$Candidate} = 1;
1394                }
1395            }
1396            else {
1397                $SysLibs{$LPath} = 1;
1398            }
1399        }
1400    }
1401    @SystemLibs = (); # clear memory
1402
1403    if(not keys(%SysLibs)) {
1404        exitStatus("Error", "can't find libraries");
1405    }
1406
1407    if(not $In::Opt{"CheckHeadersOnly"})
1408    {
1409        if($In::Opt{"Debug"}) {
1410            printMsg("INFO", localtime(time));
1411        }
1412        if($SysDesc{"Image"}) {
1413            printMsg("INFO", "Reading symbols from image ...\n");
1414        }
1415        else {
1416            printMsg("INFO", "Reading symbols from libraries ...\n");
1417        }
1418    }
1419
1420    my %Syms = ();
1421    my @AllSyms = {};
1422    my %ShortestNames = ();
1423
1424    foreach my $LPath (sort {lc($a) cmp lc($b)} keys(%SysLibs))
1425    {
1426        my $LRelPath = cutPrefix($LPath, $SystemRoot);
1427        my $LName = getFilename($LPath);
1428
1429        $ShortestNames{$LPath} = libPart($LName, "shortest");
1430
1431        my $Res = readSymbols_Lib(1, $LPath, 0, "-Weak", 0, 0);
1432
1433        if(not keys(%{$Res}) and $In::Opt{"TargetLib"}) {
1434            exitStatus("Error", "can't find exported symbols in the library");
1435        }
1436
1437        $Syms{$LPath} = $Res->{$LName};
1438        push(@AllSyms, keys(%{$Syms{$LPath}}));
1439    }
1440
1441    my $Translate = translateSymbols(@AllSyms, 1);
1442
1443    my %DupSymbols = ();
1444
1445    foreach my $LPath (sort {lc($a) cmp lc($b)} keys(%SysLibs))
1446    {
1447        my $LRelPath = cutPrefix($LPath, $SystemRoot);
1448        my $LName = getFilename($LPath);
1449        foreach my $Symbol (keys(%{$Syms{$LPath}}))
1450        {
1451            $Symbol=~s/[\@\$]+(.*)\Z//g;
1452            if($Symbol=~/\A(_Z|\?)/)
1453            {
1454                if(isPrivateData($Symbol)) {
1455                    next;
1456                }
1457                if($Symbol=~/(C1|C2|D0|D1|D2)E/)
1458                { # do NOT analyze constructors
1459                  # and destructors
1460                    next;
1461                }
1462                my $Unmangled = $Translate->{$Symbol};
1463                $Unmangled=~s/<.+>//g;
1464                if($Unmangled=~/\A([\w:]+)/)
1465                { # cut out the parameters
1466                    my @Elems = split(/::/, $1);
1467                    my ($Class, $Short) = ("", "");
1468                    $Short = $Elems[$#Elems];
1469                    if($#Elems>=1)
1470                    {
1471                        $Class = $Elems[$#Elems-1];
1472                        pop(@Elems);
1473                    }
1474                    # the short and class name should be
1475                    # matched in one header file
1476                    $SymbolGroup{$LRelPath}{$Class} = $Short;
1477                    foreach my $Sym (@Elems)
1478                    {
1479                        if($SysCInfo->{"ignore_symbols"}{$Symbol})
1480                        { # do NOT match this symbol
1481                            next;
1482                        }
1483                        $SysLib_Symbols{$LPath}{$Sym} = 1;
1484                        if(my $Prefix = getPrefix_S($Sym))
1485                        {
1486                            $PrefixToLib{$Prefix}{$LName} += 1;
1487                            $LibPrefix{$LPath}{$Prefix} += 1;
1488                            $PrefixSymbols{$LPath}{$Prefix}{$Sym} = 1;
1489                        }
1490                        $SymbolCounter{$Sym}{$LPath} = 1;
1491
1492                        if(my @Libs = keys(%{$SymbolCounter{$Sym}}))
1493                        {
1494                            if($#Libs>=1)
1495                            {
1496                                foreach (@Libs) {
1497                                    $DupSymbols{$_}{$Sym} = 1;
1498                                }
1499                            }
1500                        }
1501                    }
1502                }
1503            }
1504            else
1505            {
1506                if($SysCInfo->{"ignore_symbols"}{$Symbol})
1507                { # do NOT match this symbol
1508                    next;
1509                }
1510                $SysLib_Symbols{$LPath}{$Symbol} = 1;
1511                if(my $Prefix = getPrefix_S($Symbol))
1512                {
1513                    $PrefixToLib{$Prefix}{$LName} += 1;
1514                    $LibPrefix{$LPath}{$Prefix} += 1;
1515                    $PrefixSymbols{$LPath}{$Prefix}{$Symbol} = 1;
1516                }
1517                $SymbolCounter{$Symbol}{$LPath} = 1;
1518
1519                if(my @Libs = keys(%{$SymbolCounter{$Symbol}}))
1520                {
1521                    if($#Libs>=1)
1522                    {
1523                        foreach (@Libs) {
1524                            $DupSymbols{$_}{$Symbol} = 1;
1525                        }
1526                    }
1527                }
1528            }
1529        }
1530    }
1531
1532    %Syms = ();
1533    %{$Translate} = ();
1534
1535    # remove minor symbols
1536    foreach my $LPath (keys(%SysLib_Symbols))
1537    {
1538        my $SName = $ShortestNames{$LPath};
1539        my $Count = keys(%{$SysLib_Symbols{$LPath}});
1540        my %Prefixes = %{$LibPrefix{$LPath}};
1541        my @Prefixes = sort {$Prefixes{$b}<=>$Prefixes{$a}} keys(%Prefixes);
1542
1543        if($#Prefixes>=1)
1544        {
1545            my $MaxPrefix = $Prefixes[0];
1546            if($MaxPrefix eq "NONE") {
1547                $MaxPrefix = $Prefixes[1];
1548            }
1549            my $Max = $Prefixes{$MaxPrefix};
1550            my $None = $Prefixes{"NONE"};
1551
1552            next if($None*100/$Count>=50);
1553            next if($None>=$Max);
1554
1555            foreach my $Prefix (@Prefixes)
1556            {
1557                next if($Prefix eq $MaxPrefix);
1558                my $Num = $Prefixes{$Prefix};
1559                my $Rm = 0;
1560
1561                if($Prefix eq "NONE") {
1562                    $Rm = 1;
1563                }
1564                else
1565                {
1566                    if($Num*100/$Max<5) {
1567                        $Rm = 1;
1568                    }
1569                }
1570
1571                if($Rm)
1572                {
1573                    next if($Prefix=~/\Q$MaxPrefix\E/i);
1574                    next if($MaxPrefix=~/\Q$Prefix\E/i);
1575                    next if($Prefix=~/\Q$SName\E/i);
1576
1577                    foreach my $Symbol (keys(%{$PrefixSymbols{$LPath}{$Prefix}})) {
1578                        delete($SysLib_Symbols{$LPath}{$Symbol});
1579                    }
1580                }
1581            }
1582        }
1583    }
1584
1585    %PrefixSymbols = (); # free memory
1586
1587    if(not $In::Opt{"CheckHeadersOnly"}) {
1588        writeFile($SYS_DUMP_PATH."/debug/symbols.txt", Dumper(\%SysLib_Symbols));
1589    }
1590
1591    my (%DupLibs, %VersionedLibs) = ();
1592    foreach my $LPath (sort keys(%DupSymbols))
1593    { # match duplicated libs
1594      # libmenu contains all symbols from libmenuw
1595        my @Syms = keys(%{$SysLib_Symbols{$LPath}});
1596        next if($#Syms==-1);
1597        if($#Syms+1==keys(%{$DupSymbols{$LPath}})) {
1598            $DupLibs{$LPath} = 1;
1599        }
1600    }
1601    foreach my $Prefix (keys(%PrefixToLib))
1602    {
1603        my @Libs = keys(%{$PrefixToLib{$Prefix}});
1604        @Libs = sort {$PrefixToLib{$Prefix}{$b}<=>$PrefixToLib{$Prefix}{$a}} @Libs;
1605        $PrefixToLib{$Prefix} = $Libs[0];
1606    }
1607
1608    my %PackageFile = (); # to improve results
1609    my %FilePackage = ();
1610    my %LibraryFile = ();
1611
1612    if(0)
1613    {
1614        if($In::Opt{"Debug"}) {
1615            printMsg("INFO", localtime(time));
1616        }
1617        printMsg("INFO", "Reading info from packages ...\n");
1618        if(my $Urpmf = getCmdPath("urpmf"))
1619        { # Mandriva, ROSA
1620            my $Out = $TmpDir."/urpmf.out";
1621            system("urpmf : >\"$Out\"");
1622            open(FILE, $Out);
1623            while(<FILE>)
1624            {
1625                chomp($_);
1626                if(my $M = index($_, ":"))
1627                {
1628                    my $Pkg = substr($_, 0, $M);
1629                    my $File = substr($_, $M+1);
1630                    $PackageFile{$Pkg}{$File} = 1;
1631                    $FilePackage{$File} = $Pkg;
1632                }
1633            }
1634            close(FILE);
1635        }
1636    }
1637
1638    if(keys(%FilePackage))
1639    {
1640        foreach my $LPath (sort {lc($a) cmp lc($b)} keys(%SysLibs))
1641        {
1642            my $LName = getFilename($LPath);
1643            my $LDir = getDirname($LPath);
1644            my $LName_Short = libPart($LName, "name+ext");
1645
1646            my $Pkg = $FilePackage{$LDir."/".$LName_Short};
1647            if(not $Pkg)
1648            {
1649                my $RPkg = $FilePackage{$LPath};
1650                if(defined $PackageFile{$RPkg."-devel"}) {
1651                    $Pkg = $RPkg."-devel";
1652                }
1653                if($RPkg=~s/[\d\.]+\Z//g)
1654                {
1655                    if(defined $PackageFile{$RPkg."-devel"}) {
1656                        $Pkg = $RPkg."-devel";
1657                    }
1658                }
1659            }
1660            if($Pkg)
1661            {
1662                foreach (keys(%{$PackageFile{$Pkg}}))
1663                {
1664                    if(index($_, "/usr/include/")==0) {
1665                        $LibraryFile{$LPath}{$_} = 1;
1666                    }
1667                }
1668            }
1669
1670            $LName_Short=~s/\.so\Z/.a/;
1671            if($Pkg = $FilePackage{$LDir."/".$LName_Short})
1672            { # headers for static library
1673                foreach (keys(%{$PackageFile{$Pkg}}))
1674                {
1675                    if(index($_, "/usr/include/")==0) {
1676                        $LibraryFile{$LPath}{$_} = 1;
1677                    }
1678                }
1679            }
1680        }
1681    }
1682
1683    my %HeaderFile_Path = ();
1684
1685    if($In::Opt{"Debug"}) {
1686        printMsg("INFO", localtime(time));
1687    }
1688    printMsg("INFO", "Reading symbols from headers ...\n");
1689    foreach my $HPath (@SysHeaders)
1690    {
1691        $HPath = pathFmt($HPath);
1692        if(isElf($HPath))
1693        { # skip ELF files
1694            next;
1695        }
1696        my $HRelPath = cutPrefix($HPath, $SystemRoot);
1697        my ($HDir, $HName) = sepPath($HRelPath);
1698        if(isNotHeader($HName))
1699        { # have a wrong extension: .gch, .in
1700            next;
1701        }
1702        if($HName=~/~\Z/)
1703        { # reserved copy
1704            next;
1705        }
1706        if(index($HRelPath, "/_gen")!=-1)
1707        { # telepathy-1.0/telepathy-glib/_gen
1708          # telepathy-1.0/libtelepathy/_gen-tp-constants-deprecated.h
1709            next;
1710        }
1711        if(index($HRelPath, "include/linux/")!=-1)
1712        { # kernel-space headers
1713            next;
1714        }
1715        if(index($HRelPath, "include/asm/")!=-1)
1716        { # asm headers
1717            next;
1718        }
1719        if(index($HRelPath, "/microb-engine/")!=-1)
1720        { # MicroB engine (Maemo 4)
1721            next;
1722        }
1723        if($HRelPath=~/\Wprivate(\W|\Z)/)
1724        { # private directories (include/tcl-private, ...)
1725            next;
1726        }
1727        if(index($HRelPath, "/lib/")!=-1)
1728        {
1729            if(not isHeaderFile($HName))
1730            { # without or with a wrong extension
1731              # under the /lib directory
1732                next;
1733            }
1734        }
1735        my $Content = readFile($HPath);
1736        $Content=~s/\/\*(.|\n)+?\*\///g;
1737        $Content=~s/\/\/.*?\n//g; # remove comments
1738        $Content=~s/#\s*define[^\n\\]*(\\\n[^\n\\]*)+\n*//g; # remove defines
1739        $Content=~s/#[^\n]*?\n//g; # remove directives
1740        $Content=~s/(\A|\n)class\s+\w+;\n//g; # remove forward declarations
1741        # FIXME: try to add preprocessing stage
1742        foreach my $Symbol (split(/\W+/, $Content))
1743        {
1744            next if(not $Symbol);
1745            $Symbol_SysHeaders{$Symbol}{$HRelPath} = 1;
1746            $SysHeader_Symbols{$HRelPath}{$Symbol} = 1;
1747        }
1748        $SysHeaderDir_SysHeaders{$HDir}{$HName} = 1;
1749        $HeaderFile_Path{getFilename($HRelPath)}{$HRelPath} = 1;
1750    }
1751
1752    # writeFile($SYS_DUMP_PATH."/debug/headers.txt", Dumper(\%SysHeader_Symbols));
1753
1754    my %SkipDHeaders = (
1755    # header files, that should be in the <skip_headers> section
1756    # but should be matched in the algorithm
1757        # MeeGo 1.2 Harmattan
1758        "libtelepathy-qt4" => ["TelepathyQt4/_gen", "client.h",
1759                        "TelepathyQt4/*-*", "debug.h", "global.h",
1760                        "properties.h", "Channel", "channel.h", "message.h"],
1761    );
1762    filterFormat(\%SkipDHeaders);
1763    if(not $GroupByHeaders)
1764    {
1765        if($In::Opt{"Debug"}) {
1766            printMsg("INFO", localtime(time));
1767        }
1768        printMsg("INFO", "Matching symbols ...\n");
1769    }
1770
1771    foreach my $LPath (sort {lc($a) cmp lc($b)} keys(%SysLibs))
1772    { # matching
1773        my $LName = getFilename($LPath);
1774    }
1775
1776    foreach my $LPath (sort {lc($a) cmp lc($b)} keys(%SysLibs))
1777    { # matching
1778        my $LName = getFilename($LPath);
1779        my $LName_Short = libPart($LName, "name");
1780        my $LRelPath = cutPrefix($LPath, $SystemRoot);
1781        my $LSName = libPart($LName, "short");
1782        my $SName = $ShortestNames{$LPath};
1783
1784        my @TryNames = (); # libX-N.so.M
1785
1786        if(my $Ver = $SysLibVersion{$LName})
1787        { # libX-N-M
1788            if($LSName."-".$Ver ne $LName_Short)
1789            {
1790                push(@TryNames, $LName_Short."-".$Ver);
1791                #while($Ver=~s/\.\d+\Z//) { # partial versions
1792                #    push(@TryNames, $LName_Short."-".$Ver);
1793                #}
1794            }
1795        }
1796        push(@TryNames, $LName_Short); # libX-N
1797        if($LSName ne $LName_Short)
1798        { # libX
1799            push(@TryNames, $LSName);
1800        }
1801
1802        if($LRelPath=~/\/debug\//)
1803        { # debug libs
1804            $Skipped{$LRelPath} = 1;
1805            next;
1806        }
1807        $TotalLibs{$LRelPath} = 1;
1808        $SysLib_SysHeaders{$LRelPath} = ();
1809
1810        my (%SymbolDirs, %SymbolFiles) = ();
1811
1812        foreach my $Symbol (sort {length($b) cmp length($a)}
1813        sort keys(%{$SysLib_Symbols{$LPath}}))
1814        {
1815            if($SysCInfo->{"ignore_symbols"}{$Symbol}) {
1816                next;
1817            }
1818            if(not $DupLibs{$LPath}
1819            and not $VersionedLibs{$LPath}
1820            and keys(%{$SymbolCounter{$Symbol}})>=2
1821            and my $Prefix = getPrefix_S($Symbol))
1822            { # duplicated symbols
1823                if($PrefixToLib{$Prefix}
1824                and $PrefixToLib{$Prefix} ne $LName
1825                and not $Glibc{$LSName}) {
1826                    next;
1827                }
1828            }
1829            if(length($Symbol)<=2) {
1830                next;
1831            }
1832            if($Symbol!~/[A-Z_0-9]/
1833            and length($Symbol)<10
1834            and keys(%{$Symbol_SysHeaders{$Symbol}})>=3)
1835            { # undistinguished symbols
1836              # FIXME: improve this filter
1837              # signalfd (libc.so)
1838              # regcomp (libpcreposix.so)
1839                next;
1840            }
1841            if($Symbol=~/\A(_M_|_Rb_|_S_)/)
1842            { # _M_insert, _Rb_tree, _S_destroy_c_locale
1843                next;
1844            }
1845            if($Symbol=~/\A[A-Z][a-z]+\Z/)
1846            { # Clone, Initialize, Skip, Unlock, Terminate, Chunk
1847                next;
1848            }
1849            if($Symbol=~/\A[A-Z][A-Z]\Z/)
1850            { #  BC, PC, UP, SP
1851                next;
1852            }
1853            if($Symbol=~/_t\Z/)
1854            { # pthread_mutex_t, wchar_t
1855                next;
1856            }
1857            my @SymHeaders = keys(%{$Symbol_SysHeaders{$Symbol}});
1858            @SymHeaders = sort {lc($a) cmp lc($b)} @SymHeaders; # sort by name
1859            @SymHeaders = sort {length(getDirname($a))<=>length(getDirname($b))} @SymHeaders; # sort by length
1860            if(length($SName)>=3)
1861            { # sort candidate headers by name
1862                @SymHeaders = sort {$b=~/\Q$SName\E/i<=>$a=~/\Q$SName\E/i} @SymHeaders;
1863            }
1864            else
1865            { # libz, libX11
1866                @SymHeaders = sort {$b=~/lib\Q$SName\E/i<=>$a=~/lib\Q$SName\E/i} @SymHeaders;
1867                @SymHeaders = sort {$b=~/\Q$SName\Elib/i<=>$a=~/\Q$SName\Elib/i} @SymHeaders;
1868            }
1869            @SymHeaders = sort {$b=~/\Q$LSName\E/i<=>$a=~/\Q$LSName\E/i} @SymHeaders;
1870            @SymHeaders = sort {$SymbolDirs{getDirname($b)}<=>$SymbolDirs{getDirname($a)}} @SymHeaders;
1871            @SymHeaders = sort {$SymbolFiles{getFilename($b)}<=>$SymbolFiles{getFilename($a)}} @SymHeaders;
1872            foreach my $HRelPath (@SymHeaders)
1873            {
1874                my $HDir = getDirname($HRelPath);
1875                my $HName = getFilename($HRelPath);
1876
1877                if(my $Group = $SymbolGroup{$LRelPath}{$Symbol})
1878                {
1879                    if(not $SysHeader_Symbols{$HRelPath}{$Group}) {
1880                        next;
1881                    }
1882                }
1883                my $Filter = 0;
1884                foreach (@TryNames)
1885                {
1886                    if(my $Filt = $SysInfo->{$_}{"headers"})
1887                    { # search for specified headers
1888                        if(not checkList($HRelPath, $Filt))
1889                        {
1890                            $Filter = 1;
1891                            last;
1892                        }
1893                    }
1894                    if(my $Filt = $SysInfo->{$_}{"skip_headers"})
1895                    { # do NOT search for some headers
1896                        if(checkList($HRelPath, $Filt))
1897                        {
1898                            $Filter = 1;
1899                            last;
1900                        }
1901                    }
1902                    if(my $Filt = $SysInfo->{$_}{"skip_including"})
1903                    { # do NOT search for some headers
1904                        if(checkList($HRelPath, $Filt))
1905                        {
1906                            $SymbolDirs{$HDir}+=1;
1907                            $SymbolFiles{$HName}+=1;
1908                            $Filter = 1;
1909                            last;
1910                        }
1911                    }
1912                }
1913                if($Filter) {
1914                    next;
1915                }
1916                if(my $Filt = $SysCInfo->{"skip_headers"})
1917                { # do NOT search for some headers
1918                    if(checkList($HRelPath, $Filt)) {
1919                        next;
1920                    }
1921                }
1922                if(my $Filt = $SysCInfo->{"skip_including"})
1923                { # do NOT search for some headers
1924                    if(checkList($HRelPath, $Filt)) {
1925                        next;
1926                    }
1927                }
1928
1929                #if(defined $LibraryFile{$LRelPath})
1930                #{ # skip wrongly matched headers
1931                #    if(not defined $LibraryFile{$LRelPath}{$HRelPath})
1932                #    {
1933                #        next;
1934                #    }
1935                #}
1936
1937                $SysLib_SysHeaders{$LRelPath}{$HRelPath} = $Symbol;
1938
1939                $SysHeaderDir_Used{$HDir}{$LName_Short} = 1;
1940                $SysHeaderDir_Used{getDirname($HDir)}{$LName_Short} = 1;
1941
1942                $SymbolDirs{$HDir} += 1;
1943                $SymbolFiles{$HName} +=1 ;
1944
1945                # select one header for one symbol
1946                last;
1947            }
1948        }
1949
1950        if(keys(%{$SysLib_Symbols{$LPath}})
1951        and not $SysInfo->{$_}{"headers"})
1952        { # try to match by name of the header
1953            if(length($SName)>=3)
1954            {
1955                my @Paths = ();
1956                foreach my $Path (keys(%{$HeaderFile_Path{$SName.".h"}}), keys(%{$HeaderFile_Path{$LSName.".h"}}))
1957                {
1958                    my $Dir = getDirname($Path);
1959                    if(defined $SymbolDirs{$Dir} or $Dir eq "/usr/include") {
1960                        push(@Paths, $Path);
1961                    }
1962                }
1963                if($#Paths==0)
1964                {
1965                    my $Path = $Paths[0];
1966                    if(not defined $SysLib_SysHeaders{$LRelPath}{$Path}) {
1967                        $SysLib_SysHeaders{$LRelPath}{$Path} = "by name ($LSName)";
1968                    }
1969                }
1970            }
1971        }
1972
1973        if(not keys(%{$SysLib_SysHeaders{$LRelPath}}))
1974        {
1975            foreach (@TryNames)
1976            {
1977                if(my $List = $SysInfo->{$_}{"headers"})
1978                {
1979                    foreach my $HName (@{$List})
1980                    {
1981                        next if($HName=~/[\*\/\\]/);
1982                        if(my $HPath = selectSystemHeader($HName, 1))
1983                        {
1984                            my $HRelPath = cutPrefix($HPath, $SystemRoot);
1985                            $SysLib_SysHeaders{$LRelPath}{$HRelPath} = "by descriptor";
1986                        }
1987                    }
1988                }
1989            }
1990        }
1991
1992        if(not keys(%{$SysLib_SysHeaders{$LRelPath}})) {
1993            $Failed{$LRelPath} = 1;
1994        }
1995    }
1996
1997    if(not $GroupByHeaders)
1998    { # matching results
1999        writeFile($SYS_DUMP_PATH."/debug/match.txt", Dumper(\%SysLib_SysHeaders));
2000        writeFile($SYS_DUMP_PATH."/debug/skipped.txt", join("\n", sort keys(%Skipped)));
2001        writeFile($SYS_DUMP_PATH."/debug/failed.txt", join("\n", sort keys(%Failed)));
2002    }
2003    (%SysLib_Symbols, %SymbolGroup, %Symbol_SysHeaders, %SysHeader_Symbols) = (); # free memory
2004    if($GroupByHeaders)
2005    {
2006        if($SysDesc{"Image"} and not $In::Opt{"CheckHeadersOnly"}) {
2007            @SysHeaders = keys(%{$SysLib_SysHeaders{$SysDesc{"Image"}}});
2008        }
2009        %SysLib_SysHeaders = ();
2010        foreach my $Path (@SysHeaders)
2011        {
2012            if(my $Skip = $SysCInfo->{"skip_headers"})
2013            { # do NOT search for some headers
2014                if(checkList($Path, $Skip)) {
2015                    next;
2016                }
2017            }
2018            if(my $Skip = $SysCInfo->{"skip_including"})
2019            { # do NOT search for some headers
2020                if(checkList($Path, $Skip)) {
2021                    next;
2022                }
2023            }
2024            $SysLib_SysHeaders{$Path}{$Path} = 1;
2025        }
2026        if($In::Opt{"CheckHeadersOnly"}) {
2027            writeFile($SYS_DUMP_PATH."/mode.txt", "headers-only");
2028        }
2029        else {
2030            writeFile($SYS_DUMP_PATH."/mode.txt", "group-by-headers");
2031        }
2032    }
2033    @SysHeaders = (); # clear memory
2034
2035    if($In::Opt{"Debug"}) {
2036        printMsg("INFO", localtime(time));
2037    }
2038    printMsg("INFO", "Generating XML descriptors ...");
2039    my %Generated = ();
2040    my %CxxIncompat_L = ();
2041    foreach my $LRelPath (keys(%SysLib_SysHeaders))
2042    {
2043        my $LName = getFilename($LRelPath);
2044        my $DPath = $SYS_DUMP_PATH."/descriptors/$LName.xml";
2045        unlink($DPath);
2046        if(my @LibHeaders = keys(%{$SysLib_SysHeaders{$LRelPath}}))
2047        {
2048            my $LSName = libPart($LName, "short");
2049            my $LName_Short = libPart($LName, "name");
2050            my $LName_Shortest = libPart($LName, "shortest");
2051            if($GroupByHeaders)
2052            { # header short name
2053                $LSName = $LName;
2054                $LSName=~s/\.(.+?)\Z//;
2055            }
2056
2057            my (%DirsHeaders, %Includes, %MainDirs) = ();
2058            foreach my $HRelPath (@LibHeaders)
2059            {
2060                my $Dir = getDirname($HRelPath);
2061                $DirsHeaders{$Dir}{$HRelPath} = 1;
2062
2063                if($Dir=~/\/\Q$LName_Shortest\E(\/|\Z)/i
2064                or $Dir=~/\/\Q$LName_Short\E(\/|\Z)/i)
2065                {
2066                    if(getFilename($Dir) ne "include")
2067                    { # except /usr/include
2068                        $MainDirs{$Dir} += 1;
2069                    }
2070                }
2071            }
2072
2073            if($#LibHeaders==0)
2074            { # one header at all
2075                $Includes{$LibHeaders[0]} = 1;
2076            }
2077            else
2078            {
2079                foreach my $Dir (keys(%DirsHeaders))
2080                {
2081                    if(keys(%MainDirs) and not defined $MainDirs{$Dir})
2082                    { # search in /X/ dir for libX headers
2083                        if(getFilename($Dir) ne "include")
2084                        { # except /usr/include
2085                            next;
2086                        }
2087                    }
2088                    my $DirPart = 0;
2089                    my $TotalHeaders = keys(%{$SysHeaderDir_SysHeaders{$Dir}});
2090                    if($TotalHeaders) {
2091                        $DirPart = (keys(%{$DirsHeaders{$Dir}})*100)/$TotalHeaders;
2092                    }
2093                    my $Neighbourhoods = keys(%{$SysHeaderDir_Used{$Dir}});
2094                    if($Neighbourhoods==1)
2095                    { # one lib in this directory
2096                        if(getFilename($Dir) ne "include"
2097                        and $DirPart>=5)
2098                        { # complete directory
2099                            $Includes{$Dir} = 1;
2100                        }
2101                        else
2102                        { # list of headers
2103                            foreach (keys(%{$DirsHeaders{$Dir}})) {
2104                                $Includes{$_} = 1;
2105                            }
2106                        }
2107                    }
2108                    elsif((keys(%{$DirsHeaders{$Dir}})*100)/($#LibHeaders+1)>5)
2109                    { # remove 5% divergence
2110                        if(getFilename($Dir) ne "include"
2111                        and $DirPart>=50)
2112                        { # complete directory if more than 50%
2113                            $Includes{$Dir} = 1;
2114                        }
2115                        else
2116                        { # list of headers
2117                            foreach (keys(%{$DirsHeaders{$Dir}})) {
2118                                $Includes{$_} = 1;
2119                            }
2120                        }
2121                    }
2122                    else
2123                    { # noise
2124                        foreach (keys(%{$DirsHeaders{$Dir}}))
2125                        { # NOTE: /usr/include/libX.h
2126                            if(/\Q$LName_Shortest\E/i) {
2127                                $Includes{$_} = 1;
2128                            }
2129                        }
2130                    }
2131                }
2132            }
2133            if($GroupByHeaders)
2134            { # one header in one ABI dump
2135                %Includes = ($LibHeaders[0] => 1);
2136            }
2137            my $LVersion = $SysLibVersion{$LName};
2138            if($LVersion)
2139            { # append by system name
2140                $LVersion .= "-".$SysDesc{"Name"};
2141            }
2142            else {
2143                $LVersion = $SysDesc{"Name"};
2144            }
2145            my @Content = ("<version>\n    $LVersion\n</version>");
2146
2147            my @IncHeaders = sort keys(%Includes);
2148
2149            # sort files up
2150            @IncHeaders = sort {$b=~/\.h\Z/<=>$a=~/\.h\Z/} @IncHeaders;
2151
2152            # sort by name
2153            @IncHeaders = sort {sortHeaders($a, $b)} @IncHeaders;
2154
2155            # sort by library name
2156            sortByWord(\@IncHeaders, libPart($LName, "shortest"));
2157
2158            if(isAbsPath($IncHeaders[0]) or -f $IncHeaders[0]) {
2159                push(@Content, "<headers>\n    ".join("\n    ", @IncHeaders)."\n</headers>");
2160            }
2161            else {
2162                push(@Content, "<headers>\n    {RELPATH}/".join("\n    {RELPATH}/", @IncHeaders)."\n</headers>");
2163            }
2164            if($GroupByHeaders)
2165            {
2166                if($SysDesc{"Image"}) {
2167                    push(@Content, "<libs>\n    ".$SysDesc{"Image"}."\n</libs>");
2168                }
2169            }
2170            else
2171            {
2172                if(isAbsPath($LRelPath) or -f $LRelPath) {
2173                    push(@Content, "<libs>\n    $LRelPath\n</libs>");
2174                }
2175                else {
2176                    push(@Content, "<libs>\n    {RELPATH}/$LRelPath\n</libs>");
2177                }
2178            }
2179
2180            # system
2181            if(my @SearchHeaders = sort keys(%{$SysDesc{"SearchHeaders"}})) {
2182                push(@Content, "<search_headers>\n    ".join("\n    ", @SearchHeaders)."\n</search_headers>");
2183            }
2184            if(my @SearchLibs = sort keys(%{$SysDesc{"SearchLibs"}})) {
2185                push(@Content, "<search_libs>\n    ".join("\n    ", @SearchLibs)."\n</search_libs>");
2186            }
2187            if(my @Tools = sort keys(%{$SysDesc{"Tools"}})) {
2188                push(@Content, "<tools>\n    ".join("\n    ", @Tools)."\n</tools>");
2189            }
2190            if(my $Prefix = $SysDesc{"CrossPrefix"}) {
2191                push(@Content, "<cross_prefix>\n    $Prefix\n</cross_prefix>");
2192            }
2193
2194            # library
2195            my (@Skip, @SkipInc, @AddIncPath, @SkipIncPath,
2196            @SkipTypes, @SkipSymb, @Preamble, @Defines, @CompilerOpts) = ();
2197
2198            my @TryNames = ();
2199            if(my $Ver = $SysLibVersion{$LName})
2200            {
2201                if($LSName."-".$Ver ne $LName_Short) {
2202                    push(@TryNames, $LName_Short."-".$Ver);
2203                }
2204            }
2205            push(@TryNames, $LName_Short);
2206            if($LSName ne $LName_Short) {
2207                push(@TryNames, $LSName);
2208            }
2209
2210            # common
2211            if(my $List = $SysCInfo->{"include_preamble"}) {
2212                push(@Preamble, @{$List});
2213            }
2214            if(my $List = $SysCInfo->{"skip_headers"}) {
2215                @Skip = (@Skip, @{$List});
2216            }
2217            if(my $List = $SysCInfo->{"skip_including"}) {
2218                @SkipInc = (@SkipInc, @{$List});
2219            }
2220            if(my $List = $SysCInfo->{"skip_symbols"}) {
2221                push(@SkipSymb, @{$List});
2222            }
2223            if(my $List = $SysCInfo->{"gcc_options"}) {
2224                push(@CompilerOpts, @{$List});
2225            }
2226            if($SysCInfo->{"defines"}) {
2227                push(@Defines, $SysCInfo->{"defines"});
2228            }
2229
2230            # particular
2231            foreach (@TryNames)
2232            {
2233                if(my $List = $SysInfo->{$_}{"include_preamble"}) {
2234                    push(@Preamble, @{$List});
2235                }
2236                if(my $List = $SysInfo->{$_}{"skip_headers"}) {
2237                    @Skip = (@Skip, @{$List});
2238                }
2239                if(my $List = $SysInfo->{$_}{"skip_including"}) {
2240                    @SkipInc = (@SkipInc, @{$List});
2241                }
2242                if(my $List = $SysInfo->{$_}{"add_include_paths"}) {
2243                    @AddIncPath = (@AddIncPath, @{$List});
2244                }
2245                if(my $List = $SysInfo->{$_}{"skip_include_paths"}) {
2246                    @SkipIncPath = (@SkipIncPath, @{$List});
2247                }
2248                if(my $List = $SysInfo->{$_}{"skip_symbols"}) {
2249                    push(@SkipSymb, @{$List});
2250                }
2251                if(my $List = $SysInfo->{$_}{"skip_types"}) {
2252                    @SkipTypes = (@SkipTypes, @{$List});
2253                }
2254                if(my $List = $SysInfo->{$_}{"gcc_options"}) {
2255                    push(@CompilerOpts, @{$List});
2256                }
2257                if(my $List = $SysInfo->{$_}{"defines"}) {
2258                    push(@Defines, $List);
2259                }
2260                if($SysInfo->{$_}{"cxx_incompatible"}) {
2261                    $CxxIncompat_L{$LName} = 1;
2262                }
2263            }
2264
2265            # common other
2266            if($LSName=~/\AlibX\w+\Z/)
2267            { # add Xlib.h for libXt, libXaw, libXext and others
2268                push(@Preamble, "Xlib.h", "X11/Intrinsic.h");
2269            }
2270            if($SkipDHeaders{$LSName}) {
2271                @SkipInc = (@SkipInc, @{$SkipDHeaders{$LSName}});
2272            }
2273            if($SysDesc{"Defines"}) {
2274                push(@Defines, $SysDesc{"Defines"});
2275            }
2276
2277            # add sections
2278            if(@Preamble) {
2279                push(@Content, "<include_preamble>\n    ".join("\n    ", @Preamble)."\n</include_preamble>");
2280            }
2281            if(@Skip) {
2282                push(@Content, "<skip_headers>\n    ".join("\n    ", @Skip)."\n</skip_headers>");
2283            }
2284            if(@SkipInc) {
2285                push(@Content, "<skip_including>\n    ".join("\n    ", @SkipInc)."\n</skip_including>");
2286            }
2287            if(@AddIncPath) {
2288                push(@Content, "<add_include_paths>\n    ".join("\n    ", @AddIncPath)."\n</add_include_paths>");
2289            }
2290            if(@SkipIncPath) {
2291                push(@Content, "<skip_include_paths>\n    ".join("\n    ", @SkipIncPath)."\n</skip_include_paths>");
2292            }
2293            if(@SkipSymb) {
2294                push(@Content, "<skip_symbols>\n    ".join("\n    ", @SkipSymb)."\n</skip_symbols>");
2295            }
2296            if(@SkipTypes) {
2297                push(@Content, "<skip_types>\n    ".join("\n    ", @SkipTypes)."\n</skip_types>");
2298            }
2299            if(@CompilerOpts) {
2300                push(@Content, "<gcc_options>\n    ".join("\n    ", @CompilerOpts)."\n</gcc_options>");
2301            }
2302            if(@Defines) {
2303                push(@Content, "<defines>\n    ".join("\n    ", @Defines)."\n</defines>");
2304            }
2305
2306            writeFile($DPath, join("\n\n", @Content));
2307            $Generated{$LRelPath} = 1;
2308
2309            # save header files to create visual diff later
2310            my $HSDir = $SYS_DUMP_PATH."/headers/".$LName;
2311            rmtree($HSDir);
2312            mkpath($HSDir);
2313            foreach my $H_P (@IncHeaders)
2314            {
2315                if(-f $H_P) {
2316                    copy($H_P, $HSDir);
2317                }
2318            }
2319        }
2320    }
2321    printMsg("INFO", "Created descriptors:     ".keys(%Generated)." ($SYS_DUMP_PATH/descriptors/)\n");
2322
2323    if($In::Opt{"Debug"}) {
2324        printMsg("INFO", localtime(time));
2325    }
2326    printMsg("INFO", "Dumping ABIs:");
2327    my %Dumped = ();
2328    my @Descriptors = cmdFind($SYS_DUMP_PATH."/descriptors","f","*.xml","1");
2329    if(-d $SYS_DUMP_PATH."/descriptors" and $#Descriptors==-1) {
2330        printMsg("ERROR", "internal problem with \'find\' utility");
2331    }
2332    foreach my $DPath (sort {lc($a) cmp lc($b)} @Descriptors)
2333    {
2334        my $DName = getFilename($DPath);
2335        my $LName = "";
2336        if($DName=~/\A(.+).xml\Z/) {
2337            $LName = $1;
2338        }
2339        else {
2340            next;
2341        }
2342        if(not isTargetLib($LName)
2343        and not isTargetLib($LibSoname{$LName})) {
2344            next;
2345        }
2346        $DPath = cutPrefix($DPath, $In::Opt{"OrigDir"});
2347        my $ACC_dump = "perl $0";
2348        if($GroupByHeaders)
2349        { # header name is going here
2350            $ACC_dump .= " -l $LName";
2351        }
2352        else {
2353            $ACC_dump .= " -l ".libPart($LName, "name");
2354        }
2355        $ACC_dump .= " -dump \"$DPath\"";
2356        if($SystemRoot)
2357        {
2358            $ACC_dump .= " -relpath \"$SystemRoot\"";
2359            $ACC_dump .= " -sysroot \"$SystemRoot\"";
2360        }
2361        my $DumpPath = "$SYS_DUMP_PATH/abi_dumps/$LName.abi";
2362        $ACC_dump .= " -dump-path \"$DumpPath\"";
2363        my $LogPath = "$SYS_DUMP_PATH/logs/$LName.txt";
2364        unlink($LogPath);
2365        $ACC_dump .= " -log-path \"$LogPath\"";
2366        if($In::Opt{"CrossGcc"}) {
2367            $ACC_dump .= " -cross-gcc \"".$In::Opt{"CrossGcc"}."\"";
2368        }
2369        if($In::Opt{"CheckHeadersOnly"}) {
2370            $ACC_dump .= " -headers-only";
2371        }
2372        if($In::Opt{"UseStaticLibs"}) {
2373            $ACC_dump .= " -static-libs";
2374        }
2375        if($GroupByHeaders) {
2376            $ACC_dump .= " -header $LName";
2377        }
2378        if($In::Opt{"NoStdInc"}
2379        or $In::Opt{"Target"}=~/windows|symbian/)
2380        { # 1. user-defined
2381          # 2. windows/minGW
2382          # 3. symbian/GCC
2383            $ACC_dump .= " -nostdinc";
2384        }
2385        if($In::Opt{"CxxIncompat"} or $CxxIncompat_L{$LName}) {
2386            $ACC_dump .= " -cxx-incompatible";
2387        }
2388        if($In::Opt{"SkipUnidentified"}) {
2389            $ACC_dump .= " -skip-unidentified";
2390        }
2391        if($In::Opt{"Quiet"})
2392        { # quiet mode
2393            $ACC_dump .= " -quiet";
2394        }
2395        if($In::Opt{"LogMode"} eq "n") {
2396            $ACC_dump .= " -logging-mode n";
2397        }
2398        elsif($In::Opt{"Quiet"}) {
2399            $ACC_dump .= " -logging-mode a";
2400        }
2401        if($In::Opt{"Debug"})
2402        { # debug mode
2403            $ACC_dump .= " -debug";
2404            printMsg("INFO", "$ACC_dump");
2405        }
2406        printMsg("INFO_C", "Dumping $LName: ");
2407        system($ACC_dump." 1>$TmpDir/null 2>$TmpDir/$LName.stderr");
2408        my $ErrCode = $?;
2409        appendFile("$SYS_DUMP_PATH/logs/$LName.txt", "The ACC parameters:\n  $ACC_dump\n");
2410        my $ErrCont = readFile("$TmpDir/$LName.stderr");
2411        if($ErrCont) {
2412            appendFile("$SYS_DUMP_PATH/logs/$LName.txt", $ErrCont);
2413        }
2414
2415        if(filterError($ErrCont))
2416        {
2417            if(getCodeError($ErrCode>>8) eq "Invalid_Dump") {
2418                printMsg("INFO", "Empty");
2419            }
2420            else {
2421                printMsg("INFO", "Errors (\'$SYS_DUMP_PATH/logs/$LName.txt\')");
2422            }
2423        }
2424        elsif(not -f $DumpPath) {
2425            printMsg("INFO", "Failed (\'$SYS_DUMP_PATH/logs/$LName.txt\')");
2426        }
2427        else
2428        {
2429            $Dumped{$LName}=1;
2430            printMsg("INFO", "Ok");
2431        }
2432    }
2433    printMsg("INFO", "\n");
2434    if(not $GroupByHeaders)
2435    { # general mode
2436        printMsg("INFO", "Total libraries:         ".keys(%TotalLibs));
2437        printMsg("INFO", "Skipped libraries:       ".keys(%Skipped)." ($SYS_DUMP_PATH/skipped.txt)");
2438        printMsg("INFO", "Failed to find headers:  ".keys(%Failed)." ($SYS_DUMP_PATH/failed.txt)");
2439    }
2440    printMsg("INFO", "Dumped ABIs:             ".keys(%Dumped)." ($SYS_DUMP_PATH/abi_dumps/)");
2441    printMsg("INFO", "The ".$SysDesc{"Name"}." system ABI has been dumped to:\n  $SYS_DUMP_PATH");
2442}
2443
2444sub filterError($)
2445{
2446    my $Error = $_[0];
2447
2448    if(not $Error) {
2449        return undef;
2450    }
2451
2452    my @Err = ();
2453    foreach my $L (split(/\n/, $Error))
2454    {
2455        if($L!~/warning:/) {
2456            push(@Err, $L);
2457        }
2458    }
2459
2460    return join("\n", @Err);
2461}
2462
2463sub getBinVer($)
2464{
2465    my $Path = $_[0];
2466    if($In::Opt{"Target"} eq "windows"
2467    and $Path=~/\.dll\Z/)
2468    { # get version of DLL using "sigcheck"
2469        my $Sigcheck = getCmdPath("sigcheck");
2470        if(not $Sigcheck) {
2471            return undef;
2472        }
2473        my $TmpDir = $In::Opt{"Tmp"};
2474        my $VInfo = `$Sigcheck -nobanner -n $Path 2>$TmpDir/null`;
2475        $VInfo=~s/\s*\(.*\)\s*//;
2476        chomp($VInfo);
2477
2478        if($VInfo eq "n/a") {
2479            $VInfo = uc($VInfo);
2480        }
2481
2482        return $VInfo;
2483    }
2484    return undef;
2485}
2486
2487return 1;
2488