########################################################################### # Module for ABI Compliance Checker to compare Operating Systems # # Copyright (C) 2009-2011 Institute for System Programming, RAS # Copyright (C) 2011-2012 Nokia Corporation and/or its subsidiary(-ies) # Copyright (C) 2011-2012 ROSA Laboratory # Copyright (C) 2012-2016 Andrey Ponomarenko's ABI Laboratory # # Written by Andrey Ponomarenko # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License or the GNU Lesser # General Public License as published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # and the GNU Lesser General Public License along with this program. # If not, see <http://www.gnu.org/licenses/>. ########################################################################### use strict; use File::Temp qw(tempdir); use Cwd qw(abs_path cwd); use Fcntl; my ($Debug, $Quiet, $LogMode, $CheckHeadersOnly, $SystemRoot, $GCC_PATH, $CrossPrefix, $TargetSysInfo, $TargetLibraryName, $CrossGcc, $UseStaticLibs, $NoStdInc, $CxxIncompat, $SkipUnidentified, $OStarget, $BinaryOnly, $SourceOnly, $DisableConstantsCheck); my $OSgroup = get_OSgroup(); my $TMP_DIR = tempdir(CLEANUP=>1); my $ORIG_DIR = cwd(); my $LIB_EXT = getLIB_EXT($OSgroup); my %SysDescriptor; my %Cache; my %NonPrefix; sub cmpSystems($$$) { # -cmp-systems option handler # should be used with -d1 and -d2 options my ($SPath1, $SPath2, $Opts) = @_; initModule($Opts); if(not $SPath1) { exitStatus("Error", "the option -d1 should be specified"); } elsif(not -d $SPath1) { exitStatus("Access_Error", "can't access directory \'".$SPath1."\'"); } elsif(not -d $SPath1."/abi_dumps") { exitStatus("Access_Error", "can't access directory \'".$SPath1."/abi_dumps\'"); } if(not $SPath2) { exitStatus("Error", "the option -d2 should be specified"); } elsif(not -d $SPath2) { exitStatus("Access_Error", "can't access directory \'".$SPath2."\'"); } elsif(not -d $SPath2."/abi_dumps") { exitStatus("Access_Error", "can't access directory \'".$SPath2."/abi_dumps\'"); } # sys_dumps/<System>/<Arch>/... my $SystemName1 = get_filename(get_dirname($SPath1)); my $SystemName2 = get_filename(get_dirname($SPath2)); my $SystemName1_P = $SystemName1; my $SystemName2_P = $SystemName2; $SystemName1=~s/_/ /g; $SystemName2=~s/_/ /g; # sys_dumps/<System>/<Arch>/... my $ArchName = get_filename($SPath1); if($ArchName ne get_filename($SPath2)) { exitStatus("Error", "can't compare systems of different CPU architecture"); } if(my $OStarget_Dump = readFile($SPath1."/target.txt")) { # change target $OStarget = $OStarget_Dump; $LIB_EXT = getLIB_EXT($OStarget); } my $GroupByHeaders = 0; if(my $Mode = readFile($SPath1."/mode.txt")) { # change mode if($Mode eq "headers-only") { # -headers-only mode $CheckHeadersOnly = 1; $GroupByHeaders = 1; } if($Mode eq "group-by-headers") { $GroupByHeaders = 1; } } my $SYS_REPORT_PATH = "sys_compat_reports/".$SystemName1_P."_to_".$SystemName2_P."/$ArchName"; rmtree($SYS_REPORT_PATH); my (%LibSoname1, %LibSoname2) = (); foreach (split(/\n/, readFile($SPath1."/sonames.txt"))) { if(my ($LFName, $Soname) = split(/;/, $_)) { if($OStarget eq "symbian") { $Soname=~s/\{.+\}//; } $LibSoname1{$LFName} = $Soname; } } foreach (split(/\n/, readFile($SPath2."/sonames.txt"))) { if(my ($LFName, $Soname) = split(/;/, $_)) { if($OStarget eq "symbian") { $Soname=~s/\{.+\}//; } $LibSoname2{$LFName} = $Soname; } } my (%LibV1, %LibV2) = (); foreach (split(/\n/, readFile($SPath1."/versions.txt"))) { if(my ($LFName, $V) = split(/;/, $_)) { $LibV1{$LFName} = $V; } } foreach (split(/\n/, readFile($SPath2."/versions.txt"))) { if(my ($LFName, $V) = split(/;/, $_)) { $LibV2{$LFName} = $V; } } my @Dumps1 = cmd_find($SPath1."/abi_dumps","f","*.abi",1); my @Dumps2 = cmd_find($SPath2."/abi_dumps","f","*.abi",1); my (%LibVers1, %LibVers2) = (); my (%ShortNames1, %ShortNames2) = (); foreach my $DPath (@Dumps1) { if(my $Name = isDump($DPath)) { my ($Soname, $V) = ($LibSoname1{$Name}, $LibV1{$Name}); if(not $V) { $V = parse_libname($Name, "version", $OStarget); } if($GroupByHeaders) { $Soname = $Name; } $LibVers1{$Soname}{$V} = $DPath; $ShortNames1{parse_libname($Soname, "short", $OStarget)}{$Soname} = 1; } } foreach my $DPath (@Dumps2) { if(my $Name = isDump($DPath)) { my ($Soname, $V) = ($LibSoname2{$Name}, $LibV2{$Name}); if(not $V) { $V = parse_libname($Name, "version", $OStarget); } if($GroupByHeaders) { $Soname = $Name; } $LibVers2{$Soname}{$V} = $DPath; $ShortNames2{parse_libname($Soname, "short", $OStarget)}{$Soname} = 1; } } my (%Added, %Removed) = (); my (%ChangedSoname, %TestResults) = (); my (%AddedShort, %RemovedShort) = (); if(not $GroupByHeaders) { my %ChangedSoname_Safe = (); foreach my $LName (sort keys(%LibSoname2)) { # libcurl.so.3 -> libcurl.so.4 (search for SONAME by the file name) # OS #1 => OS #2 if(defined $LibVers2{$LName}) { # already registered next; } my $Soname = $LibSoname2{$LName}; if(defined $LibVers2{$Soname} and defined $LibVers1{$LName}) { $LibVers2{$LName} = $LibVers2{$Soname}; $ChangedSoname_Safe{$Soname}=$LName; } } foreach my $LName (sort keys(%LibSoname1)) { # libcurl.so.3 -> libcurl.so.4 (search for SONAME by the file name) # OS #1 <= OS #2 if(defined $LibVers1{$LName}) { # already registered next; } my $Soname = $LibSoname1{$LName}; if(defined $LibVers1{$Soname} and defined $LibVers2{$LName}) { $LibVers1{$LName} = $LibVers1{$Soname}; } } if(not $GroupByHeaders) { printMsg("INFO", "Checking added/removed libs"); } foreach my $LName (sort {lc($a) cmp lc($b)} keys(%LibVers1)) { # removed libs if(not is_target_lib($LName)) { next; } if(not defined $LibVers1{$LName}) { next; } my @Versions1 = keys(%{$LibVers1{$LName}}); if($#Versions1>=1) { # should be only one version next; } if(not defined $LibVers2{$LName} or not keys(%{$LibVers2{$LName}})) { # removed library if(not $LibSoname2{$LName}) { my $LSName = parse_libname($LName, "short", $OStarget); $RemovedShort{$LSName}{$LName} = 1; my $V = $Versions1[0]; $Removed{$LName}{"version"} = $V; my $ListPath = "info/$LName/symbols.html"; my $FV = $SystemName1; if($V) { $FV = $V."-".$FV; } createSymbolsList($LibVers1{$LName}{$V}, $SYS_REPORT_PATH."/".$ListPath, $LName, $FV, $ArchName); $Removed{$LName}{"list"} = $ListPath; } } } foreach my $LName (sort {lc($a) cmp lc($b)} keys(%LibVers2)) { # added libs if(not is_target_lib($LName)) { next; } if(not defined $LibVers2{$LName}) { next; } my @Versions2 = keys(%{$LibVers2{$LName}}); if($#Versions2>=1) { # should be only one version next; } if($ChangedSoname_Safe{$LName}) { # changed soname but added the symbolic link for old-version library next; } if(not defined $LibVers1{$LName} or not keys(%{$LibVers1{$LName}})) { # added library if(not $LibSoname1{$LName}) { my $LSName = parse_libname($LName, "short", $OStarget); $AddedShort{$LSName}{$LName} = 1; my $V = $Versions2[0]; $Added{$LName}{"version"} = $V; my $ListPath = "info/$LName/symbols.html"; my $FV = $SystemName2; if($V) { $FV = $V."-".$FV; } createSymbolsList($LibVers2{$LName}{$V}, $SYS_REPORT_PATH."/".$ListPath, $LName, $FV, $ArchName); $Added{$LName}{"list"} = $ListPath; } } } foreach my $LSName (keys(%AddedShort)) { # changed SONAME my @AddedSonames = sort keys(%{$AddedShort{$LSName}}); next if($#AddedSonames!=0); if(defined $RemovedShort{$LSName}) { # removed old soname my @RemovedSonames = sort keys(%{$RemovedShort{$LSName}}); $ChangedSoname{$AddedSonames[0]} = $RemovedSonames[0]; $ChangedSoname{$RemovedSonames[0]} = $AddedSonames[0]; } elsif(defined $ShortNames1{$LSName}) { # saved old soname my @Sonames = sort keys(%{$ShortNames1{$LSName}}); $ChangedSoname{$AddedSonames[0]} = $Sonames[0]; $ChangedSoname{$Sonames[0]} = $AddedSonames[0]; } } } my %SONAME_Changed = (); my %SONAME_Added = (); foreach my $LName (sort {lc($a) cmp lc($b)} keys(%LibVers1)) { if(not is_target_lib($LName)) { next; } my @Versions1 = keys(%{$LibVers1{$LName}}); if(not @Versions1 or $#Versions1>=1) { # should be only one version next; } my $LV1 = $Versions1[0]; my $DPath1 = $LibVers1{$LName}{$LV1}; my @Versions2 = keys(%{$LibVers2{$LName}}); if($#Versions2>=1) { # should be only one version next; } my ($LV2, $LName2, $DPath2) = (); my $LName_Short = parse_libname($LName, "name+ext", $OStarget); if($LName2 = $ChangedSoname{$LName}) { # changed SONAME @Versions2 = keys(%{$LibVers2{$LName2}}); if(not @Versions2 or $#Versions2>=1) { next; } $LV2 = $Versions2[0]; $DPath2 = $LibVers2{$LName2}{$LV2}; if(defined $LibVers2{$LName}) { # show old soname in the table $TestResults{$LName}{"v1"} = $LV1; $TestResults{$LName}{"v2"} = $LV1; } if(defined $LibVers2{$LName}) { # do not count results $SONAME_Added{$LName_Short} = 1; } $SONAME_Changed{$LName_Short} = 1; $LName = $LName_Short; } elsif(@Versions2) { $LV2 = $Versions2[0]; $DPath2 = $LibVers2{$LName}{$LV2}; } else { # removed next; } my $ACC_compare = "perl $0 -l $LName -d1 \"$DPath1\" -d2 \"$DPath2\""; my $BinReportPath = "compat_reports/$LName/abi_compat_report.html"; my $SrcReportPath = "compat_reports/$LName/src_compat_report.html"; my $BinReportPath_Full = $SYS_REPORT_PATH."/".$BinReportPath; my $SrcReportPath_Full = $SYS_REPORT_PATH."/".$SrcReportPath; if($BinaryOnly) { $ACC_compare .= " -binary"; $ACC_compare .= " -bin-report-path \"$BinReportPath_Full\""; } if($SourceOnly) { $ACC_compare .= " -source"; $ACC_compare .= " -src-report-path \"$SrcReportPath_Full\""; } if($CheckHeadersOnly) { $ACC_compare .= " -headers-only"; } if($GroupByHeaders) { $ACC_compare .= " -component header"; } if($DisableConstantsCheck) { $ACC_compare .= " -disable-constants-check"; } $ACC_compare .= " -skip-added-constants"; $ACC_compare .= " -skip-removed-constants"; if($Quiet) { # quiet mode $ACC_compare .= " -quiet"; } if($LogMode eq "n") { $ACC_compare .= " -logging-mode n"; } elsif($Quiet) { $ACC_compare .= " -logging-mode a"; } if($Debug) { # debug mode $ACC_compare .= " -debug"; printMsg("INFO", "$ACC_compare"); } printMsg("INFO_C", "Checking $LName: "); system($ACC_compare." 1>$TMP_DIR/null 2>$TMP_DIR/$LName.stderr"); if(-s "$TMP_DIR/$LName.stderr") { my $ErrorLog = readFile("$TMP_DIR/$LName.stderr"); chomp($ErrorLog); printMsg("INFO", "Failed ($ErrorLog)"); } else { printMsg("INFO", "Ok"); if($BinaryOnly) { $TestResults{$LName}{"Binary"} = readAttributes($BinReportPath_Full, 0); $TestResults{$LName}{"Binary"}{"path"} = $BinReportPath; } if($SourceOnly) { $TestResults{$LName}{"Source"} = readAttributes($SrcReportPath_Full, 0); $TestResults{$LName}{"Source"}{"path"} = $SrcReportPath; } $TestResults{$LName}{"v1"} = $LV1; $TestResults{$LName}{"v2"} = $LV2; } my $HP1 = $SPath1."/headers/".$LName; my $HP2 = $SPath2."/headers/".$LName; if(-d $HP1 and -d $HP2 and my $RfcDiff = get_CmdPath("rfcdiff")) { my @Headers1 = cmd_find($HP1,"f"); my @Headers2 = cmd_find($HP2,"f"); my (%Files1, %Files2) = (); foreach my $P (@Headers1) { $Files1{get_filename($P)} = $P; } foreach my $P (@Headers2) { $Files2{get_filename($P)} = $P; } my $Diff = ""; foreach my $N (sort {lc($a) cmp lc($b)} keys(%Files1)) { my $Path1 = $Files1{$N}; my $Path2 = undef; if(defined $Files2{$N}) { $Path2 = $Files2{$N}; } else { next; } if(-s $Path1 == -s $Path2) { if(readFile($Path1) eq readFile($Path2)) { next; } } my $DiffOut = $TMP_DIR."/rfcdiff"; if(-e $DiffOut) { unlink($DiffOut); } my $Cmd_R = $RfcDiff." --width 80 --stdout \"$Path1\" \"$Path2\" >$DiffOut 2>/dev/null"; qx/$Cmd_R/; # execute if(-s $DiffOut) { my $Content = readFile($DiffOut); if(length($Content)<3500 and $Content=~/The files are identical|No changes|Failed to create/i) { next; } $Content=~s/<\!--(.|\n)+?-->\s*//g; $Content=~s/\A((.|\n)+<body\s*>)((.|\n)+)(<\/body>(.|\n)+)\Z/$3/; $Content=~s/(<td colspan=\"5\"[^>]*>)(.+)(<\/td>)/$1$3/; $Content=~s/(<table) /$1 class='diff_tbl' /g; $Content=~s&<td class="lineno" valign="top"></td>&&g; $Content=~s&<td class="lineno"></td>&&g; $Content=~s&<th></th>&&g; $Content=~s&<td></td>&&g; $Content=~s/(\Q$N\E)( )/$1 ($LV1-$SystemName1)$2/; $Content=~s/(\Q$N\E)( )/$1 ($LV2-$SystemName2)$2/; if($Diff) { $Diff .= "<br/><br/>\n"; } $Diff .= $Content; } } if($Diff) { my $Title = $LName.": headers diff between $LV1-$SystemName1 and $LV2-$SystemName2 versions"; my $Keywords = $LName.", header, diff"; my $Description = "Diff for header files between $LV1-$SystemName1 and $LV2-$SystemName2 versions of $LName"; my $Styles = readModule("Styles", "HeadersDiff.css"); my $Link = "This html diff was produced by <a href='http://tools.ietf.org/tools/rfcdiff/'>rfcdiff</a> 1.41."; $Diff .= "<br/>"; $Diff .= "<div style='width:100%;' align='left'>$Link</div>\n"; $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; $Diff = "<table width='100%' cellpadding='0' cellspacing='0'><tr><td>$Diff</td></tr></table>"; $Diff = composeHTML_Head($Title, $Keywords, $Description, $Styles, "")."\n<body>\n$Diff\n</body>\n</html>\n"; my $Output = $SYS_REPORT_PATH."/headers_diff/$LName"; writeFile($Output."/diff.html", $Diff); } } } my %TOTAL = (); foreach my $LName (keys(%TestResults)) { if($SONAME_Changed{$LName}) { next; } foreach my $Comp ("Binary", "Source") { if(not defined $TestResults{$LName}{$Comp}) { next; } foreach my $Kind (keys(%{$TestResults{$LName}{$Comp}})) { if($Kind=~/_problems_(high|medium|low)/) { $TOTAL{$LName}{$Comp} += $TestResults{$LName}{$Comp}{$Kind}; } } } } my %META_DATA = (); my %STAT = (); foreach my $Comp ("Binary", "Source") { $STAT{$Comp}{"total"} = keys(%TestResults) - keys(%SONAME_Changed); $STAT{$Comp}{"added"} = keys(%Added); $STAT{$Comp}{"removed"} = keys(%Removed); foreach ("added", "removed") { my $Kind = $_."_interfaces"; foreach my $LName (keys(%TestResults)) { next if($SONAME_Changed{$LName}); $STAT{$Comp}{$Kind} += $TestResults{$LName}{$Comp}{$_}; } push(@{$META_DATA{$Comp}}, $Kind.":".$STAT{$Comp}{$Kind}); } foreach my $T ("type", "interface") { foreach my $S ("high", "medium", "low") { my $Kind = $T."_problems_".$S; foreach my $LName (keys(%TestResults)) { next if($SONAME_Changed{$LName}); $STAT{$Comp}{$Kind} += $TestResults{$LName}{$Comp}{$Kind}; } push(@{$META_DATA{$Comp}}, $Kind.":".$STAT{$Comp}{$Kind}); } } foreach my $LName (keys(%TestResults)) { next if($SONAME_Changed{$LName}); foreach ("affected", "changed_constants") { $STAT{$Comp}{$_} += $TestResults{$LName}{$Comp}{$_}; } if(not defined $STAT{$Comp}{"verdict"} and $TestResults{$LName}{$Comp}{"verdict"} eq "incompatible") { $STAT{$Comp}{"verdict"} = "incompatible"; } } if(not defined $STAT{$Comp}{"verdict"}) { $STAT{$Comp}{"verdict"} = "compatible"; } if($STAT{$Comp}{"total"}) { $STAT{$Comp}{"affected"} /= $STAT{$Comp}{"total"}; } else { $STAT{$Comp}{"affected"} = 0; } $STAT{$Comp}{"affected"} = show_number($STAT{$Comp}{"affected"}); if($STAT{$Comp}{"verdict"}>1) { $STAT{$Comp}{"verdict"} = 1; } push(@{$META_DATA{$Comp}}, "changed_constants:".$STAT{$Comp}{"changed_constants"}); push(@{$META_DATA{$Comp}}, "tool_version:".get_dumpversion("perl $0")); foreach ("removed", "added", "total", "affected", "verdict") { @{$META_DATA{$Comp}} = ($_.":".$STAT{$Comp}{$_}, @{$META_DATA{$Comp}}); } } my $SONAME_Title = "SONAME"; if($OStarget eq "windows") { $SONAME_Title = "DLL"; } elsif($OStarget eq "symbian") { $SONAME_Title = "DSO"; } if($GroupByHeaders) { # show the list of headers $SONAME_Title = "Header File"; } my $SYS_REPORT = "<h1>"; if($BinaryOnly and $SourceOnly) { $SYS_REPORT .= "API compatibility"; } elsif($BinaryOnly) { $SYS_REPORT .= "Binary compatibility"; } elsif($SourceOnly) { $SYS_REPORT .= "Source compatibility"; } $SYS_REPORT .= " report between <span style='color:Blue;'>$SystemName1</span> and <span style='color:Blue;'>$SystemName2</span>"; $SYS_REPORT .= " on <span style='color:Blue;'>".showArch($ArchName)."</span>\n"; $SYS_REPORT .= "</h1>"; $SYS_REPORT .= "<br/>\n"; # legend my $LEGEND = "<table class='legend'><tr>\n"; $LEGEND .= "<td class='new' width='70px' style='text-align:left'>ADDED</td>\n"; $LEGEND .= "<td class='passed' width='70px' style='text-align:left'>COMPATIBLE</td>\n"; $LEGEND .= "</tr><tr>\n"; $LEGEND .= "<td class='warning' style='text-align:left'>WARNING</td>\n"; $LEGEND .= "<td class='failed' style='text-align:left'>INCOMPATIBLE</td>\n"; $LEGEND .= "</tr></table>\n"; $SYS_REPORT .= $LEGEND; $SYS_REPORT .= "<br/>\n"; my $Columns = 2; my $Total = (keys(%TestResults) + keys(%Added) + keys(%Removed) - keys(%SONAME_Changed)); my $HDiff = $SYS_REPORT_PATH."/headers_diff"; $SYS_REPORT .= "<table class='summary'>\n"; $SYS_REPORT .= "<tr>\n"; $SYS_REPORT .= "<th rowspan='2'>$SONAME_Title<sup>$Total</sup></th>\n"; if(not $GroupByHeaders) { $SYS_REPORT .= "<th colspan='2'>Version</th>\n"; } if($BinaryOnly and $SourceOnly) { $SYS_REPORT .= "<th colspan='2'>Compatibility</th>\n"; } else { $SYS_REPORT .= "<th rowspan='2'>Compatibility</th>\n"; } $SYS_REPORT .= "<th rowspan='2'>Added<br/>Symbols</th>\n"; $SYS_REPORT .= "<th rowspan='2'>Removed<br/>Symbols</th>\n"; if(-d $HDiff) { $SYS_REPORT .= "<th rowspan='2'>Headers<br/>Diff</th>\n"; $Columns += 1; } $SYS_REPORT .= "</tr>\n"; $SYS_REPORT .= "<tr>\n"; if(not $GroupByHeaders) { $SYS_REPORT .= "<th class='ver'>$SystemName1</th><th class='ver'>$SystemName2</th>\n"; } if($BinaryOnly and $SourceOnly) { $SYS_REPORT .= "<th>Binary</th><th>Source</th>\n"; } $SYS_REPORT .= "</tr>\n"; my %RegisteredPairs = (); foreach my $LName (sort {lc($a) cmp lc($b)} (keys(%TestResults), keys(%Added), keys(%Removed))) { next if($SONAME_Changed{$LName}); my $LName_Short = parse_libname($LName, "name+ext", $OStarget); my $Anchor = $LName; $Anchor=~s/\+/p/g; # anchor for libFLAC++ is libFLACpp $Anchor=~s/\~/-/g; # libqttracker.so.1~6 $SYS_REPORT .= "<tr>\n"; $SYS_REPORT .= "<td class='object'>$LName<a name=\'$Anchor\'></a></td>\n"; if(defined $Removed{$LName}) { $SYS_REPORT .= "<td class='failed ver'>".printVer($Removed{$LName}{"version"})."</td>\n"; } elsif(defined $Added{$LName}) { $SYS_REPORT .= "<td class='new'><a href='".$Added{$LName}{"list"}."'>added</a></td>\n"; } elsif(not $GroupByHeaders) { $SYS_REPORT .= "<td class='ver'>".printVer($TestResults{$LName}{"v1"})."</td>\n"; } my $SONAME_report = "<td colspan=\'$Columns\' rowspan='2'>\n"; if($BinaryOnly and $SourceOnly) { $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"; } elsif($BinaryOnly) { $SONAME_report .= "SONAME has been <a href='".$TestResults{$LName_Short}{"Binary"}{"path"}."'>changed</a>\n"; } elsif($SourceOnly) { $SONAME_report .= "SONAME has been <a href='".$TestResults{$LName_Short}{"Source"}{"path"}."'>changed</a>\n"; } $SONAME_report .= "</td>\n"; if(defined $Added{$LName}) { # added library $SYS_REPORT .= "<td class='new ver'>".printVer($Added{$LName}{"version"})."</td>\n"; $SYS_REPORT .= "<td class='passed'>100%</td>\n" if($BinaryOnly); $SYS_REPORT .= "<td class='passed'>100%</td>\n" if($SourceOnly); if($RegisteredPairs{$LName}) { # do nothing } elsif(my $To = $ChangedSoname{$LName}) { $RegisteredPairs{$To}=1; $SYS_REPORT .= $SONAME_report; } else { foreach (1 .. $Columns) { $SYS_REPORT .= "<td>N/A</td>\n"; # colspan='5' } } $SYS_REPORT .= "</tr>\n"; next; } elsif(defined $Removed{$LName}) { # removed library $SYS_REPORT .= "<td class='failed'><a href='".$Removed{$LName}{"list"}."'>removed</a></td>\n"; $SYS_REPORT .= "<td class='failed'>0%</td>\n" if($BinaryOnly); $SYS_REPORT .= "<td class='failed'>0%</td>\n" if($SourceOnly); if($RegisteredPairs{$LName}) { # do nothing } elsif(my $To = $ChangedSoname{$LName}) { $RegisteredPairs{$To}=1; $SYS_REPORT .= $SONAME_report; } else { foreach (1 .. $Columns) { $SYS_REPORT .= "<td>N/A</td>\n"; # colspan='5' } } $SYS_REPORT .= "</tr>\n"; next; } elsif(defined $ChangedSoname{$LName}) { # added library $SYS_REPORT .= "<td class='ver'>".printVer($TestResults{$LName}{"v2"})."</td>\n"; $SYS_REPORT .= "<td class='passed'>100%</td>\n" if($BinaryOnly); $SYS_REPORT .= "<td class='passed'>100%</td>\n" if($SourceOnly); if($RegisteredPairs{$LName}) { # do nothing } elsif(my $To = $ChangedSoname{$LName}) { $RegisteredPairs{$To}=1; $SYS_REPORT .= $SONAME_report; } else { foreach (1 .. $Columns) { $SYS_REPORT .= "<td>N/A</td>\n"; # colspan='5' } } $SYS_REPORT .= "</tr>\n"; next; } elsif(not $GroupByHeaders) { $SYS_REPORT .= "<td class='ver'>".printVer($TestResults{$LName}{"v2"})."</td>\n"; } my $BinCompatReport = $TestResults{$LName}{"Binary"}{"path"}; my $SrcCompatReport = $TestResults{$LName}{"Source"}{"path"}; if($BinaryOnly) { if($TestResults{$LName}{"Binary"}{"verdict"} eq "compatible") { my $Cl = "passed"; if($TOTAL{$LName}{"Binary"}) { $Cl = "warning"; } $SYS_REPORT .= "<td class=\'$Cl\'><a href=\'$BinCompatReport\'>100%</a></td>\n"; } else { my $Compatible = 100 - $TestResults{$LName}{"Binary"}{"affected"}; my $Cl = "incompatible"; if($Compatible>=90) { $Cl = "warning"; } elsif($Compatible>=80) { $Cl = "almost_compatible"; } $SYS_REPORT .= "<td class=\'$Cl\'><a href=\'$BinCompatReport\'>$Compatible%</a></td>\n"; } } if($SourceOnly) { if($TestResults{$LName}{"Source"}{"verdict"} eq "compatible") { my $Cl = "passed"; if($TOTAL{$LName}{"Source"}) { $Cl = "warning"; } $SYS_REPORT .= "<td class=\'$Cl\'><a href=\'$SrcCompatReport\'>100%</a></td>\n"; } else { my $Compatible = 100 - $TestResults{$LName}{"Source"}{"affected"}; my $Cl = "incompatible"; if($Compatible>=90) { $Cl = "warning"; } elsif($Compatible>=80) { $Cl = "almost_compatible"; } $SYS_REPORT .= "<td class=\'$Cl\'><a href=\'$SrcCompatReport\'>$Compatible%</a></td>\n"; } } if($BinaryOnly) { # show added/removed symbols at binary level # for joined and -binary-only reports my $AddedSym=""; if(my $Count = $TestResults{$LName}{"Binary"}{"added"}) { $AddedSym="<a href='$BinCompatReport\#Added'>$Count new</a>"; } if($AddedSym) { $SYS_REPORT.="<td class='new'>$AddedSym</td>\n"; } else { $SYS_REPORT.="<td class='passed'>0</td>\n"; } my $RemovedSym=""; if(my $Count = $TestResults{$LName}{"Binary"}{"removed"}) { $RemovedSym="<a href='$BinCompatReport\#Removed'>$Count removed</a>"; } if($RemovedSym) { $SYS_REPORT.="<td class='failed'>$RemovedSym</td>\n"; } else { $SYS_REPORT.="<td class='passed'>0</td>\n"; } } elsif($SourceOnly) { my $AddedSym=""; if(my $Count = $TestResults{$LName}{"Source"}{"added"}) { $AddedSym="<a href='$SrcCompatReport\#Added'>$Count new</a>"; } if($AddedSym) { $SYS_REPORT.="<td class='new'>$AddedSym</td>\n"; } else { $SYS_REPORT.="<td class='passed'>0</td>\n"; } my $RemovedSym=""; if(my $Count = $TestResults{$LName}{"Source"}{"removed"}) { $RemovedSym="<a href='$SrcCompatReport\#Removed'>$Count removed</a>"; } if($RemovedSym) { $SYS_REPORT.="<td class='failed'>$RemovedSym</td>\n"; } else { $SYS_REPORT.="<td class='passed'>0</td>\n"; } } if(-d $HDiff) { if(-d $HDiff."/".$LName) { $SYS_REPORT .= "<td><a href=\'headers_diff/$LName/diff.html\'>diff</a></td>\n"; } elsif(defined $Added{$LName} or defined $Removed{$LName}) { $SYS_REPORT .= "<td>N/A</td>\n"; } else { $SYS_REPORT .= "<td>Empty</td>\n"; } } $SYS_REPORT .= "</tr>\n"; } $SYS_REPORT .= "</table>"; my $Title = "$SystemName1 vs $SystemName2 compatibility report"; my $Keywords = "compatibility, $SystemName1, $SystemName2, API, changes"; my $Description = "API compatibility report between $SystemName1 and $SystemName2 on ".showArch($ArchName); my $Styles = readModule("Styles", "CmpSystems.css"); $SYS_REPORT = composeHTML_Head($Title, $Keywords, $Description, $Styles, "")."\n<body>\n<div>".$SYS_REPORT."</div>\n"; $SYS_REPORT .= "<br/><br/>\n"; $SYS_REPORT .= getReportFooter(); $SYS_REPORT .= "</body></html>\n"; if($SourceOnly) { $SYS_REPORT = "<!-\- kind:source;".join(";", @{$META_DATA{"Source"}})." -\->\n".$SYS_REPORT; } if($BinaryOnly) { $SYS_REPORT = "<!-\- kind:binary;".join(";", @{$META_DATA{"Binary"}})." -\->\n".$SYS_REPORT; } my $REPORT_PATH = $SYS_REPORT_PATH."/"; if($BinaryOnly and $SourceOnly) { $REPORT_PATH .= "compat_report.html"; } elsif($BinaryOnly) { $REPORT_PATH .= "abi_compat_report.html"; } elsif($SourceOnly) { $REPORT_PATH .= "src_compat_report.html"; } writeFile($REPORT_PATH, $SYS_REPORT); printMsg("INFO", "\nSee detailed report:\n $REPORT_PATH"); } sub printVer($) { if($_[0] eq "") { return 0; } return $_[0]; } sub getPrefix_S($) { my $Prefix = getPrefix($_[0]); if(not $Prefix or defined $NonPrefix{lc($Prefix)}) { return "NONE"; } return $Prefix; } sub problem_title($) { if($_[0]==1) { return "1 change"; } else { return $_[0]." changes"; } } sub warning_title($) { if($_[0]==1) { return "1 warning"; } else { return $_[0]." warnings"; } } sub readSystemDescriptor($) { my $Content = $_[0]; $Content=~s/\/\*(.|\n)+?\*\///g; $Content=~s/<\!--(.|\n)+?-->//g; $SysDescriptor{"Name"} = parseTag(\$Content, "name"); my @Tools = (); if(not $SysDescriptor{"Name"}) { exitStatus("Error", "system name is not specified (<name> section)"); } foreach my $Path (split(/\s*\n\s*/, parseTag(\$Content, "libs"))) { # target libs if(not -e $Path) { exitStatus("Access_Error", "can't access \'$Path\'"); } $Path = get_abs_path($Path); $Path=~s/[\/\\]+\Z//g; $SysDescriptor{"Libs"}{$Path} = 1; } foreach my $Path (split(/\s*\n\s*/, parseTag(\$Content, "search_libs"))) { # target libs if(not -d $Path) { exitStatus("Access_Error", "can't access directory \'$Path\'"); } $Path = get_abs_path($Path); $Path=~s/[\/\\]+\Z//g; $SysDescriptor{"SearchLibs"}{$Path} = 1; } foreach my $Path (split(/\s*\n\s*/, parseTag(\$Content, "skip_libs"))) { # skip libs $SysDescriptor{"SkipLibs"}{$Path} = 1; } foreach my $Path (split(/\s*\n\s*/, parseTag(\$Content, "headers"))) { if(not -e $Path) { exitStatus("Access_Error", "can't access \'$Path\'"); } $Path = get_abs_path($Path); $Path=~s/[\/\\]+\Z//g; $SysDescriptor{"Headers"}{$Path} = 1; } foreach my $Path (split(/\s*\n\s*/, parseTag(\$Content, "search_headers"))) { if(not -d $Path) { exitStatus("Access_Error", "can't access directory \'$Path\'"); } $Path = get_abs_path($Path); $Path=~s/[\/\\]+\Z//g; $SysDescriptor{"SearchHeaders"}{$Path} = 1; } foreach my $Path (split(/\s*\n\s*/, parseTag(\$Content, "tools"))) { if(not -d $Path) { exitStatus("Access_Error", "can't access directory \'$Path\'"); } $Path = get_abs_path($Path); $Path=~s/[\/\\]+\Z//g; $SysDescriptor{"Tools"}{$Path} = 1; push(@Tools, $Path); } foreach my $Path (split(/\s*\n\s*/, parseTag(\$Content, "gcc_options"))) { $Path=~s/[\/\\]+\Z//g; $SysDescriptor{"GccOpts"}{$Path} = 1; } if($SysDescriptor{"CrossPrefix"} = parseTag(\$Content, "cross_prefix")) { # <cross_prefix> section of XML descriptor $CrossPrefix = $SysDescriptor{"CrossPrefix"}; } elsif($CrossPrefix) { # -cross-prefix tool option $SysDescriptor{"CrossPrefix"} = $CrossPrefix; } $SysDescriptor{"Defines"} = parseTag(\$Content, "defines"); if($SysDescriptor{"Image"} = parseTag(\$Content, "image")) { # <image> # FIXME: isn't implemented yet if(not -f $SysDescriptor{"Image"}) { exitStatus("Access_Error", "can't access \'".$SysDescriptor{"Image"}."\'"); } } return {"Tools"=>\@Tools,"CrossPrefix"=>$CrossPrefix}; } sub initModule($) { my $S = $_[0]; $OStarget = $S->{"OStarget"}; $Debug = $S->{"Debug"}; $Quiet = $S->{"Quiet"}; $LogMode = $S->{"LogMode"}; $CheckHeadersOnly = $S->{"CheckHeadersOnly"}; $SystemRoot = $S->{"SystemRoot"}; $GCC_PATH = $S->{"GCC_PATH"}; $TargetSysInfo = $S->{"TargetSysInfo"}; $CrossPrefix = $S->{"CrossPrefix"}; $TargetLibraryName = $S->{"TargetLibraryName"}; $CrossGcc = $S->{"CrossGcc"}; $UseStaticLibs = $S->{"UseStaticLibs"}; $NoStdInc = $S->{"NoStdInc"}; $CxxIncompat = $S->{"CxxIncompat"}; $SkipUnidentified = $S->{"SkipUnidentified"}; $DisableConstantsCheck = $S->{"DisableConstantsCheck"}; $BinaryOnly = $S->{"BinaryOnly"}; $SourceOnly = $S->{"SourceOnly"}; if(not $BinaryOnly and not $SourceOnly) { # default $BinaryOnly = 1; } } sub check_list($$) { my ($Item, $Skip) = @_; return 0 if(not $Skip); foreach (@{$Skip}) { my $Pattern = $_; if(index($Pattern, "*")!=-1) { # wildcards $Pattern=~s/\*/.*/g; # to perl format if($Item=~/$Pattern/) { return 1; } } elsif(index($Pattern, "/")!=-1 or index($Pattern, "\\")!=-1) { # directory if(index($Item, $Pattern)!=-1) { return 1; } } elsif($Item eq $Pattern or get_filename($Item) eq $Pattern) { # by name return 1; } } return 0; } sub filter_format($) { my $FiltRef = $_[0]; foreach my $Entry (keys(%{$FiltRef})) { foreach my $Filt (@{$FiltRef->{$Entry}}) { if($Filt=~/[\/\\]/) { $Filt = path_format($Filt, $OSgroup); } } } } sub readSysDescriptor($) { my $Path = $_[0]; my $Content = readFile($Path); my %Tags = ( "headers" => "mf", "skip_headers" => "mf", "skip_including" => "mf", "skip_include_paths" => "mf", "skip_libs" => "mf", "include_preamble" => "mf", "add_include_paths" => "mf", "gcc_options" => "m", "skip_symbols" => "m", "skip_types" => "m", "ignore_symbols" => "h", "non_prefix" => "h", "defines" => "s", "cxx_incompatible" => "s" ); my %DInfo = (); foreach my $Tag (keys(%Tags)) { if(my $TContent = parseTag(\$Content, $Tag)) { if($Tags{$Tag}=~/m/) { # multi-line (+order) my @Items = split(/\s*\n\s*/, $TContent); $DInfo{$Tag} = []; foreach my $Item (@Items) { if($Tags{$Tag}=~/f/) { $Item = path_format($Item, $OSgroup); } push(@{$DInfo{$Tag}}, $Item); } } elsif($Tags{$Tag}=~/s/) { # single element $DInfo{$Tag} = $TContent; } else { # hash array my @Items = split(/\s*\n\s*/, $TContent); foreach my $Item (@Items) { $DInfo{$Tag}{$Item}=1; } } } } if(defined $DInfo{"non_self_compiled"}) { # support for old ABI dumps $DInfo{"skip_including"} = $DInfo{"non_self_compiled"}; } return \%DInfo; } sub readSysInfo($) { my $Target = $_[0]; if(not $TargetSysInfo) { exitStatus("Error", "system info path is not specified"); } if(not -d $TargetSysInfo) { exitStatus("Module_Error", "can't access \'$TargetSysInfo\'"); } # Library Specific Info my %SysInfo = (); if(-d $TargetSysInfo."/descriptors/") { foreach my $DPath (cmd_find($TargetSysInfo."/descriptors/","f","",1)) { my $LSName = get_filename($DPath); $LSName=~s/\.xml\Z//; $SysInfo{$LSName} = readSysDescriptor($DPath); } } else { printMsg("WARNING", "can't find \'$TargetSysInfo/descriptors\'"); } # Exceptions if(check_gcc($GCC_PATH, "4.4")) { # exception for libstdc++ $SysInfo{"libstdc++"}{"gcc_options"} = ["-std=c++0x"]; } if($OStarget eq "symbian") { # exception for libstdcpp $SysInfo{"libstdcpp"}{"defines"} = "namespace std { struct nothrow_t {}; }"; } if($SysDescriptor{"Name"}=~/maemo/i) { # GL/gl.h: No such file $SysInfo{"libSDL"}{"skip_headers"}=["SDL_opengl.h"]; } # Common Info my $SysCInfo = {}; if(-f $TargetSysInfo."/common.xml") { $SysCInfo = readSysDescriptor($TargetSysInfo."/common.xml"); } else { printMsg("Module_Error", "can't find \'$TargetSysInfo/common.xml\'"); } my @CompilerOpts = (); if($SysDescriptor{"Name"}=~/maemo|meego/i) { push(@CompilerOpts, "-DMAEMO_CHANGES", "-DM_APPLICATION_NAME=\\\"app\\\""); } if(my @Opts = keys(%{$SysDescriptor{"GccOpts"}})) { push(@CompilerOpts, @Opts); } if(@CompilerOpts) { if(not $SysCInfo->{"gcc_options"}) { $SysCInfo->{"gcc_options"} = []; } push(@{$SysCInfo->{"gcc_options"}}, @CompilerOpts); } return (\%SysInfo, $SysCInfo); } sub get_binversion($) { my $Path = $_[0]; if($OStarget eq "windows" and $LIB_EXT eq "dll") { # get version of DLL using "sigcheck" my $SigcheckCmd = get_CmdPath("sigcheck"); if(not $SigcheckCmd) { return ""; } my $VInfo = `$SigcheckCmd -nobanner -n $Path 2>$TMP_DIR/null`; $VInfo=~s/\s*\(.*\)\s*//; chomp($VInfo); if($VInfo eq "n/a") { $VInfo = uc($VInfo); } return $VInfo; } return ""; } sub readBytes($) { sysopen(FILE, $_[0], O_RDONLY); sysread(FILE, my $Header, 4); close(FILE); my @Bytes = map { sprintf('%02x', ord($_)) } split (//, $Header); return join("", @Bytes); } sub dumpSystem($) { # -dump-system option handler # should be used with -sysroot and -cross-gcc options my $Opts = $_[0]; initModule($Opts); my $SysName_P = $SysDescriptor{"Name"}; $SysName_P=~s/ /_/g; my $SYS_DUMP_PATH = "sys_dumps/".$SysName_P."/".getArch(1); if(not $TargetLibraryName) { rmtree($SYS_DUMP_PATH); } my (@SystemLibs, @SysHeaders) = (); foreach my $Path (keys(%{$SysDescriptor{"Libs"}})) { if(not -e $Path) { exitStatus("Access_Error", "can't access \'$Path\'"); } if(-d $Path) { if(my @SubLibs = find_libs($Path,"",1)) { push(@SystemLibs, @SubLibs); } $SysDescriptor{"SearchLibs"}{$Path}=1; } else { # single file push(@SystemLibs, $Path); $SysDescriptor{"SearchLibs"}{get_dirname($Path)}=1; } } foreach my $Path (keys(%{$SysDescriptor{"Headers"}})) { if(not -e $Path) { exitStatus("Access_Error", "can't access \'$Path\'"); } if(-d $Path) { if(my @SubHeaders = cmd_find($Path,"f","","")) { push(@SysHeaders, @SubHeaders); } $SysDescriptor{"SearchHeaders"}{$Path}=1; } else { # single file push(@SysHeaders, $Path); $SysDescriptor{"SearchHeaders"}{get_dirname($Path)}=1; } } my $GroupByHeaders = 0; if($CheckHeadersOnly) { # -headers-only $GroupByHeaders = 1; # @SysHeaders = optimize_set(@SysHeaders); } elsif($SysDescriptor{"Image"}) { # one big image $GroupByHeaders = 1; @SystemLibs = ($SysDescriptor{"Image"}); } writeFile($SYS_DUMP_PATH."/target.txt", $OStarget); my (%SysLib_Symbols, %SymbolGroup, %Symbol_SysHeaders, %SysHeader_Symbols, %SysLib_SysHeaders) = (); my (%Skipped, %Failed) = (); my (%SysHeaderDir_Used, %SysHeaderDir_SysHeaders) = (); my (%SymbolCounter, %TotalLibs) = (); my (%PrefixToLib, %LibPrefix, %PrefixSymbols) = (); my %Glibc = map {$_=>1} ( "libc", "libpthread" ); my ($SysInfo, $SysCInfo) = readSysInfo($OStarget); foreach (keys(%{$SysCInfo->{"non_prefix"}})) { $NonPrefix{$_} = 1; $NonPrefix{$_."_"} = 1; $NonPrefix{"_".$_} = 1; $NonPrefix{"_".$_."_"} = 1; } if(not $GroupByHeaders) { if($Debug) { printMsg("INFO", localtime(time)); } printMsg("INFO", "Indexing sonames ...\n"); } my (%LibSoname, %SysLibVersion) = (); my %DevelPaths = map {$_=>1} @SystemLibs; foreach my $Path (sort keys(%{$SysDescriptor{"SearchLibs"}})) { foreach my $LPath (find_libs($Path,"",1)) { $DevelPaths{$LPath}=1; } } foreach my $LPath (keys(%DevelPaths)) { # register SONAMEs my $LName = get_filename($LPath); if(not is_target_lib($LName)) { next; } if($OSgroup=~/\A(linux|macos|freebsd)\Z/ and $LName!~/\Alib/) { next; } if(my $Soname = getSONAME($LPath)) { if($OStarget eq "symbian") { if($Soname=~/[\/\\]/) { # L://epoc32/release/armv5/lib/gfxtrans{000a0000}.dso $Soname = get_filename($Soname); } $Soname = lc($Soname); } if(not defined $LibSoname{$LName}) { $LibSoname{$LName}=$Soname; } if(-l $LPath and my $Path = realpath_F($LPath)) { my $Name = get_filename($Path); if(not defined $LibSoname{$Name}) { $LibSoname{$Name}=$Soname; } } } else { # windows and others $LibSoname{$LName}=$LName; } } my $SONAMES = ""; foreach (sort {lc($a) cmp lc($b)} keys(%LibSoname)) { $SONAMES .= $_.";".$LibSoname{$_}."\n"; } if(not $GroupByHeaders) { writeFile($SYS_DUMP_PATH."/sonames.txt", $SONAMES); } foreach my $LPath (sort keys(%DevelPaths)) { # register VERSIONs my $LName = get_filename($LPath); if(not is_target_lib($LName) and not is_target_lib($LibSoname{$LName})) { next; } if(my $BV = get_binversion($LPath)) { # binary version $SysLibVersion{$LName} = $BV; } elsif(my $PV = parse_libname($LName, "version", $OStarget)) { # source version $SysLibVersion{$LName} = $PV; } elsif(my $SV = parse_libname(getSONAME($LPath), "version", $OStarget)) { # soname version $SysLibVersion{$LName} = $SV; } elsif($LName=~/(\d[\d\.\-\_]*)\.$LIB_EXT\Z/) { # libfreebl3.so if($1 ne 32 and $1 ne 64) { $SysLibVersion{$LName} = $1; } } } my $VERSIONS = ""; foreach (sort {lc($a) cmp lc($b)} keys(%SysLibVersion)) { $VERSIONS .= $_.";".$SysLibVersion{$_}."\n"; } if(not $GroupByHeaders) { writeFile($SYS_DUMP_PATH."/versions.txt", $VERSIONS); } # create target list my @SkipLibs = keys(%{$SysDescriptor{"SkipLibs"}}); if(my $CSkip = $SysCInfo->{"skip_libs"}) { push(@SkipLibs, @{$CSkip}); } if(@SkipLibs and not $TargetLibraryName) { my %SkipLibs = map {$_ => 1} @SkipLibs; my @Target = (); foreach my $LPath (@SystemLibs) { my $LName = get_filename($LPath); my $LName_Short = parse_libname($LName, "name+ext", $OStarget); if(not defined $SkipLibs{$LName_Short} and not defined $SkipLibs{$LName} and not check_list($LPath, \@SkipLibs)) { push(@Target, $LName); } } add_target_libs(\@Target); } my %SysLibs = (); foreach my $LPath (sort @SystemLibs) { my $LName = get_filename($LPath); my $LSName = parse_libname($LName, "short", $OStarget); my $LRelPath = cut_path_prefix($LPath, $SystemRoot); if(not is_target_lib($LName)) { next; } if($OSgroup=~/\A(linux|macos|freebsd)\Z/ and $LName!~/\Alib/) { next; } if($OStarget eq "symbian") { if(my $V = parse_libname($LName, "version", $OStarget)) { # skip qtcore.dso # register qtcore{00040604}.dso delete($SysLibs{get_dirname($LPath)."\\".$LSName.".".$LIB_EXT}); my $MV = parse_libname($LibSoname{$LSName.".".$LIB_EXT}, "version", $OStarget); if($MV and $V ne $MV) { # skip other versions: # qtcore{00040700}.dso # qtcore{00040702}.dso next; } } } if(-l $LPath) { # symlinks if(my $Path = realpath_F($LPath)) { $SysLibs{$Path} = 1; } } elsif(-f $LPath) { if($Glibc{$LSName} and cmd_file($LPath)=~/ASCII/) { # GNU ld scripts (libc.so, libpthread.so) my @Candidates = cmd_find($SystemRoot."/lib","",$LSName.".".$LIB_EXT."*","1"); if(@Candidates) { my $Candidate = $Candidates[0]; if(-l $Candidate and my $Path = realpath_F($Candidate)) { $Candidate = $Path; } $SysLibs{$Candidate} = 1; } } else { $SysLibs{$LPath} = 1; } } } @SystemLibs = (); # clear memory if(not keys(%SysLibs)) { exitStatus("Error", "can't find libraries"); } if(not $CheckHeadersOnly) { if($Debug) { printMsg("INFO", localtime(time)); } if($SysDescriptor{"Image"}) { printMsg("INFO", "Reading symbols from image ...\n"); } else { printMsg("INFO", "Reading symbols from libraries ...\n"); } } my %Syms = (); my @AllSyms = {}; my %ShortestNames = (); foreach my $LPath (sort {lc($a) cmp lc($b)} keys(%SysLibs)) { my $LRelPath = cut_path_prefix($LPath, $SystemRoot); my $LName = get_filename($LPath); $ShortestNames{$LPath} = parse_libname($LName, "shortest", $OStarget); my $Res = readSymbols_Lib(1, $LPath, 0, "-Weak", 0, 0); if(not keys(%{$Res}) and $TargetLibraryName) { exitStatus("Error", "can't find exported symbols in the library"); } $Syms{$LPath} = $Res->{$LName}; push(@AllSyms, keys(%{$Syms{$LPath}})); } my $Translate = translateSymbols(@AllSyms, 1); my %DupSymbols = (); foreach my $LPath (sort {lc($a) cmp lc($b)} keys(%SysLibs)) { my $LRelPath = cut_path_prefix($LPath, $SystemRoot); my $LName = get_filename($LPath); foreach my $Symbol (keys(%{$Syms{$LPath}})) { $Symbol=~s/[\@\$]+(.*)\Z//g; if($Symbol=~/\A(_Z|\?)/) { if(isPrivateData($Symbol)) { next; } if($Symbol=~/(C1|C2|D0|D1|D2)E/) { # do NOT analyze constructors # and destructors next; } my $Unmangled = $Translate->{$Symbol}; $Unmangled=~s/<.+>//g; if($Unmangled=~/\A([\w:]+)/) { # cut out the parameters my @Elems = split(/::/, $1); my ($Class, $Short) = ("", ""); $Short = $Elems[$#Elems]; if($#Elems>=1) { $Class = $Elems[$#Elems-1]; pop(@Elems); } # the short and class name should be # matched in one header file $SymbolGroup{$LRelPath}{$Class} = $Short; foreach my $Sym (@Elems) { if($SysCInfo->{"ignore_symbols"}{$Symbol}) { # do NOT match this symbol next; } $SysLib_Symbols{$LPath}{$Sym} = 1; if(my $Prefix = getPrefix_S($Sym)) { $PrefixToLib{$Prefix}{$LName} += 1; $LibPrefix{$LPath}{$Prefix} += 1; $PrefixSymbols{$LPath}{$Prefix}{$Sym} = 1; } $SymbolCounter{$Sym}{$LPath} = 1; if(my @Libs = keys(%{$SymbolCounter{$Sym}})) { if($#Libs>=1) { foreach (@Libs) { $DupSymbols{$_}{$Sym} = 1; } } } } } } else { if($SysCInfo->{"ignore_symbols"}{$Symbol}) { # do NOT match this symbol next; } $SysLib_Symbols{$LPath}{$Symbol} = 1; if(my $Prefix = getPrefix_S($Symbol)) { $PrefixToLib{$Prefix}{$LName} += 1; $LibPrefix{$LPath}{$Prefix} += 1; $PrefixSymbols{$LPath}{$Prefix}{$Symbol} = 1; } $SymbolCounter{$Symbol}{$LPath} = 1; if(my @Libs = keys(%{$SymbolCounter{$Symbol}})) { if($#Libs>=1) { foreach (@Libs) { $DupSymbols{$_}{$Symbol} = 1; } } } } } } %Syms = (); %{$Translate} = (); # remove minor symbols foreach my $LPath (keys(%SysLib_Symbols)) { my $SName = $ShortestNames{$LPath}; my $Count = keys(%{$SysLib_Symbols{$LPath}}); my %Prefixes = %{$LibPrefix{$LPath}}; my @Prefixes = sort {$Prefixes{$b}<=>$Prefixes{$a}} keys(%Prefixes); if($#Prefixes>=1) { my $MaxPrefix = $Prefixes[0]; if($MaxPrefix eq "NONE") { $MaxPrefix = $Prefixes[1]; } my $Max = $Prefixes{$MaxPrefix}; my $None = $Prefixes{"NONE"}; next if($None*100/$Count>=50); next if($None>=$Max); foreach my $Prefix (@Prefixes) { next if($Prefix eq $MaxPrefix); my $Num = $Prefixes{$Prefix}; my $Rm = 0; if($Prefix eq "NONE") { $Rm = 1; } else { if($Num*100/$Max<5) { $Rm = 1; } } if($Rm) { next if($Prefix=~/\Q$MaxPrefix\E/i); next if($MaxPrefix=~/\Q$Prefix\E/i); next if($Prefix=~/\Q$SName\E/i); foreach my $Symbol (keys(%{$PrefixSymbols{$LPath}{$Prefix}})) { delete($SysLib_Symbols{$LPath}{$Symbol}); } } } } } %PrefixSymbols = (); # free memory if(not $CheckHeadersOnly) { writeFile($SYS_DUMP_PATH."/debug/symbols.txt", Dumper(\%SysLib_Symbols)); } my (%DupLibs, %VersionedLibs) = (); foreach my $LPath (sort keys(%DupSymbols)) { # match duplicated libs # libmenu contains all symbols from libmenuw my @Syms = keys(%{$SysLib_Symbols{$LPath}}); next if($#Syms==-1); if($#Syms+1==keys(%{$DupSymbols{$LPath}})) { $DupLibs{$LPath} = 1; } } foreach my $Prefix (keys(%PrefixToLib)) { my @Libs = keys(%{$PrefixToLib{$Prefix}}); @Libs = sort {$PrefixToLib{$Prefix}{$b}<=>$PrefixToLib{$Prefix}{$a}} @Libs; $PrefixToLib{$Prefix} = $Libs[0]; } my %PackageFile = (); # to improve results my %FilePackage = (); my %LibraryFile = (); if(0) { if($Debug) { printMsg("INFO", localtime(time)); } printMsg("INFO", "Reading info from packages ...\n"); if(my $Urpmf = get_CmdPath("urpmf")) { # Mandriva, ROSA my $Out = $TMP_DIR."/urpmf.out"; system("urpmf : >\"$Out\""); open(FILE, $Out); while(<FILE>) { chomp($_); if(my $M = index($_, ":")) { my $Pkg = substr($_, 0, $M); my $File = substr($_, $M+1); $PackageFile{$Pkg}{$File} = 1; $FilePackage{$File} = $Pkg; } } close(FILE); } } if(keys(%FilePackage)) { foreach my $LPath (sort {lc($a) cmp lc($b)} keys(%SysLibs)) { my $LName = get_filename($LPath); my $LDir = get_dirname($LPath); my $LName_Short = parse_libname($LName, "name+ext", $OStarget); my $Pkg = $FilePackage{$LDir."/".$LName_Short}; if(not $Pkg) { my $RPkg = $FilePackage{$LPath}; if(defined $PackageFile{$RPkg."-devel"}) { $Pkg = $RPkg."-devel"; } if($RPkg=~s/[\d\.]+\Z//g) { if(defined $PackageFile{$RPkg."-devel"}) { $Pkg = $RPkg."-devel"; } } } if($Pkg) { foreach (keys(%{$PackageFile{$Pkg}})) { if(index($_, "/usr/include/")==0) { $LibraryFile{$LPath}{$_} = 1; } } } $LName_Short=~s/\.so\Z/.a/; if($Pkg = $FilePackage{$LDir."/".$LName_Short}) { # headers for static library foreach (keys(%{$PackageFile{$Pkg}})) { if(index($_, "/usr/include/")==0) { $LibraryFile{$LPath}{$_} = 1; } } } } } my %HeaderFile_Path = (); if($Debug) { printMsg("INFO", localtime(time)); } printMsg("INFO", "Reading symbols from headers ...\n"); foreach my $HPath (@SysHeaders) { $HPath = path_format($HPath, $OSgroup); if(readBytes($HPath) eq "7f454c46") { # skip ELF files next; } my $HRelPath = cut_path_prefix($HPath, $SystemRoot); my ($HDir, $HName) = separate_path($HRelPath); if(is_not_header($HName)) { # have a wrong extension: .gch, .in next; } if($HName=~/~\Z/) { # reserved copy next; } if(index($HRelPath, "/_gen")!=-1) { # telepathy-1.0/telepathy-glib/_gen # telepathy-1.0/libtelepathy/_gen-tp-constants-deprecated.h next; } if(index($HRelPath, "include/linux/")!=-1) { # kernel-space headers next; } if(index($HRelPath, "include/asm/")!=-1) { # asm headers next; } if(index($HRelPath, "/microb-engine/")!=-1) { # MicroB engine (Maemo 4) next; } if($HRelPath=~/\Wprivate(\W|\Z)/) { # private directories (include/tcl-private, ...) next; } if(index($HRelPath, "/lib/")!=-1) { if(not is_header_file($HName)) { # without or with a wrong extension # under the /lib directory next; } } my $Content = readFile($HPath); $Content=~s/\/\*(.|\n)+?\*\///g; $Content=~s/\/\/.*?\n//g; # remove comments $Content=~s/#\s*define[^\n\\]*(\\\n[^\n\\]*)+\n*//g; # remove defines $Content=~s/#[^\n]*?\n//g; # remove directives $Content=~s/(\A|\n)class\s+\w+;\n//g; # remove forward declarations # FIXME: try to add preprocessing stage foreach my $Symbol (split(/\W+/, $Content)) { next if(not $Symbol); $Symbol_SysHeaders{$Symbol}{$HRelPath} = 1; $SysHeader_Symbols{$HRelPath}{$Symbol} = 1; } $SysHeaderDir_SysHeaders{$HDir}{$HName} = 1; $HeaderFile_Path{get_filename($HRelPath)}{$HRelPath} = 1; } # writeFile($SYS_DUMP_PATH."/debug/headers.txt", Dumper(\%SysHeader_Symbols)); my %SkipDHeaders = ( # header files, that should be in the <skip_headers> section # but should be matched in the algorithm # MeeGo 1.2 Harmattan "libtelepathy-qt4" => ["TelepathyQt4/_gen", "client.h", "TelepathyQt4/*-*", "debug.h", "global.h", "properties.h", "Channel", "channel.h", "message.h"], ); filter_format(\%SkipDHeaders); if(not $GroupByHeaders) { if($Debug) { printMsg("INFO", localtime(time)); } printMsg("INFO", "Matching symbols ...\n"); } foreach my $LPath (sort {lc($a) cmp lc($b)} keys(%SysLibs)) { # matching my $LName = get_filename($LPath); } foreach my $LPath (sort {lc($a) cmp lc($b)} keys(%SysLibs)) { # matching my $LName = get_filename($LPath); my $LName_Short = parse_libname($LName, "name", $OStarget); my $LRelPath = cut_path_prefix($LPath, $SystemRoot); my $LSName = parse_libname($LName, "short", $OStarget); my $SName = $ShortestNames{$LPath}; my @TryNames = (); # libX-N.so.M if(my $Ver = $SysLibVersion{$LName}) { # libX-N-M if($LSName."-".$Ver ne $LName_Short) { push(@TryNames, $LName_Short."-".$Ver); #while($Ver=~s/\.\d+\Z//) { # partial versions # push(@TryNames, $LName_Short."-".$Ver); #} } } push(@TryNames, $LName_Short); # libX-N if($LSName ne $LName_Short) { # libX push(@TryNames, $LSName); } if($LRelPath=~/\/debug\//) { # debug libs $Skipped{$LRelPath} = 1; next; } $TotalLibs{$LRelPath} = 1; $SysLib_SysHeaders{$LRelPath} = (); my (%SymbolDirs, %SymbolFiles) = (); foreach my $Symbol (sort {length($b) cmp length($a)} sort keys(%{$SysLib_Symbols{$LPath}})) { if($SysCInfo->{"ignore_symbols"}{$Symbol}) { next; } if(not $DupLibs{$LPath} and not $VersionedLibs{$LPath} and keys(%{$SymbolCounter{$Symbol}})>=2 and my $Prefix = getPrefix_S($Symbol)) { # duplicated symbols if($PrefixToLib{$Prefix} and $PrefixToLib{$Prefix} ne $LName and not $Glibc{$LSName}) { next; } } if(length($Symbol)<=2) { next; } if($Symbol!~/[A-Z_0-9]/ and length($Symbol)<10 and keys(%{$Symbol_SysHeaders{$Symbol}})>=3) { # undistinguished symbols # FIXME: improve this filter # signalfd (libc.so) # regcomp (libpcreposix.so) next; } if($Symbol=~/\A(_M_|_Rb_|_S_)/) { # _M_insert, _Rb_tree, _S_destroy_c_locale next; } if($Symbol=~/\A[A-Z][a-z]+\Z/) { # Clone, Initialize, Skip, Unlock, Terminate, Chunk next; } if($Symbol=~/\A[A-Z][A-Z]\Z/) { # BC, PC, UP, SP next; } if($Symbol=~/_t\Z/) { # pthread_mutex_t, wchar_t next; } my @SymHeaders = keys(%{$Symbol_SysHeaders{$Symbol}}); @SymHeaders = sort {lc($a) cmp lc($b)} @SymHeaders; # sort by name @SymHeaders = sort {length(get_dirname($a))<=>length(get_dirname($b))} @SymHeaders; # sort by length if(length($SName)>=3) { # sort candidate headers by name @SymHeaders = sort {$b=~/\Q$SName\E/i<=>$a=~/\Q$SName\E/i} @SymHeaders; } else { # libz, libX11 @SymHeaders = sort {$b=~/lib\Q$SName\E/i<=>$a=~/lib\Q$SName\E/i} @SymHeaders; @SymHeaders = sort {$b=~/\Q$SName\Elib/i<=>$a=~/\Q$SName\Elib/i} @SymHeaders; } @SymHeaders = sort {$b=~/\Q$LSName\E/i<=>$a=~/\Q$LSName\E/i} @SymHeaders; @SymHeaders = sort {$SymbolDirs{get_dirname($b)}<=>$SymbolDirs{get_dirname($a)}} @SymHeaders; @SymHeaders = sort {$SymbolFiles{get_filename($b)}<=>$SymbolFiles{get_filename($a)}} @SymHeaders; foreach my $HRelPath (@SymHeaders) { my $HDir = get_dirname($HRelPath); my $HName = get_filename($HRelPath); if(my $Group = $SymbolGroup{$LRelPath}{$Symbol}) { if(not $SysHeader_Symbols{$HRelPath}{$Group}) { next; } } my $Filter = 0; foreach (@TryNames) { if(my $Filt = $SysInfo->{$_}{"headers"}) { # search for specified headers if(not check_list($HRelPath, $Filt)) { $Filter = 1; last; } } if(my $Filt = $SysInfo->{$_}{"skip_headers"}) { # do NOT search for some headers if(check_list($HRelPath, $Filt)) { $Filter = 1; last; } } if(my $Filt = $SysInfo->{$_}{"skip_including"}) { # do NOT search for some headers if(check_list($HRelPath, $Filt)) { $SymbolDirs{$HDir}+=1; $SymbolFiles{$HName}+=1; $Filter = 1; last; } } } if($Filter) { next; } if(my $Filt = $SysCInfo->{"skip_headers"}) { # do NOT search for some headers if(check_list($HRelPath, $Filt)) { next; } } if(my $Filt = $SysCInfo->{"skip_including"}) { # do NOT search for some headers if(check_list($HRelPath, $Filt)) { next; } } if(defined $LibraryFile{$LRelPath}) { # skip wrongly matched headers if(not defined $LibraryFile{$LRelPath}{$HRelPath}) { print "WRONG: $LRelPath $HRelPath\n"; # next; } } $SysLib_SysHeaders{$LRelPath}{$HRelPath} = $Symbol; $SysHeaderDir_Used{$HDir}{$LName_Short} = 1; $SysHeaderDir_Used{get_dirname($HDir)}{$LName_Short} = 1; $SymbolDirs{$HDir} += 1; $SymbolFiles{$HName} +=1 ; # select one header for one symbol last; } } if(keys(%{$SysLib_Symbols{$LPath}}) and not $SysInfo->{$_}{"headers"}) { # try to match by name of the header if(length($SName)>=3) { my @Paths = (); foreach my $Path (keys(%{$HeaderFile_Path{$SName.".h"}}), keys(%{$HeaderFile_Path{$LSName.".h"}})) { my $Dir = get_dirname($Path); if(defined $SymbolDirs{$Dir} or $Dir eq "/usr/include") { push(@Paths, $Path); } } if($#Paths==0) { my $Path = $Paths[0]; if(not defined $SysLib_SysHeaders{$LRelPath}{$Path}) { $SysLib_SysHeaders{$LRelPath}{$Path} = "by name ($LSName)"; } } } } if(not keys(%{$SysLib_SysHeaders{$LRelPath}})) { foreach (@TryNames) { if(my $List = $SysInfo->{$_}{"headers"}) { foreach my $HName (@{$List}) { next if($HName=~/[\*\/\\]/); if(my $HPath = selectSystemHeader($HName, 1)) { my $HRelPath = cut_path_prefix($HPath, $SystemRoot); $SysLib_SysHeaders{$LRelPath}{$HRelPath} = "by descriptor"; } } } } } if(not keys(%{$SysLib_SysHeaders{$LRelPath}})) { $Failed{$LRelPath} = 1; } } if(not $GroupByHeaders) { # matching results writeFile($SYS_DUMP_PATH."/debug/match.txt", Dumper(\%SysLib_SysHeaders)); writeFile($SYS_DUMP_PATH."/debug/skipped.txt", join("\n", sort keys(%Skipped))); writeFile($SYS_DUMP_PATH."/debug/failed.txt", join("\n", sort keys(%Failed))); } (%SysLib_Symbols, %SymbolGroup, %Symbol_SysHeaders, %SysHeader_Symbols) = (); # free memory if($GroupByHeaders) { if($SysDescriptor{"Image"} and not $CheckHeadersOnly) { @SysHeaders = keys(%{$SysLib_SysHeaders{$SysDescriptor{"Image"}}}); } %SysLib_SysHeaders = (); foreach my $Path (@SysHeaders) { if(my $Skip = $SysCInfo->{"skip_headers"}) { # do NOT search for some headers if(check_list($Path, $Skip)) { next; } } if(my $Skip = $SysCInfo->{"skip_including"}) { # do NOT search for some headers if(check_list($Path, $Skip)) { next; } } $SysLib_SysHeaders{$Path}{$Path} = 1; } if($CheckHeadersOnly) { writeFile($SYS_DUMP_PATH."/mode.txt", "headers-only"); } else { writeFile($SYS_DUMP_PATH."/mode.txt", "group-by-headers"); } } @SysHeaders = (); # clear memory if($Debug) { printMsg("INFO", localtime(time)); } printMsg("INFO", "Generating XML descriptors ..."); my %Generated = (); my %CxxIncompat_L = (); foreach my $LRelPath (keys(%SysLib_SysHeaders)) { my $LName = get_filename($LRelPath); my $DPath = $SYS_DUMP_PATH."/descriptors/$LName.xml"; unlink($DPath); if(my @LibHeaders = keys(%{$SysLib_SysHeaders{$LRelPath}})) { my $LSName = parse_libname($LName, "short", $OStarget); my $LName_Short = parse_libname($LName, "name", $OStarget); my $LName_Shortest = parse_libname($LName, "shortest", $OStarget); if($GroupByHeaders) { # header short name $LSName = $LName; $LSName=~s/\.(.+?)\Z//; } my (%DirsHeaders, %Includes, %MainDirs) = (); foreach my $HRelPath (@LibHeaders) { my $Dir = get_dirname($HRelPath); $DirsHeaders{$Dir}{$HRelPath} = 1; if($Dir=~/\/\Q$LName_Shortest\E(\/|\Z)/i or $Dir=~/\/\Q$LName_Short\E(\/|\Z)/i) { if(get_filename($Dir) ne "include") { # except /usr/include $MainDirs{$Dir} += 1; } } } if($#LibHeaders==0) { # one header at all $Includes{$LibHeaders[0]} = 1; } else { foreach my $Dir (keys(%DirsHeaders)) { if(keys(%MainDirs) and not defined $MainDirs{$Dir}) { # search in /X/ dir for libX headers if(get_filename($Dir) ne "include") { # except /usr/include next; } } my $DirPart = 0; my $TotalHeaders = keys(%{$SysHeaderDir_SysHeaders{$Dir}}); if($TotalHeaders) { $DirPart = (keys(%{$DirsHeaders{$Dir}})*100)/$TotalHeaders; } my $Neighbourhoods = keys(%{$SysHeaderDir_Used{$Dir}}); if($Neighbourhoods==1) { # one lib in this directory if(get_filename($Dir) ne "include" and $DirPart>=5) { # complete directory $Includes{$Dir} = 1; } else { # list of headers foreach (keys(%{$DirsHeaders{$Dir}})) { $Includes{$_} = 1; } } } elsif((keys(%{$DirsHeaders{$Dir}})*100)/($#LibHeaders+1)>5) { # remove 5% divergence if(get_filename($Dir) ne "include" and $DirPart>=50) { # complete directory if more than 50% $Includes{$Dir} = 1; } else { # list of headers foreach (keys(%{$DirsHeaders{$Dir}})) { $Includes{$_} = 1; } } } else { # noise foreach (keys(%{$DirsHeaders{$Dir}})) { # NOTE: /usr/include/libX.h if(/\Q$LName_Shortest\E/i) { $Includes{$_} = 1; } } } } } if($GroupByHeaders) { # one header in one ABI dump %Includes = ($LibHeaders[0] => 1); } my $LVersion = $SysLibVersion{$LName}; if($LVersion) { # append by system name $LVersion .= "-".$SysDescriptor{"Name"}; } else { $LVersion = $SysDescriptor{"Name"}; } my @Content = ("<version>\n $LVersion\n</version>"); my @IncHeaders = sort keys(%Includes); # sort files up @IncHeaders = sort {$b=~/\.h\Z/<=>$a=~/\.h\Z/} @IncHeaders; # sort by name @IncHeaders = sort {sortHeaders($a, $b)} @IncHeaders; # sort by library name sortByWord(\@IncHeaders, parse_libname($LName, "shortest", $OStarget)); if(is_abs($IncHeaders[0]) or -f $IncHeaders[0]) { push(@Content, "<headers>\n ".join("\n ", @IncHeaders)."\n</headers>"); } else { push(@Content, "<headers>\n {RELPATH}/".join("\n {RELPATH}/", @IncHeaders)."\n</headers>"); } if($GroupByHeaders) { if($SysDescriptor{"Image"}) { push(@Content, "<libs>\n ".$SysDescriptor{"Image"}."\n</libs>"); } } else { if(is_abs($LRelPath) or -f $LRelPath) { push(@Content, "<libs>\n $LRelPath\n</libs>"); } else { push(@Content, "<libs>\n {RELPATH}/$LRelPath\n</libs>"); } } # system if(my @SearchHeaders = sort keys(%{$SysDescriptor{"SearchHeaders"}})) { push(@Content, "<search_headers>\n ".join("\n ", @SearchHeaders)."\n</search_headers>"); } if(my @SearchLibs = sort keys(%{$SysDescriptor{"SearchLibs"}})) { push(@Content, "<search_libs>\n ".join("\n ", @SearchLibs)."\n</search_libs>"); } if(my @Tools = sort keys(%{$SysDescriptor{"Tools"}})) { push(@Content, "<tools>\n ".join("\n ", @Tools)."\n</tools>"); } if(my $Prefix = $SysDescriptor{"CrossPrefix"}) { push(@Content, "<cross_prefix>\n $Prefix\n</cross_prefix>"); } # library my (@Skip, @SkipInc, @AddIncPath, @SkipIncPath, @SkipTypes, @SkipSymb, @Preamble, @Defines, @CompilerOpts) = (); my @TryNames = (); if(my $Ver = $SysLibVersion{$LName}) { if($LSName."-".$Ver ne $LName_Short) { push(@TryNames, $LName_Short."-".$Ver); } } push(@TryNames, $LName_Short); if($LSName ne $LName_Short) { push(@TryNames, $LSName); } # common if(my $List = $SysCInfo->{"include_preamble"}) { push(@Preamble, @{$List}); } if(my $List = $SysCInfo->{"skip_headers"}) { @Skip = (@Skip, @{$List}); } if(my $List = $SysCInfo->{"skip_including"}) { @SkipInc = (@SkipInc, @{$List}); } if(my $List = $SysCInfo->{"skip_symbols"}) { push(@SkipSymb, @{$List}); } if(my $List = $SysCInfo->{"gcc_options"}) { push(@CompilerOpts, @{$List}); } if($SysCInfo->{"defines"}) { push(@Defines, $SysCInfo->{"defines"}); } # particular foreach (@TryNames) { if(my $List = $SysInfo->{$_}{"include_preamble"}) { push(@Preamble, @{$List}); } if(my $List = $SysInfo->{$_}{"skip_headers"}) { @Skip = (@Skip, @{$List}); } if(my $List = $SysInfo->{$_}{"skip_including"}) { @SkipInc = (@SkipInc, @{$List}); } if(my $List = $SysInfo->{$_}{"add_include_paths"}) { @AddIncPath = (@AddIncPath, @{$List}); } if(my $List = $SysInfo->{$_}{"skip_include_paths"}) { @SkipIncPath = (@SkipIncPath, @{$List}); } if(my $List = $SysInfo->{$_}{"skip_symbols"}) { push(@SkipSymb, @{$List}); } if(my $List = $SysInfo->{$_}{"skip_types"}) { @SkipTypes = (@SkipTypes, @{$List}); } if(my $List = $SysInfo->{$_}{"gcc_options"}) { push(@CompilerOpts, @{$List}); } if(my $List = $SysInfo->{$_}{"defines"}) { push(@Defines, $List); } if($SysInfo->{$_}{"cxx_incompatible"}) { $CxxIncompat_L{$LName} = 1; } } # common other if($LSName=~/\AlibX\w+\Z/) { # add Xlib.h for libXt, libXaw, libXext and others push(@Preamble, "Xlib.h", "X11/Intrinsic.h"); } if($SkipDHeaders{$LSName}) { @SkipInc = (@SkipInc, @{$SkipDHeaders{$LSName}}); } if($SysDescriptor{"Defines"}) { push(@Defines, $SysDescriptor{"Defines"}); } # add sections if(@Preamble) { push(@Content, "<include_preamble>\n ".join("\n ", @Preamble)."\n</include_preamble>"); } if(@Skip) { push(@Content, "<skip_headers>\n ".join("\n ", @Skip)."\n</skip_headers>"); } if(@SkipInc) { push(@Content, "<skip_including>\n ".join("\n ", @SkipInc)."\n</skip_including>"); } if(@AddIncPath) { push(@Content, "<add_include_paths>\n ".join("\n ", @AddIncPath)."\n</add_include_paths>"); } if(@SkipIncPath) { push(@Content, "<skip_include_paths>\n ".join("\n ", @SkipIncPath)."\n</skip_include_paths>"); } if(@SkipSymb) { push(@Content, "<skip_symbols>\n ".join("\n ", @SkipSymb)."\n</skip_symbols>"); } if(@SkipTypes) { push(@Content, "<skip_types>\n ".join("\n ", @SkipTypes)."\n</skip_types>"); } if(@CompilerOpts) { push(@Content, "<gcc_options>\n ".join("\n ", @CompilerOpts)."\n</gcc_options>"); } if(@Defines) { push(@Content, "<defines>\n ".join("\n ", @Defines)."\n</defines>"); } writeFile($DPath, join("\n\n", @Content)); $Generated{$LRelPath} = 1; # save header files to create visual diff later my $HSDir = $SYS_DUMP_PATH."/headers/".$LName; rmtree($HSDir); mkpath($HSDir); foreach my $H_P (@IncHeaders) { if(-f $H_P) { copy($H_P, $HSDir); } } } } printMsg("INFO", "Created descriptors: ".keys(%Generated)." ($SYS_DUMP_PATH/descriptors/)\n"); if($Debug) { printMsg("INFO", localtime(time)); } printMsg("INFO", "Dumping ABIs:"); my %Dumped = (); my @Descriptors = cmd_find($SYS_DUMP_PATH."/descriptors","f","*.xml","1"); if(-d $SYS_DUMP_PATH."/descriptors" and $#Descriptors==-1) { printMsg("ERROR", "internal problem with \'find\' utility"); } foreach my $DPath (sort {lc($a) cmp lc($b)} @Descriptors) { my $DName = get_filename($DPath); my $LName = ""; if($DName=~/\A(.+).xml\Z/) { $LName = $1; } else { next; } if(not is_target_lib($LName) and not is_target_lib($LibSoname{$LName})) { next; } $DPath = cut_path_prefix($DPath, $ORIG_DIR); my $ACC_dump = "perl $0"; if($GroupByHeaders) { # header name is going here $ACC_dump .= " -l $LName"; } else { $ACC_dump .= " -l ".parse_libname($LName, "name", $OStarget); } $ACC_dump .= " -dump \"$DPath\""; if($SystemRoot) { $ACC_dump .= " -relpath \"$SystemRoot\""; $ACC_dump .= " -sysroot \"$SystemRoot\""; } my $DumpPath = "$SYS_DUMP_PATH/abi_dumps/$LName.abi"; $ACC_dump .= " -dump-path \"$DumpPath\""; my $LogPath = "$SYS_DUMP_PATH/logs/$LName.txt"; unlink($LogPath); $ACC_dump .= " -log-path \"$LogPath\""; if($CrossGcc) { $ACC_dump .= " -cross-gcc \"$CrossGcc\""; } if($CheckHeadersOnly) { $ACC_dump .= " -headers-only"; } if($UseStaticLibs) { $ACC_dump .= " -static-libs"; } if($GroupByHeaders) { $ACC_dump .= " -header $LName"; } if($NoStdInc or $OStarget=~/windows|symbian/) { # 1. user-defined # 2. windows/minGW # 3. symbian/GCC $ACC_dump .= " -nostdinc"; } if($CxxIncompat or $CxxIncompat_L{$LName}) { $ACC_dump .= " -cxx-incompatible"; } if($SkipUnidentified) { $ACC_dump .= " -skip-unidentified"; } if($Quiet) { # quiet mode $ACC_dump .= " -quiet"; } if($LogMode eq "n") { $ACC_dump .= " -logging-mode n"; } elsif($Quiet) { $ACC_dump .= " -logging-mode a"; } if($Debug) { # debug mode $ACC_dump .= " -debug"; printMsg("INFO", "$ACC_dump"); } printMsg("INFO_C", "Dumping $LName: "); system($ACC_dump." 1>$TMP_DIR/null 2>$TMP_DIR/$LName.stderr"); my $ErrCode = $?; appendFile("$SYS_DUMP_PATH/logs/$LName.txt", "The ACC parameters:\n $ACC_dump\n"); my $ErrCont = readFile("$TMP_DIR/$LName.stderr"); if($ErrCont) { appendFile("$SYS_DUMP_PATH/logs/$LName.txt", $ErrCont); } if(filterError($ErrCont)) { if(get_CodeError($ErrCode>>8) eq "Invalid_Dump") { printMsg("INFO", "Empty"); } else { printMsg("INFO", "Errors (\'$SYS_DUMP_PATH/logs/$LName.txt\')"); } } elsif(not -f $DumpPath) { printMsg("INFO", "Failed (\'$SYS_DUMP_PATH/logs/$LName.txt\')"); } else { $Dumped{$LName}=1; printMsg("INFO", "Ok"); } } printMsg("INFO", "\n"); if(not $GroupByHeaders) { # general mode printMsg("INFO", "Total libraries: ".keys(%TotalLibs)); printMsg("INFO", "Skipped libraries: ".keys(%Skipped)." ($SYS_DUMP_PATH/skipped.txt)"); printMsg("INFO", "Failed to find headers: ".keys(%Failed)." ($SYS_DUMP_PATH/failed.txt)"); } printMsg("INFO", "Dumped ABIs: ".keys(%Dumped)." ($SYS_DUMP_PATH/abi_dumps/)"); printMsg("INFO", "The ".$SysDescriptor{"Name"}." system ABI has been dumped to:\n $SYS_DUMP_PATH"); } sub filterError($) { my $Error = $_[0]; if(not $Error) { return undef; } my @Err = (); foreach my $L (split(/\n/, $Error)) { if($L!~/warning:/) { push(@Err, $L); } } return join("\n", @Err); } return 1;