pax_global_header00006660000000000000000000000064126727723060014526gustar00rootroot0000000000000052 comment=03cde4325c49e8dc1635e15e0d4d80d7a81beb3a pkgdiff-1.7.2/000077500000000000000000000000001267277230600131475ustar00rootroot00000000000000pkgdiff-1.7.2/INSTALL000066400000000000000000000027441267277230600142070ustar00rootroot00000000000000 Copyright (C) 2012-2016 Andrey Ponomarenko's ABI Laboratory All rights reserved. RELEASE INFORMATION Project: Package Changes Analyzer (pkgdiff) Version: 1.7.2 Date: 2016-03-18 This file explains how to install and setup environment for the tool in your computer. Content: 1. Requirements for Linux and FreeBSD 2. Requirements for Mac OS X 3. Configure and Install 4. Usage 1. REQUIREMENTS FOR LINUX AND FREEBSD ===================================== 1. Perl 5 (5.8 or newer) 2. GNU Diff 3. GNU Wdiff 4. GNU Awk 5. GNU Binutils (readelf) 1.1 Analysis of RPM packages RPM (rpm, rpm2cpio) 1.2 Analysis of DEB packages DPKG (dpkg, dpkg-deb) 1.3 Suggestions 1. ABI Compliance Checker (1.99.1 or newer) 2. ABI Dumper (0.97 or newer) 2. REQUIREMENTS FOR MAC OS X ============================ 1. Perl 5 (5.8 or newer) 2. GNU Diff 3. GNU Wdiff 4. GNU Awk 5. Xcode (otool) 2.1 Suggestions 1. ABI Compliance Checker (1.99.1 or newer) 2. ABI Dumper (0.97 or newer) 3. CONFIGURE AND INSTALL ======================== This command will install the pkgdiff program into the PREFIX/bin system directory and private modules into the PREFIX/share: sudo make install prefix=PREFIX [/usr, /usr/local, ...] 3.1 Remove sudo make uninstall prefix=PREFIX 4. USAGE ======== Compare two packages: pkgdiff PKG1 PKG2 For advanced usage, see output of --help option Enjoy! pkgdiff-1.7.2/LICENSE000066400000000000000000000431571267277230600141660ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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 along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License.pkgdiff-1.7.2/Makefile000066400000000000000000000002221267277230600146030ustar00rootroot00000000000000prefix ?= /usr .PHONY: install install: perl Makefile.pl -install -prefix "$(prefix)" uninstall: perl Makefile.pl -remove -prefix "$(prefix)" pkgdiff-1.7.2/Makefile.pl000066400000000000000000000152121267277230600152220ustar00rootroot00000000000000#!/usr/bin/perl ########################################################################### # Makefile for PkgDiff # Install/remove the tool for GNU/Linux, FreeBSD and Mac OS X # # Copyright (C) 2012-2015 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 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 along # with this program. If not, see . ########################################################################### use Getopt::Long; Getopt::Long::Configure ("posix_default", "no_ignore_case"); use File::Path qw(mkpath rmtree); use File::Copy qw(copy); use File::Basename qw(dirname); use Cwd qw(abs_path); use File::Find; use strict; my $TOOL_SNAME = "pkgdiff"; my $ARCHIVE_DIR = abs_path(dirname($0)); my $HELP_MSG = " NAME: Makefile for pkgdiff DESCRIPTION: Install $TOOL_SNAME command and private modules. USAGE: sudo perl $0 -install -prefix /usr sudo perl $0 -remove -prefix /usr OPTIONS: -h|-help Print this help. --prefix=PREFIX Install files in PREFIX [/usr]. -install Command to install the tool. -remove Command to remove the tool. EXTRA OPTIONS: --destdir=DESTDIR This option is for maintainers to build RPM or DEB packages inside the build root. The environment variable DESTDIR is also supported. \n"; if(not @ARGV) { print $HELP_MSG; exit(0); } my ($PREFIX, $DESTDIR, $Help, $Install, $Remove); GetOptions( "h|help!" => \$Help, "prefix=s" => \$PREFIX, "destdir=s" => \$DESTDIR, "install!" => \$Install, "remove!" => \$Remove ) or exit(1); sub scenario() { if($Help) { print $HELP_MSG; exit(0); } if(not $Install and not $Remove) { print STDERR "ERROR: command is not selected (-install or -remove)\n"; exit(1); } if($Install) { # remove old version first $Remove = 1; } if($PREFIX ne "/") { $PREFIX=~s/[\/]+\Z//g; } if(not $PREFIX) { # default prefix $PREFIX = "/usr"; } if(my $Var = $ENV{"DESTDIR"}) { print "Using DESTDIR environment variable\n"; $DESTDIR = $Var; } if($DESTDIR) { if($DESTDIR ne "/") { $DESTDIR=~s/[\/]+\Z//g; } if($DESTDIR!~/\A\//) { print STDERR "ERROR: destdir is not absolute path\n"; exit(1); } if(not -d $DESTDIR) { print STDERR "ERROR: you should create destdir directory first\n"; exit(1); } $PREFIX = $DESTDIR.$PREFIX; if(not -d $PREFIX) { print STDERR "ERROR: you should create installation directory first (destdir + prefix):\n mkdir -p $PREFIX\n"; exit(1); } } else { if($PREFIX!~/\A\//) { print STDERR "ERROR: prefix is not absolute path\n"; exit(1); } if(not -d $PREFIX) { print STDERR "ERROR: you should create prefix directory first\n"; exit(1); } } print "INSTALL PREFIX: $PREFIX\n"; # paths my $EXE_PATH = "$PREFIX/bin"; my $MODULES_PATH = "$PREFIX/share/$TOOL_SNAME"; my $REL_PATH = "../share/$TOOL_SNAME"; if(not -w $PREFIX) { print STDERR "ERROR: you should be root\n"; exit(1); } if($Remove) { if(-e $EXE_PATH."/".$TOOL_SNAME) { # remove executable print "-- Removing $EXE_PATH/$TOOL_SNAME\n"; unlink($EXE_PATH."/".$TOOL_SNAME); } elsif(not $Install) { print "The tool is not installed\n"; } if(-d $MODULES_PATH) { # remove modules print "-- Removing $MODULES_PATH\n"; rmtree($MODULES_PATH); } elsif(not $Install) { print "The modules of the tool are not installed\n"; } } if($Install) { # configure my $Content = readFile($ARCHIVE_DIR."/".$TOOL_SNAME.".pl"); if($DESTDIR) { # relative path $Content=~s/MODULES_INSTALL_PATH/$REL_PATH/; } else { # absolute path $Content=~s/MODULES_INSTALL_PATH/$MODULES_PATH/; } # copy executable print "-- Installing $EXE_PATH/$TOOL_SNAME\n"; mkpath($EXE_PATH); writeFile($EXE_PATH."/".$TOOL_SNAME, $Content); chmod(0755, $EXE_PATH."/".$TOOL_SNAME); # copy modules if(-d $ARCHIVE_DIR."/modules") { print "-- Installing $MODULES_PATH\n"; mkpath($MODULES_PATH); copyDir($ARCHIVE_DIR."/modules", $MODULES_PATH); my $TOOLS_PATH = $MODULES_PATH."/modules/Internals/Tools"; my @Tools = listDir($TOOLS_PATH); foreach my $Tool (@Tools) { chmod(0755, $TOOLS_PATH."/".$Tool); } } # check PATH if($ENV{"PATH"}!~/(\A|:)\Q$EXE_PATH\E[\/]?(\Z|:)/) { print "WARNING: your PATH variable doesn't include \'$EXE_PATH\'\n"; } } exit(0); } sub listDir($) { my $Path = $_[0]; return () if(not $Path or not -d $Path); opendir(my $DH, $Path); return () if(not $DH); my @Contents = grep { $_ ne "." && $_ ne ".." } readdir($DH); return @Contents; } sub copyDir($$) { my ($From, $To) = @_; my %Files; find(\&wanted, $From); sub wanted { $Files{$File::Find::dir."/$_"} = 1 if($_ ne "."); } foreach my $Path (sort keys(%Files)) { my $Inst = $Path; $Inst=~s/\A\Q$ARCHIVE_DIR\E/$To/; if(-d $Path) { # directories mkpath($Inst); } else { # files mkpath(dirname($Inst)); copy($Path, $Inst); } } } sub readFile($) { my $Path = $_[0]; return "" if(not $Path or not -f $Path); open(FILE, $Path) || die ("can't open file \'$Path\': $!\n"); local $/ = undef; my $Content = ; close(FILE); return $Content; } sub writeFile($$) { my ($Path, $Content) = @_; return if(not $Path); open(FILE, ">".$Path) || die ("can't open file \'$Path\': $!\n"); print FILE $Content; close(FILE); } scenario(); pkgdiff-1.7.2/README000066400000000000000000000015231267277230600140300ustar00rootroot00000000000000NAME: Package Changes Analyzer (pkgdiff) - a tool for visualizing changes in Linux software packages (RPM, DEB, TAR.GZ, etc). The tool is intended for Linux maintainers who are interested in ensuring compatibility of old and new versions of packages. Sample report: http://abi-laboratory.pro/tracker/package_diff/libssh/0.6.5/0.7.0/report.html The tool is developed by Andrey Ponomarenko: http://abi-laboratory.pro/ INSTALL: sudo make install prefix=/usr REQUIRES: Perl 5 GNU Diff GNU Wdiff GNU Awk GNU Binutils USAGE: pkgdiff PKG1 PKG2 EXAMPLE: pkgdiff libssh-0.6.5.tar.xz libssh-0.7.0.tar.xz ADV. USAGE: For advanced usage, see output of --help option SUGGESTS: ABI Compliance Checker 1.99.1 or newer: https://github.com/lvc/abi-compliance-checker/ ABI Dumper 0.97 or newer: https://github.com/lvc/abi-dumper pkgdiff-1.7.2/doc/000077500000000000000000000000001267277230600137145ustar00rootroot00000000000000pkgdiff-1.7.2/doc/Readme.html000066400000000000000000000415171267277230600160070ustar00rootroot00000000000000 pkgdiff - Package Changes Analyzer Fork me on GitHub

pkgdiff

A tool for visualizing changes in Linux software packages

Package Changes Analyzer (pkgdiff) is a tool for visualizing changes in Linux software packages (RPM, DEB, TAR.GZ, etc). The tool is intended for Linux maintainers who are interested in ensuring compatibility of old and new versions of packages.

The tool is developed by Andrey Ponomarenko: http://abi-laboratory.pro/
Table of Contents

Downloads

All releases can be downloaded from this page.

Latest release: pkgdiff-1.7.2.tar.gz

Read-only access to the latest development version:

 git clone git://github.com/lvc/pkgdiff 

License

This program is free software. You may use, redistribute and/or modify it under the terms of the GNU GPL

Supported Platforms

GNU/Linux, FreeBSD, Mac OS X

Dependencies

Requires: Suggests:

Installation

The tool is ready-to-use after extracting the archive.

You can also use a Makefile to install the tool into the system:

 sudo make install prefix=PREFIX [/usr, /usr/local] 

This command will install a pkgdiff program to the PREFIX/bin system directory and private modules into the PREFIX/share.

Usage

 pkgdiff PKG1 PKG2 

The HTML report will be generated to:

 pkgdiff_reports/<pkg>/<v1>_to_<v2>/changes_report.html 

Examples

Compare 0.4.1 and 0.8.1 versions of libqb SRPM-packages:

 pkgdiff libqb-0.4.1-2.fc15.src.rpm libqb-0.8.1-2.fc16.src.rpm 

The HTML report will be generated to:

 pkgdiff_reports/libqb/0.4.1_to_0.8.1/changes_report.html 

Compare 0.10.23 and 0.10.32 versions of gstreamer TXZ-packages:

 pkgdiff gstreamer-0.10.23-i486-1.txz gstreamer-0.10.32-i486-1.txz 

The HTML report will be generated to:

 pkgdiff_reports/gstreamer/0.10.23-i486-1_to_0.10.32-i486-1/changes_report.html 

Compare 0.3.4 and 0.4.0 versions of libssh TAR.GZ-packages:

 pkgdiff libssh-0.3.4.tar.gz libssh-0.4.0.tar.gz 

The HTML report will be generated to:

 pkgdiff_reports/libssh/0.3.4_to_0.4.0/changes_report.html 

Compare 1.10.2 and 1.12.2 versions of cairo DEB-packages:

 pkgdiff libcairo2-dev_1.10.2-6.1_i386.deb libcairo2-dev_1.12.2-1_i386.deb 

The HTML report will be generated to:

 pkgdiff_reports/libcairo2-dev/1.10.2-6.1_to_1.12.2-1/changes_report.html 

Compare 2.24.1 and 2.28.8 versions of glib RPM-packages:

 pkgdiff libglib2.0-devel-2.24.1.i586.rpm libglib2.0-devel-2.28.8.i586.rpm 

The HTML report will be generated to:

 pkgdiff_reports/libglib2.0-devel/2.24.1_to_2.28.8/changes_report.html 

Adv. Usage

For advanced usage, see output of --help option:

 pkgdiff --help 

If you need to analyze a group of packages then you can create an XML-descriptor of this group (OLD.xml file):

<version>
    2.24.1
</version>

<group>
    libglib
</group>

<packages>
    libglib2.0-devel-2.24.1-1mdv2010.1.i586.rpm
    libglib2.0_0-2.24.1-1mdv2010.1.i586.rpm
</packages>

And then pass XML-descriptors of old and new versions of a group to the tool:

 pkgdiff OLD.xml NEW.xml 

The HTML report will be generated to:

 pkgdiff_reports/<group>/<v1>_to_<v2>/changes_report.html 

Adv. Examples

Compare 2.3.12 and 2.4.5 versions of libfreetype6 group of RPM-packages:

 pkgdiff 2.3.12.xml 2.4.5.xml 

File 2.3.12.xml:

<version>
    2.3.12
</version>

<group>
    libfreetype6
</group>

<packages>
    libfreetype6-2.3.12-1mdv2010.1.i586.rpm
    libfreetype6-devel-2.3.12-1mdv2010.1.i586.rpm
</packages>

File 2.4.5.xml:

<version>
    2.4.5
</version>

<group>
    libfreetype6
</group>

<packages>
    libfreetype6-2.4.5-2-mdv2011.0.i586.rpm
    libfreetype6-devel-2.4.5-2-mdv2011.0.i586.rpm
</packages>

The HTML report will be generated to:

 pkgdiff_reports/libfreetype6/2.3.12_to_2.4.5/changes_report.html 

Bugs

Please post your bug reports, feature requests and questions to the issue tracker.

Maintainers

The tool is developed by Andrey Ponomarenko.

Changes

Version 1.7.2 (March 18, 2016)
New Features
  • Ability to download plain-text added/removed patches from the report
Bug Fixes
  • Do not show time stamp in the report
  • Fixed removal of tmp directory in the rfcdiff-1.41-CUSTOM.sh

Version 1.7.1 (December 11, 2015)
New Features
  • Added -skip-pattern option: skip checking of paths within archives matching a regex
Bug Fixes
  • Fixed incompatibility with the latest versions of the ABICC tool
  • Fixed comparison of the RPM/Deb packages info

Version 1.7.0 (October 18, 2015)
New Features
  • Added -d option: compare directories instead of packages
  • Added -list-added-removed option: show content of added/removed text files
  • Added -vnum1 and -vnum2 options: set version numbers of input packages
  • Added -title option: set name of the package in the title of the report
  • Added -skip-subarchives option: skip checking of archives inside packages

Version 1.6.4 (September 08, 2015)
Bug Fixes
  • Fixed style of the report
  • Fixed install permissions
  • Fixed errors when comparing man pages
  • Added check for wdiff
  • Simplified Makefile

Version 1.6.3 (November 05, 2014)
New Features
  • Added -hide-unchanged option: don't show unchanged files in the report
  • Show change rate in the extra info files.xml (moved, changed, renamed)
  • Check if we can remove a common prefix from files of both packages
  • Encoding of diff reports has been changed to utf-8
Bug Fixes
  • Fixed a problem with spaces in package name
  • Added -o option to unzip command
  • Fixed -tmp-dir option

Version 1.6 (June 07, 2013)
New Features
  • ABI compatibility check for shared objects and kernel modules containing DWARF debugging information (in -details mode)
  • ABI status section in the report for debuginfo-packages
  • Suggests ABI Compliance Checker 1.99.1
  • Suggests ABI Dumper 0.97

Version 1.5 (April 04, 2013)
New Features
  • Added -extra-info option to dump extra analysis information
  • Improved identification of moved files
  • Classifying files by magic bytes
  • Compare public symbols in shared objects before detailed comparison
  • Added statistics comment line in the report
  • Support for more file types
Bug Fixes
  • Corrected Makefile
  • Corrected parser of package version

Version 1.4.1 (December 14, 2012)
Bug Fixes
  • Corrected parser of package version
  • Opening file diffs in a separate window

Version 1.4 (October 25, 2012)
New Features
  • Sorting by status, delta and file name in the report
  • New -open option to open report in the default browser
  • Printing of estimated change rate to the console
  • Simplified usage: pkgdiff PKG1 PKG2
  • Support for Mac OS X
  • Support for more file formats
pkgdiff-1.7.2/modules/000077500000000000000000000000001267277230600146175ustar00rootroot00000000000000pkgdiff-1.7.2/modules/FileType.xml000066400000000000000000001453141267277230600170720ustar00rootroot00000000000000 HEADER Header file h, hh, H, hp, hxx, hpp, HPP, h++, tcc 100 Text SHARED_OBJECT Shared library so 90 KERNEL_MODULE Kernel module ko 85 DEBUG_INFO Debug info file debug 80 DLL DLL library dll 85 SHARED_LIBRARY Dynamic library dylib 85 SPEC Spec file spec 90 Text C_SOURCE C program c, i 90 Text PATCH Patch patch, diff, diffs diff output 80 Text STATIC_LIBRARY Static library a 80 CPP_SOURCE C++ program cc, cp, cxx, cpp, CPP, c++, C, ii 90 Text ADA Ada program ads, adb 70 Text FORTRAN Fortran program f, for, ftn F, FOR, fpp, FPP, FTN f90, f95. f03, f08, F90, F95, F03, F08 70 Text SYMBOL Symbols file sym, syms, fncs 60 Text LIBTOOL Libtool file la, lo, plo 50 MANPAGE Man page 50 DOCBOOK DocBook document docbook 40 Text NVDL NVDL script nvdl 40 Text RSS RSS file rss 40 Text MMP Symbian Project file mmp 40 Text BINARY_TERRAIN Binary Terrain file bt 40 Text X3D X3D Image x3d, x3dv 40 Text S_EXPRESSION_FILE S-Expression file sexpr 40 Text LOG_FILE Log file xorg.log.1 log, log.1 logs 40 Text ARGUMENT_FILE Argument file arg, args, argv 40 Text ML_PROGRAM ML program ml 40 Text AUGEAS_FILE Augeas file aug 40 Text VMWARE_SCRIPT VMware Configuration file vmx 40 Text STAP_SCRIPT SystemTap script stp 40 Text PPD_FILE PPD file ppd 40 Text LDAP LDAP Data file ldif 40 Text ESRI_SHAPEFILE ESRI Shape file shp, shx 40 DBASE_DATABASE dBASE Database file dbf DBase 3 data 40 DJVU DjVu document djvu, djv 40 XPT XPConnect Typelib file xpt 40 ELF_BINARY ELF Binary file 40 TDF TDF file tdf 40 Text MESSAGE_FILE Message file eml, msg 40 Text XINDY Xindy file xdy 40 Text GNAT_PROJECT GNAT Project file gpr 40 Text BIBTEX BibTeX document bst, bbl, bib, csf 40 Text INCLUDE Include file inc, inl 40 Text LANGUAGE_DATA Language Data file lng 40 XIB Interface Builder file xib, nib 40 Text MDMP Memory Dump file dmp, mdmp 40 REGISTRY Registry file rgs, reg 40 Text RPC RPC file srpc, rpc, x 40 Text BINHEX BinHex Encoded file hqx 40 PROTOBUF Protocol Buffer file proto 40 Text OPTIONS Options file opt 40 Text SCONS SCons file SConstruct, SConscript scons 40 Text MD5 MD5 Checksum file md5 40 Text SIGNATURE Signature file sig 40 Text OPERA_MAILBOX Opera Mailbox file mbs 40 OPERA_BOOKMARK Opera Bookmark file adr 40 QT_UI Qt UI file ui 40 Text QT_SS Qt style sheet qss 40 Text QT_MESSAGE Qt Message file qm 40 QT_HELP Qt Help file qhp, qch, qhc, qhcp 40 OBJECTIVE_C Objective-C program m, mi 70 Text OBJECTIVE_CPP Objective-C++ program mm, mii, M 70 Text SGML SGML document sgml, sgm 50 Text QT_PROJECT Qt Project file pro, pri, prf 40 Text FSH OpenGL FSH file fsh 40 Text GLSL GLSL Shader file glsl, glslf, glslv, vert, frag 40 Text XCODE Xcode file xcconfig, pbxproj, xcodeproj, nib, pbxbtree, pbxuser xcworkspace, xcsnapshots, xcode 40 PROPERTY_LIST Property list plist 40 POLICY_FILE Policy file policy, Policy 40 Text JAVA_ASSEMBLER Java assembler program jasm, jsasm 50 Text PROPERTY_FILE Property file prp, pty, props, properties 40 Text SWG_FILE SWG file swg 40 Text INITIALIZATION Initialization file ini 50 Text MANIFEST Manifest file manifest, MANIFEST manifest, mf, MF, nmf, mn 50 Text RESOURCE Resource file res, rc, resource, mc, grd, rsrc, rc2 40 Text XTB XTB data file xtb 40 MOLECULE_SPEC Molecule spec file xyz 40 Text QT_RESOURCE Qt Resource file qrc 40 Text QT_DOC Qt Doc file qdoc, qdocconf, qdocinc 40 Text XBEL XBEL bookmark xbel 40 Text IDL IDL file idl 40 Text QML QML file qml, qs 50 Text XML XML document xml 50 Text WML WML document wml 50 MAKEFILE Makefile mk, make, makefile, Makefile, nmake, kmk, mak Makefile, GNUmakefile, Imakefile, Makeconfig, Makerules 50 Text JAVA_MAKEFILE Java Makefile jmk, gmk 50 Text UCM UCM file Unicode Character Mapping ucm 40 Text CHARMAP Charmap file cm charmaps 40 Text ABI_LIST ABI list abilist 40 Text LOCALE_DATA Localization data iconvdata, localedata, locales 40 Text TIMEZONE_DATA Timezone data timezone 40 Text CONFIGURE_SCRIPT Configure script configure, preconfigure 40 Text VERSIONING Version file Version, Versions 40 Text GYP GYP file gyp, gypi 40 Text PKGCONFIG Pkg-config file pc 50 PkgConfigFiles Text OOO OpenOffice document sxd, sxi, sxm, smf, sds, odf, odp, otp, odg odb, otg, odt, ott, sda, sdc, ods, ots 40 QT_TS Qt Translation file ts, qph 40 Text DOT DOT Graph file dot 40 Text DESKTOP Desktop file desktop 40 Text MS_VISUAL_STUDIO MS Visual Studio file vcxproj, vcproj, vcprojin, vcxproj.filters, vcxproj.filtersin sln, vc9, resx, vc6, vcwin32, dsw, dsp, csproj, vsprops, xsx cbproj, vbp, vbproj, vcp, vcw, vbg, vbz, vdp, vdproj, vc4, frm 40 WINDOWS MS Windows file clp, clg, hme, msi, msu, chm, hlp, wmf, emf 40 CSV_FILE CSV file csv 40 Text COMMAND Command file cmd, com, COM 40 Text WIX WiX Installer file wxi, wxs 40 Text STRINGS Text Strings file strings 40 Text OVF OVF file ovf 40 DISK_IMAGE Disk Image file vdi, vmdk, vhd, iso, vco 40 VIRTUAL_BOX VirtualBox file vbox 40 Text MMC Windows MMC file msc 40 VISUAL_BASIC Visual Basic program vbs, vb, vbx 50 Text LEX Lex program l 50 Text YACC Yacc program y 50 Text LISP Lisp program lsp, lisp, el 50 Text PASCAL Pascal program pas 50 Text DOS_BATCH DOS Batch file bat 40 Text POSTSCRIPT PostScript file ps, eps, pfa 40 JAVASCRIPT JavaScript file js, sjs, jsm 50 Text NSIS NSIS script nsi, nsh 40 Text PARAM Parameter file prm, par, ff 40 Text DOXYGEN Doxygen file dox, doxy, doxyfile, doxygen Doxyfile, Doxyfile.in 40 Text QML_PROJECT QML Project file qmlproject 50 Text CMAKE CMake file cmake, cmakein CMakeLists.txt 50 Text EXE Executable file 50 PE_BINARY PE Binary file 45 PE32, PE64, MS-DOS executable AUTOMAKE Automake file am, ac Makefile.am, Makefile.ac 50 Text IPDL IPDL file ipdl 40 Text XPI XPI file xpi 40 CERTIFICATE Certificate file ca, crt, cert, crl, cer, pem 40 ICC ICC profile icc, icm, pf 40 CGI CGI script cgi 40 Text XUL XML UI file xul 40 Text XBL XBL file xbl 40 Text XSL XML style sheet xsl 40 Text XSD XML schema definition xsd 40 Text XSLT XSL transformation file xslt 40 Text DTD DTD file dtd 40 Text ENTITY_FILE Entity file ent 40 Text XML_RNG RNG file rng 40 Text RDF RDF file rdf 40 Text SQLITE QSLite database sqlite 40 BERKELEY_DB Berkeley database db 40 GPERF GNU Gperf file gperf 40 Text YAML YAML file yml, yaml, yml2 40 Text TCL Tcl program tcl 70 Text PYTHON Python program py 70 Text PYTHON_COMPILED Python compiled program d1f20d0a pyc, pyo 60 SED Sed program sed 70 Text FONT Font file bdf, ttf, TTF, ttc, pfb, qpf, qpf2, otf, eot woff, fnt, bfc, afm, xafm, tfm, vf, ofm, sfd 40 BINARY_DTD Binary DTD file bdtd 40 ENCODING Encoding file enc, utf 40 GO Go program go 70 Text IBY ROM Image file iby 40 Text JAVA Java program java, jsp, jj, sjava, java.template 70 Text CS C# program cs 60 Text SCALA Scala program scl 70 Text SHELL Shell program sh, SH, csh, ksh, bash 70 Text ASSEMBLER Assembler program asm, s, S, sx, il 70 Text PERL_TEST Perl test program t 50 Text PERL Perl program pl, PL, plx, e2x, ph 70 Text LUA Lua program lua 60 Text AWK Awk program awk 60 Text PYTHON_EGG Python egg egg Python Eggs 60 RUBY Ruby program rb 60 Text PHP PHP program php, phpt 60 Text VIM Vim Settings file vim 40 SQL SQL file sql 40 SHELL_ARCHIVE Shell archive shar 40 Text WEB_SOURCE WEB Source file web, w 40 Text GETTEXT Gettext file gmo, po, mo, pot 40 GPG GNU GPG file gpg 40 RTF RTF file rtf Rich Text 40 RST RST file rst 40 Text VRML VRML file wrl, vrml 40 Text IV Scene graph file iv 40 MATLAB MATLAB file mat 40 TEX_DOCUMENT TeX document latex, tex, sty LaTeX 40 DVI DVI file dvi, xdv 40 XQ XQuery file xq 40 Text JAVA_CLASS Java class class 60 PERL_MODULE Perl module pm 70 Text PERL_XS Perl XS file xs 40 Text JSON JSON file json 50 Text POD POD document pod 40 Text SKIN Skin file skin 40 COMPILED_OBJECT Compiled object o, obj 40 MACRO Macro file mac 40 INFODOC Info document 40 GNU_LD_SCRIPT GNU LD script ld, lds 50 Text DEF Module-Definition file def 50 Text GIT Git file 40 Text CVS CVS file 40 SVN Subversion file 40 HG Mercurial file 40 BZR Bazaar file 40 TEXINFO Texinfo file texi, texinfo 40 Text MS_OFFICE MS office document doc, docx, xl, xls, xlsb, xlsx, tsv, dex 40 CSS CSS style sheet css 40 Text CSS DATA Data file data, dat, out, stdout, stdin, tbl, input, exp, ex 30 Text M4 M4 macro file m4 50 M4 Text SYMLINK Symbolic link 50 HTML HTML page html, htm, xhtml, dhtml, shtml, shtm 40 HTML PRECOMPILED_HEADER Precompiled header gch, pch 40 PACKAGE Package file pkg 40 PACKAGE_INFO Package Info file PkgInfo pkginfo 40 Text JAM_FILE Jam file Jamfile, Jamrules jam 40 Text LIST_FILE Data List list, lst 40 Text GLADE Glade interface glade 40 Text CONFIGURATION Configuration file conf, config, cfg, cnf, inf, INF, sysconf, sysconfig Config, config 50 Text PDF PDF file pdf 30 X_PIXMAP X Pixmap file xpm, xbm 30 Text IMAGE Image file png, jpg, jpeg, exif, raw, bmp, pbm, webp, spp, tga img, gif, tiff, xcf, cur, svgz, cct, jp2, jpx ppm, tif, yuv, jps, mng, pcx, blend 30 VECTOR_IMAGE Vector Image file svg, emf, dia, asy, fig, cdr, cvx, fxg, hpl, vsd 30 Text AUDIO Audio file wav, aiff, flac, aac, mpc, ogg, wma, sf2, aa, ac3 m3u, midi, wave, wax, wow, mp1, mp2, mp3, mpa, mpga mpu, oga, omg, omx, omf, pcast, pls, acm, afc, mid dls, flw, amr 30 VIDEO Video file swf, flv, mng, avi, 3gp, divx, ogv, movie, mov, swf, wmv, xvid mp4, mpeg, mjpeg, mjp, mpeg4, mpg, mpg2, mpv, mpv2, webm 30 ICON Icon ico, icon, ICO, icns 30 RPM RPM package rpm 20 SRPM SRPM package src.rpm, srpm 20 DEB Deb package deb 20 DIR Directory 20 TEXT Text file txt, TXT 10 Text ARCHIVE Archive tar.gz, tgz, tar.Z, taz, tar.xz, txz tar.bz2, tbz2, tbz, tb2, tar.lzma, tlzma tar.lz, tlz, zip, zae, tar, lzma, gz jar, war, ear, xz, 7z, rar, sfx, ace bz, bz2, zipx 10 INFORM Information file PATCHING, ANNOUNCE, FAQ, HACKING, BUGS, CONFORMANCE TRADEMARK, THANKYOU, PACKAGERS, DEBUG, HOWTO, MAINTAINERS TODO, NOTES, AUTHORS, THANKS, IDEAS, HOWTO.DEBUG, DESIGN PKG-INFO, CREDITS, HISTORY, INSTALL, INDEX, RELEASE CONTRIB, NOTE, INSTALLATION, NEW, DEPS, LEGAL.NOTICE CONTRIBUTORS.txt, BUILDING.txt, Install.txt, BLACKLIST.txt WISHLIST.TXT, HOWTO.txt install, INSTALL 10 CHANGELOG Change log CHANGES, ChangeLog, NEWS, HISTORY Release_notes.txt, RELEASE.txt, CHANGES.txt 10 README Readme file README Readme.txt, README.Debian readme.txt, README 10 LICENSE License LGPL-2, LGPL-3, LICENSE, COPYING, COPYING3 COPYING3.LIB, COPYING.LIB, LGPL, COPYING.LESSER LICENSE.LESSER, LICENSE-MPL LGPL.txt, COPYING.txt, COPYRIGHT, License.txt, COPYING.GPLv2 APACHE.license, LGPL.license, COPYING.readme, COPYING.GPLv3 COPYING.LGPL, LICENSING.txt, COPYING.LGPLv2.1, GPL.txt GPL-LICENSE.txt, MIT-LICENSE.txt, COPYING.MPL, MPL-1.1.txt license, licence 10 OTHER Other Other Files 0 pkgdiff-1.7.2/modules/Internals/000077500000000000000000000000001267277230600165565ustar00rootroot00000000000000pkgdiff-1.7.2/modules/Internals/Scripts/000077500000000000000000000000001267277230600202055ustar00rootroot00000000000000pkgdiff-1.7.2/modules/Internals/Scripts/Sort.js000066400000000000000000000041641267277230600214770ustar00rootroot00000000000000function sort(el, status) { var col_sort = el.innerHTML; var tr = el.parentNode; var table = tr.parentNode; var td, col_sort_num; for (var i=0; (td = tr.getElementsByTagName('th').item(i)); i++) { if(td.innerHTML == col_sort) { col_sort_num = i; if(td.prevsort == 'y') { el.up = Number(!el.up); } else if(td.prevsort == 'n') { td.prevsort = 'y'; el.up = 0; } else { if(col_sort_num==0) { // already sorted td.prevsort = 'n'; el.up = 1; } else if(col_sort_num==2) { // delta td.prevsort = 'n'; el.up = 1; } else { td.prevsort = 'y'; el.up = 0; } } } else { if(td.prevsort == 'y') { td.prevsort = 'n'; } } } var a = new Array(); for(var i=1; i < table.rows.length; i++) { var cols = table.rows[i].getElementsByTagName('td'); if(cols.item(status)==null) { // double status a[i-2][2] = table.rows[i]; } else { a[i-1] = new Array(); var indent = cols.item(col_sort_num).innerHTML; if(indent=='') indent='0'; if(indent.substr(indent.length-1, 1)=="%") { // delta indent = indent.substr(0, indent.length-1)*100; } a[i-1][0] = indent; a[i-1][1] = table.rows[i]; a[i-1][2] = null; } } // sort table a.sort(sort_array); if(el.up) a.reverse(); // draw table for(var i in a) { table.appendChild(a[i][1]); if(a[i][2]!=null) { table.appendChild(a[i][2]); } } } function sort_array(a,b) { if(a[0] == b[0]) return 0; if(a[0] > b[0]) return 1; return -1; }pkgdiff-1.7.2/modules/Internals/Styles/000077500000000000000000000000001267277230600200415ustar00rootroot00000000000000pkgdiff-1.7.2/modules/Internals/Styles/Index.css000066400000000000000000000031131267277230600216200ustar00rootroot00000000000000body { font-family:Arial, sans-serif; background-color:White; color:Black; } hr { color:Black; background-color:Black; height:1px; border:0; } h1 { margin-bottom:0px; padding-bottom:0px; font-size:1.625em; } h2 { margin-bottom:0px; padding-bottom:0px; font-size:1.25em; white-space:nowrap; } table.summary { border-collapse:collapse; border:1px outset black; } table.summary th { background-color:#eeeeee; font-weight:bold; text-align:left; font-size:0.94em; white-space:nowrap; border:1px inset gray; padding: 3px; } table.summary td { text-align:right; white-space:nowrap; border:1px inset gray; padding: 3px 5px 3px 5px; } table.summary td.left { text-align:left; } table.summary th.left { text-align:left; font-weight:100; } table.summary td.f_path { font-size:0.875em; font-family:Consolas, 'DejaVu Sans Mono', 'Droid Sans Mono', Monaco, Monospace; white-space:normal; word-wrap:break-word; max-width:57em; } table.summary td.value { padding-left:10px; } td.passed, tr.passed { background-color:#CCFFCC; } td.warning, tr.warning { background-color:#F4F4AF; } td.renamed, tr.renamed { background-color:#FFDAA3; } td.moved, tr.moved { background-color:#FFDAA3; } td.failed, tr.failed { background-color:#FFC3CE; } td.new, tr.new { background-color:#C6DEFF; } a.default { color:#336699; } div.p_list { padding-left:10px; color:#333333; font-size:0.94em; } .top_ref { font-size:0.69em; } .footer { font-size:0.75em; } pkgdiff-1.7.2/modules/Internals/Styles/View.css000066400000000000000000000011771267277230600214730ustar00rootroot00000000000000body { margin:0.4em; font-size:0.86em; } .view { cursor:text; /*margin-top:7px;*/ font-family:Consolas, 'DejaVu Sans Mono', 'Droid Sans Mono', Monaco, Monospace; background-color:White; word-wrap:break-word; background-color: #FFF; max-width:41.5em; display: inline-block; text-align:left; white-space:pre-wrap; } .header, .plain { background-color:Orange; text-align:center; font-weight:bold; padding-left:1em; padding-right:1em; } .plain { background-color:#BFEFFF; width:0px; } .add { background-color:#C6DEFF; } .rm { background-color:#FFCCCC; } pkgdiff-1.7.2/modules/Internals/Tools/000077500000000000000000000000001267277230600176565ustar00rootroot00000000000000pkgdiff-1.7.2/modules/Internals/Tools/rfcdiff-1.41-CUSTOM.sh000066400000000000000000001030121267277230600232030ustar00rootroot00000000000000#!/bin/bash # # Synopsis: # Show changes between 2 internet-drafts using changebars or html # side-by-side diff. # # Usage: # rfcdiff [options] file1 file2 # # rfcdiff takes two RFCs or Internet-Drafts in text form as input, and # produces output which indicates the differences found in one of various # forms, controlled by the options listed below. In all cases, page # headers and page footers are stripped before looking for changes. # # --html Produce side-by-side .html diff (default) # # --chbars Produce changebar marked .txt output # # --diff Produce a regular diff output # # --wdiff Produce paged wdiff output # # --hwdiff Produce html-wrapped coloured wdiff output # # --oldcolour COLOURNAME Colour for new file in hwdiff (default is "green") # --oldcolor COLORNAME Color for old file in hwdiff (default is "red") # # --newcolour COLOURNAME Colour for new file in hwdiff (default is "green") # --newcolor COLORNAME Color for new file in hwdiff (default is "green") # # --larger Make difference text in hwdiff slightly larger # # --browse Show html output in browser # # --keep Don't delete temporary workfiles # # --version Show version # # --help Show this help # # --info "Synopsis|Usage|Copyright|Description|Log" # Show various info # # --width N Set a maximum width of N characters for the # display of each half of the old/new html diff # # --linenum Show linenumbers for each line, not only at the # start of each change section # # --body Strip document preamble (title, boilerplate and # table of contents) and postamble (Intellectual # Property Statement, Disclaimer etc) # # --nostrip Don't strip headers and footers (or body) # # --ab-diff Before/After diff, suitable for rfc-editor # --abdiff # # --stdout Send output to stdout instead to a file # # --tmpdiff Path to intermediate diff file # # --prelines N Set value for diff -U option # # --minimal Set value for diff -d option # # --ignore-space-change # Ignore changes in the amount of white space. # # --ignore-all-space # Ignore all white space. # # --ignore-blank-lines # Ignore changes whose lines are all blank. # # # Copyright: # ----------------------------------------------------------------- # # Copyright 2002 Henrik Levkowetz # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # 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 # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # ----------------------------------------------------------------- # # Description: # # The purpose of this program is to compare two versions of an # internet-draft, and as output produce a diff in one of several # formats: # # - side-by-side html diff # - paged wdiff output in a text terminal # - a text file with changebars in the left margin # - a simple unified diff output # # In all cases, internet-draft headers and footers are stripped before # generating the diff, to produce a cleaner diff. # # It is called as # # rfcdiff first-file second-file # # The latest version is available from # http://tools.ietf.org/tools/rfcdiff/ # export version="1.41" export prelines="10" export basename=$(basename $0) export workdir=`mktemp -d -t rfcdiff.XXXXXXXX` export pagecache1="$workdir/pagecache1" export pagecache2="$workdir/pagecache2" # ---------------------------------------------------------------------- # Utility to find an executable # ---------------------------------------------------------------------- lookfor() { for b in "$@"; do found=$(which "$b" 2>/dev/null) if [ -n "$found" ]; then if [ -x "$found" ]; then echo "$found" return fi fi done } AWK=$(lookfor gawk nawk awk) # ---------------------------------------------------------------------- # Strip headers footers and formfeeds from infile to stdout # ---------------------------------------------------------------------- strip() { $AWK ' { gsub(/\r/, ""); } { gsub(/[ \t]+$/, ""); } { pagelength++; } /\[?[Pp]age [0-9ivx]+\]?[ \t\f]*$/ { match($0, /[Pp]age [0-9ivx]+/); num = substr($0, RSTART+5, RLENGTH-5); print num, outline > ENVIRON["pagecache" ENVIRON["which"]] pagelength = 0; } /\f/ { newpage=1; pagelength=1; } /\f$/ { # a form feed followed by a \n does not contribute to the # line count. (But a \f followed by something else does.) pagelength--; } /\f/ { next; } /\[?[Pp]age [0-9ivx]+\]?[ \t\f]*$/ { preindent = indent; next; } /^ *Internet.Draft.+[12][0-9][0-9][0-9] *$/ && (FNR > 15) { newpage=1; next; } /^ *INTERNET.DRAFT.+[12][0-9][0-9][0-9] *$/ && (FNR > 15) { newpage=1; next; } /^ *Draft.+( +)[12][0-9][0-9][0-9] *$/ && (FNR > 15) { newpage=1; next; } /^RFC[ -]?[0-9]+.*( +).* [12][0-9][0-9][0-9]$/ && (FNR > 15) { newpage=1; next; } /^draft-[-a-z0-9_.]+.*[0-9][0-9][0-9][0-9]$/ && (FNR > 15) { newpage=1; next; } /(Jan|Feb|Mar|March|Apr|April|May|Jun|June|Jul|July|Aug|Sep|Oct|Nov|Dec) (19[89][0-9]|20[0-9][0-9]) *$/ && pagelength < 3 { newpage=1; next; } newpage && $0 ~ /^ *draft-[-a-z0-9_.]+ *$/ { newpage=1; next; } /^[ \t]+\[/ { sentence=1; } /[^ \t]/ { indent = match($0, /[^ ]/); if (indent < preindent) { sentence = 1; } if (newpage) { if (sentence) { outline++; print ""; } } else { if (haveblank) { outline++; print ""; } } haveblank=0; sentence=0; newpage=0; line = $0; sub(/^ *\t/, " ", line); thiscolumn = match(line, /[^ ]/); } /[.:][ \t]*$/ { sentence=1; } /\(http:\/\/trustee\.ietf\.org\/license-info\)\./ { sentence=0; } /^[ \t]*$/ { haveblank=1; next; } { outline++; print; } ' "$1" } # ---------------------------------------------------------------------- # Strip preamble (title, boilerplate and table of contents) and # postamble (Intellectual Property Statement, Disclaimer etc) # ---------------------------------------------------------------------- bodystrip() { $AWK ' /^[ \t]*Acknowledgment/ { inbody = 0; } /^(Full )*Copyright Statement$/ { inbody = 0; } /^[ \t]*Disclaimer of Validid/ { inbody = 0; } /^[ \t]*Intellectual Property/ { inbody = 0; } /^Abstract$/ { inbody = 0; } /^Table of Contents$/ { inbody = 0; } /^1.[ \t]*Introduction$/ { inbody = 1; } inbody { print; } ' "$1" } # ---------------------------------------------------------------------- # From two words, find common prefix and differing part, join descriptively # ---------------------------------------------------------------------- worddiff() { $AWK ' BEGIN { w1 = ARGV[1] w2 = ARGV[2] format = ARGV[3] do { if (substr(w1,1,1) == substr(w2,1,1)) { w1 = substr(w1,2) w2 = substr(w2,2) } else { break; } prefixlen++; } while (length(w1) && length(w2)) prefix = substr(ARGV[1],1,prefixlen); do { l1 = length(w1); l2 = length(w2); if (substr(w1,l1,1) == substr(w2,l2,1)) { w1 = substr(w1,1,l1-1) w2 = substr(w2,1,l2-1) } else { break; } } while (l1 && l2) suffix = substr(ARGV[1], prefixlen+length(w1)) printf format, prefix, w1, w2, suffix; } ' "$1" "$2" "$3" } # ---------------------------------------------------------------------- # Generate a html page with side-by-side diff from a unified diff # ---------------------------------------------------------------------- htmldiff() { $AWK ' BEGIN { FS = "[ \t,]"; # Read pagecache1 maxpage[1] = 1 pageend[1,0] = 2; while ( getline < ENVIRON["pagecache1"] > 0) { pageend[1,$1] = $2; if ($1+0 > maxpage[1]) maxpage[1] = $1+0; } # Read pagecache2 maxpage[2] = 1 pageend[2,0] = 2; while ( getline < ENVIRON["pagecache2"] > 0) { pageend[2,$1] = $2; if ($1+0 > maxpage[2]) maxpage[2] = $1+0; } wdiff = ENVIRON["wdiffbin"] base1 = ENVIRON["base1"] base2 = ENVIRON["base2"] optwidth = ENVIRON["optwidth"] optnums = ENVIRON["optnums"] optlinks = ENVIRON["optlinks"] cmdline = ENVIRON["cmdline"] gsub("--", "- -", cmdline) ENVIRON["cmdline"] = cmdline header(base1, base2) difflines1 = 0 difflines2 = 0 } function header(file1, file2) { url1 = file1; url2 = file2; if (optlinks) { if (file1 ~ /^draft-/) { url1 = sprintf("%s", file1, file1); } if (file1 ~ /^draft-/) { prev = sprintf("<", file1); } if (file2 ~ /^draft-/) { url2 = sprintf("%s", file2, file2); } if (file2 ~ /^draft-/) { nxt = sprintf(">", file2) } } printf "" \ " \n" \ " \n" \ "\n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " Diff: %s - %s \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ "", ENVIRON["version"], ENVIRON["cmdline"], ENVIRON["uname"], ENVIRON["awkbin"], ENVIRON["awkver"], ENVIRON["diffbin"], ENVIRON["diffver"], ENVIRON["wdiffbin"], ENVIRON["wdiffver"], file1, file2, prev, url1, url2, nxt; } function worddiff(w1, w2) { prefixlen = 0; word1 = w1; do { if (substr(w1,1,1) == substr(w2,1,1)) { w1 = substr(w1,2); w2 = substr(w2,2); } else { break; } prefixlen++; } while (length(w1) && length(w2)); prefix = substr(word1,1,prefixlen); do { l1 = length(w1); l2 = length(w2); if (substr(w1,l1,1) == substr(w2,l2,1)) { w1 = substr(w1,1,l1-1); w2 = substr(w2,1,l2-1); } else { break; } } while (l1 && l2); suffix = substr(word1, prefixlen+length(w1)+1); wordpart[0] = prefix; wordpart[1] = w1; wordpart[2] = w2; wordpart[3] = suffix; } function numdisplay(which, line) { if (optnums && (line != prevline[which])) { prevline[which] = line; return line-1; } return ""; } function fixesc(line) { # Making this a no-op for now -- the change in line-breaking # "
" => "\n" should make this less necessary. # line = gensub(/&(<[^>]*>)/, "\\1\\&", "g", line); # We still have to handle cases where we have a broken up "<" / ">" gsub(/&l<\/span>t;/, "\\<", line); gsub(/&g<\/span>t;/, "\\>", line); gsub(/&amp;/, "\\&", line) gsub(/&amp;/, "\\&", line) gsub(/&lt;/, "\\<", line); gsub(/&gt;/, "\\>", line); gsub(/&lt;/, "\\<", line); gsub(/&gt;/, "\\>", line); gsub(/&l<\/span>t;/, "\\<", line); gsub(/&g<\/span>t;/, "\\>", line); gsub(/&l<\/span>t;/, "\\<", line); gsub(/&g<\/span>t;/, "\\>", line); return line; } function chunkdiff(chunk) { if (difflines1 == 0 && difflines2 == 0) return; chunkfile1= sprintf("1/chunk%04d", chunk); chunkfile2= sprintf("2/chunk%04d", chunk); printf "" > chunkfile1; printf "" > chunkfile2; for (l = 0; l < difflines1; l++) { print stack1[l] >> chunkfile1; } for (l = 0; l < difflines2; l++) { print stack2[l] >> chunkfile2; } close(chunkfile1); close(chunkfile2); cmd1 = sprintf("%s -n -2 -w \"\" -x \"\" %s %s", wdiff, chunkfile1, chunkfile2); cmd2 = sprintf("%s -n -1 -y \"\" -z \"\" %s %s", wdiff, chunkfile1, chunkfile2); l=0; while (cmd1 | getline > 0) { stack1[l] = fixesc($0); l++; } difflines1 = l; l=0; while (cmd2 | getline > 0) { stack2[l] = fixesc($0); l++; } difflines2 = l; close(cmd1); close(cmd2); } function flush() { if (difflines1 || difflines2) { difftag++; multiline = (difflines1 > 1) || (difflines2 > 1); if (multiline && (wdiff != "")) chunkdiff(difftag); printf " \n", difftag; for (l = 0; l < difflines1 || l < difflines2; l++) { if (l in stack1) { line1 = stack1[l]; delete stack1[l]; linenum1++ if (line1 == "") if (optwidth > 0) { line1 = substr(" ",0,optwidth); } else { line1 = " "; } } else { line1 = ""; } if (l in stack2) { line2 = stack2[l]; delete stack2[l]; linenum2++; if (line2 == "") if (optwidth > 0) { line2 = substr(" ",0,optwidth); } else { line2 = " "; } } else { line2 = ""; } if (!multiline || (wdiff == "")) { worddiff(line1, line2); line1 = fixesc(sprintf("%s%s%s", wordpart[0], wordpart[1], wordpart[3])); line2 = fixesc(sprintf("%s%s%s", wordpart[0], wordpart[2], wordpart[3])); # Clean up; remove empty spans sub(/<\/span>/,"", line1); sub(/<\/span>/,"", line2); } left = sprintf("", numdisplay(1, linenum1), line1); right = sprintf("", line2, numdisplay(2, linenum2)); printf " %s%s\n", left, right; } } } function getpage(which, line) { line = line + ENVIRON["prelines"]; page = "?"; for (p=1; p <= maxpage[which]; p++) { if (pageend[which,p] == 0) continue; if (line <= pageend[which,p]) { page = p; break; } } return page; } function getpageline(which, line, page) { if (page == "?") { return line + ENVIRON["prelines"]; } else { if (pageend[which,page-1]+0 != 0) { return line + ENVIRON["prelines"] - pageend[which,page-1] + 3; # numbers of header lines stripped } else { return "?"; } } } function htmlesc(line) { gsub("&", "\\&", line); gsub("<", "\\<", line); gsub(">", "\\>", line); return line; } function expandtabs(line) { spaces = " "; while (pos = index(line, "\t")) { sub("\t", substr(spaces, 0, (8-pos%8)), line); } return line; } function maybebreakline(line, width) { width = optwidth; new = ""; if (width > 0) { line = expandtabs(line); while (length(line) > width) { new = new htmlesc(substr(line, 1, width)) "\n"; line = substr(line, width+1); } } line = new htmlesc(line) ; return line; } /^@@/ { linenum1 = 0 - $2; linenum2 = 0 + $4; diffnum ++; if (linenum1 > 1) { printf " \n"; page1 = getpage(1,linenum1); page2 = getpage(2,linenum2); if (page1 == "?") { posinfo1 = sprintf("skipping to change at line %s", diffnum, getpageline(1, linenum1, page1)); } else { posinfo1 = sprintf("skipping to change at page %s, line %s", diffnum, page1, getpageline(1, linenum1, page1)); } if (page2 == "?") { posinfo2 = sprintf("skipping to change at line %s", diffnum, getpageline(2, linenum2, page2)); } else { posinfo2 = sprintf("skipping to change at page %s, line %s", diffnum, page2, getpageline(2, linenum2, page2)); } printf " \n", posinfo1, posinfo2; } } /^---/ { next; } /^[+][+][+]/ { next; } /^[ ]/ { line = substr($0, 2); line = maybebreakline(line); flush(); linenum1++; linenum2++; printf " ", numdisplay(1, linenum1), line; printf "\n", line, numdisplay(2, linenum2); diffcount1 += difflines1 difflines1 = 0 diffcount2 += difflines2 difflines2 = 0 } /^-/ { line = substr($0, 2); line = maybebreakline(line); stack1[difflines1] = line; difflines1++; } /^[+]/ { line = substr($0, 2); line = maybebreakline(line); stack2[difflines2] = line; difflines2++; } END { flush(); printf("\n" \ " \n" \ " \n" \ " \n" \ " \n" \ "
%s %s   %s %s
%s%s%s%s
%s %s
%s%s %s%s
 %s. %s change blocks. 
%s lines changed or deleted %s lines changed or added

This html diff was produced by rfcdiff %s. The latest version is available from http://tools.ietf.org/tools/rfcdiff/
\n" \ " \n" \ " \n", diffnum?"End of changes":"No changes", difftag, diffcount1, diffcount2, ENVIRON["version"]); } ' "$1" } # ---------------------------------------------------------------------- # Generate before/after text output from a context diff # ---------------------------------------------------------------------- abdiff() { $AWK ' BEGIN { # Read pagecache1 maxpage[1] = 1 pageend[1,0] = 2; while ( getline < ENVIRON["pagecache1"] > 0) { pageend[1,$1] = $2; if ($1+0 > maxpage[1]) maxpage[1] = $1+0; } # Read pagecache2 maxpage[2] = 1 pageend[2,0] = 2; while ( getline < ENVIRON["pagecache2"] > 0) { pageend[2,$1] = $2; if ($1+0 > maxpage[2]) maxpage[2] = $1+0; } base1 = ENVIRON["base1"] base2 = ENVIRON["base2"] section = "INTRODUCTION"; para = 0; } /^\+\+/ { next; } /^\-\-/ { next; } /^ Appendix ./ { section = $1 " " $2; para = 0; } /^ ? ? ?[0-9]+(\.[0-9]+)*\.? / { section = "Section " $1; para = 0; } /^ ?$/ { if (inpara) { printf "\n%s, paragraph %s:\n", section, para; print "OLD:\n" print oldpara print "NEW:\n" print newpara } oldpara = ""; newpara = ""; para ++; inpara = 0 } /^ ./ { oldpara = oldpara $0 "\n" newpara = newpara $0 "\n" } /^\-/ { sub(/^./, " "); oldpara = oldpara $0 "\n" inpara++; } /^\+/ { sub(/^./, " "); newpara = newpara $0 "\n" inpara++; } END { if (inpara) { printf "\n%s, paragraph %s:\n", section, para; print "OLD:\n" print oldpara print "NEW:\n" print newpara } } ' } # ---------------------------------------------------------------------- # Utility to extract keyword info # ---------------------------------------------------------------------- extract() { $AWK -v keyword="$1" ' BEGIN { # print "Keyword", keyword; } /^# [A-Z]/ { # print "New key", $2; if ($2 == keyword ":" ) { output=1; } else { output=0; } # print "Output", output; } /^#\t/ { # print "Content", output, $0; if ( output ) { sub(/^#/,""); print; } } { next; } ' "$2" } # ---------------------------------------------------------------------- # Utility to start a browser # ---------------------------------------------------------------------- browse() { if [ "$(uname)" = "Darwin" ]; then open "$@" else browser=$(lookfor firefox phoenix MozillaFirebird mozilla opera Netscape netscape dillo) if [ -z "$browser" ]; then echo "Couldn't find any browser, can't display $*." exit 1 fi # make sure file name is absolute if [ "${1#/}" = "$1" ]; then # not absolute path, add pwd arg="file://$PWD/$1" else arg="file://$1" fi # see if a browser is running, act accordingly $browser -remote "ping()" >/dev/null 2>&1 if [ $? -eq 0 ]; then # use running instance $browser -raise -remote "openurl($arg, new-tab)" else # error exit: no running instance echo "Starting web browser." $browser "$arg" >/dev/null 2>&1 & fi fi } # ---------------------------------------------------------------------- # Utility for error exit # ---------------------------------------------------------------------- die() { echo $*; exit 1; } # ---------------------------------------------------------------------- # Process options # ---------------------------------------------------------------------- # Default values opthtml=1; optdiff=0; optchbars=0; optwdiff=0; optshow=0; optnowdiff=0; optkeep=0; optinfo=0; optwidth=0; optnums=0; optbody=0; optabdiff=0; optstrip=1; opthwdiff=0; optlinks=0; optoldcolour="red"; optnewcolour="green"; optlarger="" optstdout=0; opttmpdiff=0; tmpdiff=$workdir/diff; while [ $# -gt 0 ]; do case "$1" in --html) opthtml=1; optdiff=0; optchbars=0; optwdiff=0; opthwdiff=0; optabdiff=0;; --diff) opthtml=0; optdiff=1; optchbars=0; optwdiff=0; opthwdiff=0; optabdiff=0;; --chbars) opthtml=0; optdiff=0; optchbars=1; optwdiff=0; opthwdiff=0; optabdiff=0;; --wdiff) opthtml=0; optdiff=0; optchbars=0; optwdiff=1; opthwdiff=0; optabdiff=0;; --hwdiff) opthtml=0; optdiff=0; optchbars=0; optwdiff=0; opthwdiff=1; optabdiff=0;; --changes)opthtml=0; optdiff=0; optchbars=0; optwdiff=0; opthwdiff=0; optabdiff=1;; --abdiff) opthtml=0; optdiff=0; optchbars=0; optwdiff=0; opthwdiff=0; optabdiff=1;; --ab-diff)opthtml=0; optdiff=0; optchbars=0; optwdiff=0; opthwdiff=0; optabdiff=1;; --rfc-editor-diff)opthtml=0; optdiff=0; optchbars=0; optwdiff=0; opthwdiff=0; optabdiff=1;; --version)echo -e "$basename\t$version"; exit 0;; --browse) optshow=1;; --nowdiff)optnowdiff=1;; --keep) optkeep=1;; --info) optinfo=1; keyword=$2; shift;; --help) optinfo=1; keyword="Usage";; --width) optwidth=$2; shift;; --oldcolor) optoldcolour=$2; shift;; --oldcolour) optoldcolour=$2; shift;; --newcolor) optnewcolour=$2; shift;; --newcolour) optnewcolour=$2; shift;; --larger) optlarger='size="+1"';; --linenum)optnums=1;; --body) optbody=1;; --nostrip)optstrip=0; optbody=0;; --stdout) optstdout=1;; --links) optlinks=1;; --ignore-space-change) optnospacechange=1;; --ignore-all-space) optignorewhite=1;; --ignore-blank-lines) optignoreblank=1;; --wdiff-args) optwdiffargs=$2; shift;; --tmpdiff) opttmpdiff=1; tmpdiff=$2; shift;; --prelines) export prelines=$2; shift;; --minimal) optminimal=1;; --) shift; break;; -v) echo "$basename $version"; exit 0;; -*) echo "Unrecognized option: $1"; exit 1;; *) break;; esac shift done export optwidth export optnums export optlinks # ---------------------------------------------------------------------- # Determine output file name. Maybe output usage and exit. # ---------------------------------------------------------------------- #set -x if [ $optinfo -gt 0 ]; then extract $keyword $0 exit fi if [ $# -ge 2 ]; then if [ "$1" = "$2" ]; then echo "The files are the same file" exit fi export base1=$(basename "$1") export base2=$(basename "$2") outbase=$(worddiff "$base2" "$base1" "%s%s-from-%s") else extract Usage $0 exit 1 fi # ---------------------------------------------------------------------- # create working directory. # ---------------------------------------------------------------------- mkdir $workdir/1 || die "$0: Error: Failed to create temporary directory '$workdir/1'." mkdir $workdir/2 || die "$0: Error: Failed to create temporary directory '$workdir/2'." # ---------------------------------------------------------------------- # If any of the files is an http or ftp URL we download it, else copy it # ---------------------------------------------------------------------- wgetbin=$(lookfor wget) dowgetarg1=0 dowgetarg2=0 if [ -n "$wgetbin" ]; then if [ "${1#http://}" != "$1" ]; then dowgetarg1=1; fi if [ "${1#ftp://}" != "$1" ]; then dowgetarg1=1; fi if [ "${2#http://}" != "$2" ]; then dowgetarg2=1; fi if [ "${2#ftp://}" != "$2" ]; then dowgetarg2=1; fi fi if [ $dowgetarg1 -gt 0 ]; then $wgetbin -nv "$1" -O $workdir/1/"$base1" else cp "$1" $workdir/1/"$base1" fi if [ $dowgetarg2 -gt 0 ]; then $wgetbin -nv "$2" -O $workdir/2/"$base2" else cp "$2" $workdir/2/"$base2" fi # ---------------------------------------------------------------------- # Maybe strip headers/footers from both files # ---------------------------------------------------------------------- if [ $optstrip -gt 0 ]; then export which=1 strip $workdir/1/"$base1" > $workdir/1/"$base1".stripped mv -f $workdir/1/"$base1".stripped $workdir/1/"$base1" export which=2 strip $workdir/2/"$base2" > $workdir/2/"$base2".stripped mv -f $workdir/2/"$base2".stripped $workdir/2/"$base2" fi # ---------------------------------------------------------------------- # Maybe do html quoting # ---------------------------------------------------------------------- if [ $opthwdiff -gt 0 ]; then sed -e 's/&/&/g' -e 's//\>/g' $workdir/1/"$base1" > $workdir/1/"$base1".quoted mv -f $workdir/1/"$base1".quoted $workdir/1/"$base1" sed -e 's/&/&/g' -e 's//\>/g' $workdir/2/"$base2" > $workdir/2/"$base2".quoted mv -f $workdir/2/"$base2".quoted $workdir/2/"$base2" fi # ---------------------------------------------------------------------- # Maybe strip preamble (title, boilerplate and table of contents) and # postamble (Intellectual Property Statement, Disclaimer etc) # ---------------------------------------------------------------------- if [ $optbody -gt 0 ]; then bodystrip $workdir/1/"$base1" > $workdir/1/"$base1".stripped mv $workdir/1/"$base1".stripped $workdir/1/"$base1" bodystrip $workdir/2/"$base2" > $workdir/2/"$base2".stripped mv $workdir/2/"$base2".stripped $workdir/2/"$base2" fi # ---------------------------------------------------------------------- # Get output file name # ---------------------------------------------------------------------- if [ "$3" ]; then outfile="$3" else if [ $opthtml -gt 0 ]; then outfile=./"$outbase".diff.html fi if [ $optchbars -gt 0 ]; then outfile=./"$outbase".chbar fi if [ $optdiff -gt 0 ]; then outfile=./"$outbase".diff fi if [ $optabdiff -gt 0 ]; then outfile=./"$outbase".changes fi if [ $opthwdiff -gt 0 ]; then outfile=./"$outbase".wdiff.html fi fi if [ "$outfile" ]; then tempout=./$(basename "$outfile") fi # ---------------------------------------------------------------------- # Check if we can use wdiff for block diffs # ---------------------------------------------------------------------- if [ $optnowdiff -eq 0 ]; then wdiffbin=$(lookfor wdiff) if [ -n "$wdiffbin" ]; then wdiffver=$($wdiffbin --version 2>/dev/null | egrep "(wdiff|GNU).+[0-9]\.[0-9]") if [ -z "$wdiffver" ]; then wdiffbin=""; echo -en "\n Found wdiff, but it reported no recognisable version." fi else echo -en "\n Couldn't find wdiff." fi if [ -z "$wdiffbin" ]; then echo " Falling back to builtin diff colouring..."; fi export wdiffbin export wdiffver #echo "Found wdiff at $wdiffbin" fi # ---------------------------------------------------------------------- # Get some misc. info # ---------------------------------------------------------------------- uname=$(uname -a) export uname awkbin=$AWK export awkbin awkver=$( { $AWK --version 2>/dev/null || $AWK -V 2>/dev/null; } | head -n 1) export awkver diffbin=$(lookfor diff) export diffbin diffver=$(diff --version | head -n 1) export diffver # ---------------------------------------------------------------------- # Check that we don't have a broken awk # ---------------------------------------------------------------------- if [ $opthtml -gt 0 -a "${uname%% *}" == "Darwin" -a "$awkver" == "awk version 20070501" ]; then echo -e "\n Oops. Awk version 20070501 on OS X doesn't work with rfcdiff's html mode.\n To make rfcdiff work, you could install Gnu Awk (gawk), for instance using\n MacPorts, http://www.macports.org/." exit 1 fi # ---------------------------------------------------------------------- # Do diff # ---------------------------------------------------------------------- origdir=$PWD cd $workdir if cmp 1/"$base1" 2/"$base2" >/dev/null; then echo "" echo "The files are identical." fi if [ $opthtml -gt 0 ]; then diff ${optignoreblank:+-B} ${optminimal:+-d} ${optnospacechange:+-b} ${optignorewhite:+-w} -U $prelines 1/"$base1" 2/"$base2" | tee $tmpdiff | htmldiff > "$tempout" fi if [ $optchbars -gt 0 ]; then diff -Bwd -U 10000 1/"$base1" 2/"$base2" | tee $tmpdiff | grep -v "^-" | tail -n +3 | sed 's/^+/|/' > "$tempout" fi if [ $optdiff -gt 0 ]; then diff -Bwd -U $prelines 1/"$base1" 2/"$base2" | tee $tmpdiff > "$tempout" fi if [ $optabdiff -gt 0 ]; then diff -wd -U 1000 1/"$base1" 2/"$base2" | tee $tmpdiff | abdiff fi if [ $optwdiff -gt 0 ]; then wdiff -a $optwdiffargs 1/"$base1" 2/"$base2" fi if [ $opthwdiff -gt 0 ]; then echo "wdiff "$base1" "$base2"" > "$tempout" echo "
"								>> "$tempout"
    wdiff -w "" -x ""	\
          -y "" -z ""	\
	  1/"$base1" 2/"$base2"							>> "$tempout"
    echo "
" >> "$tempout" echo "" >> "$tempout" fi if [ $optstdout -gt 0 ]; then cat "$tempout" rm "$tempout" else cd "$origdir"; if [ -f $workdir/"$tempout" ]; then mv $workdir/"$tempout" "$outfile"; fi fi if [ $optshow -gt 0 ]; then browse "$outfile" fi if [ $optkeep -eq 0 ]; then if [ -f $pagecache1 ]; then rm $pagecache1; fi if [ -f $pagecache2 ]; then rm $pagecache2; fi rm -fr $workdir/1 rm -fr $workdir/2 if [ -f $tmpdiff ]; then if [ $opttmpdiff -eq 0 ]; then rm $tmpdiff fi fi rm -f $workdir/$tmpdiff rmdir $workdir else cd /tmp tar czf $basename-$$.tgz $basename-$$ echo " Temporary workfiles have been left in $workdir/, and packed up in $workdir.tgz" fi pkgdiff-1.7.2/pkgdiff.pl000066400000000000000000003255051267277230600151300ustar00rootroot00000000000000#!/usr/bin/perl ########################################################################### # PkgDiff - Package Changes Analyzer 1.7.2 # A tool for visualizing changes in Linux software packages # # Copyright (C) 2012-2016 Andrey Ponomarenko's ABI Laboratory # # Written by Andrey Ponomarenko # # PLATFORMS # ========= # GNU/Linux, FreeBSD, Mac OS X # # PACKAGE FORMATS # =============== # RPM, DEB, TAR.GZ, etc. # # REQUIREMENTS # ============ # Perl 5 (5.8 or newer) # GNU Diff # GNU Wdiff # GNU Awk # GNU Binutils (readelf) # RPM (rpm, rpmbuild, rpm2cpio) for analysis of RPM-packages # DPKG (dpkg, dpkg-deb) for analysis of DEB-packages # # SUGGESTIONS # =========== # ABI Compliance Checker (1.99.1 or newer) # ABI Dumper (0.97 or newer) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU 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 along # with this program. If not, see . ########################################################################### use Getopt::Long; Getopt::Long::Configure ("posix_default", "no_ignore_case", "permute"); use File::Path qw(mkpath rmtree); use File::Temp qw(tempdir); use File::Copy qw(move copy); use File::Compare; use Cwd qw(abs_path cwd); use Config; use Fcntl; my $TOOL_VERSION = "1.7.2"; my $ORIG_DIR = cwd(); # Internal modules my $MODULES_DIR = get_Modules(); push(@INC, get_dirname($MODULES_DIR)); my $DIFF = $MODULES_DIR."/Internals/Tools/rfcdiff-1.41-CUSTOM.sh"; my $ACC = "abi-compliance-checker"; my $ACC_VER = "1.99.1"; my $ABI_DUMPER = "abi-dumper"; my $ABI_DUMPER_VER = "0.97"; my ($Help, $ShowVersion, $DumpVersion, $GenerateTemplate, %Descriptor, $CheckUsage, $PackageManager, $OutputReportPath, $ShowDetails, $Debug, $SizeLimit, $QuickMode, $DiffWidth, $DiffLines, $Minimal, $IgnoreSpaceChange, $IgnoreAllSpace, $IgnoreBlankLines, $ExtraInfo, $CustomTmpDir, $HideUnchanged, $TargetName, $TargetTitle, %TargetVersion, $CompareDirs, $ListAddedRemoved, $SkipSubArchives, $LinksTarget, $SkipPattern); my $CmdName = get_filename($0); my %ERROR_CODE = ( # Unchanged verdict "Unchanged"=>0, # Changed verdict "Changed"=>1, # Undifferentiated error code "Error"=>2, # System command is not found "Not_Found"=>3, # Cannot access input files "Access_Error"=>4, # Cannot find a module "Module_Error"=>9 ); my $HomePage = "http://lvc.github.com/pkgdiff/"; my $ShortUsage = "Package Changes Analyzer (PkgDiff) $TOOL_VERSION A tool for visualizing changes in Linux software packages Copyright (C) 2016 Andrey Ponomarenko's ABI Laboratory License: GNU GPL Usage: $CmdName PKG1 PKG2 [options] Example: $CmdName OLD.rpm NEW.rpm More info: $CmdName --help\n"; if($#ARGV==-1) { printMsg("INFO", $ShortUsage); exit(0); } GetOptions("h|help!" => \$Help, "v|version!" => \$ShowVersion, "dumpversion!" => \$DumpVersion, # arguments "old=s" => \$Descriptor{1}, "new=s" => \$Descriptor{2}, # options "check-usage!" => \$CheckUsage, "pkg-manager=s" => \$PackageManager, "template!" => \$GenerateTemplate, "report-path=s" => \$OutputReportPath, "details!" => \$ShowDetails, "size-limit=s" => \$SizeLimit, "width=s" => \$DiffWidth, "prelines=s" => \$DiffLines, "ignore-space-change" => \$IgnoreSpaceChange, "ignore-all-space" => \$IgnoreAllSpace, "ignore-blank-lines" => \$IgnoreBlankLines, "quick!" => \$QuickMode, "minimal!" => \$Minimal, "extra-info=s" => \$ExtraInfo, "tmp-dir=s" => \$CustomTmpDir, "hide-unchanged!" => \$HideUnchanged, "debug!" => \$Debug, "v1|vnum1=s" => \$TargetVersion{1}, "v2|vnum2=s" => \$TargetVersion{2}, "name=s" => \$TargetName, "title=s" => \$TargetTitle, "d|directories!" => \$CompareDirs, "list-added-removed!" => \$ListAddedRemoved, "skip-subarchives!" => \$SkipSubArchives, "skip-pattern=s" => \$SkipPattern, "links-target=s" => \$LinksTarget ) or ERR_MESSAGE(); my $TMP_DIR = undef; if($CustomTmpDir) { printMsg("INFO", "using custom temp directory: $CustomTmpDir"); $TMP_DIR = abs_path($CustomTmpDir); mkpath($TMP_DIR); cleanTmp(); } else { $TMP_DIR = tempdir(CLEANUP=>1); } sub cleanTmp() { foreach ("null", "error", "unpack", "output", "fmt", "content1", "content2", "xcontent1", "xcontent2") { if(-f $TMP_DIR."/".$_) { unlink($TMP_DIR."/".$_); } elsif(-d $TMP_DIR."/".$_) { rmtree($TMP_DIR."/".$_); } } } if(@ARGV) { if($#ARGV==1) { # pkgdiff OLD.pkg NEW.pkg $Descriptor{1} = $ARGV[0]; $Descriptor{2} = $ARGV[1]; } else { ERR_MESSAGE(); } } sub ERR_MESSAGE() { printMsg("INFO", "\n".$ShortUsage); exit($ERROR_CODE{"Error"}); } my $HelpMessage=" NAME: Package Changes Analyzer A tool for analyzing changes in Linux software packages DESCRIPTION: Package Changes Analyzer (PkgDiff) is a tool for analyzing changes in Linux software packages (RPM, DEB, TAR.GZ, etc). The tool is intended for Linux maintainers who are interested in ensuring compatibility of old and new versions of packages. This tool is free software: you can redistribute it and/or modify it under the terms of the GNU GPL. USAGE: $CmdName PKG1 PKG2 [options] EXAMPLES: $CmdName OLD.rpm NEW.rpm $CmdName OLD.tar.gz NEW.tar.gz ARGUMENTS: PKG1 Path to the old version of a package (RPM, DEB, TAR.GZ, etc). If you need to analyze a group of packages then you can pass an XML-descriptor of this group (VERSION.xml file): /* Group version */ /* Group name */ /path1/to/package(s) /path2/to/package(s) ... PKG2 Path to the new version of a package (RPM, DEB, TAR.GZ, etc). INFORMATION OPTIONS: -h|-help Print this help. -v|-version Print version information. -dumpversion Print the tool version ($TOOL_VERSION) and don't do anything else. GENERAL OPTIONS: -report-path PATH Path to the report. Default: pkgdiff_reports//_to_/changes_report.html -details Try to create detailed reports. -size-limit SIZE Don't analyze files larger than SIZE in kilobytes. -width WIDTH Width of the Visual Diff. Default: 75 -prelines NUM Size of the context in the Visual Diff. Default: 10 -ignore-space-change Ignore changes in the amount of white space. -ignore-all-space Ignore all white space. -ignore-blank-lines Ignore changes whose lines are all blank. -quick Quick mode without creating of Visual Diffs for files. -minimal Try to find a smaller set of changes. OTHER OPTIONS: -check-usage Check if package content is used by other packages in the repository. -pkg-manager NAME Specify package management tool. Supported: urpm - Mandriva URPM -template Create XML-descriptor template ./VERSION.xml -extra-info DIR Dump extra info to DIR. -tmp-dir DIR Use custom temp directory. -hide-unchanged Don't show unchanged files in the report. -debug Show debug info. -name NAME Set name of the package to NAME. -title TITLE Set name of the package in the title of the report to TITLE. -vnum1 NUM Set version number of the old package to NUM. -vnum2 NUM Set version number of the new package to NUM. -links-target TARGET Set target attribute for links in the report: _self _blank (default) -list-added-removed Show content of added and removed text files. -skip-subarchives Skip checking of archives inside the input packages. -skip-pattern REGEX Skip checking of paths within archives matching REGEX. -d|-directories Compare directories instead of packages. REPORT: Report will be generated to: pkgdiff_reports//_to_/changes_report.html EXIT CODES: 0 - Unchanged. The tool has run without any errors. non-zero - Changed or the tool has run with errors. MORE INFORMATION: ".$HomePage."\n"; sub HELP_MESSAGE() { printMsg("INFO", $HelpMessage."\n"); } my $DescriptorTemplate = " /* Primary sections */ /* Version of a group of packages */ /* Name of a group of packages */ /* The list of paths to packages and/or directories with packages, one per line */ /* The list of files that should not be analyzed, one per line */ "; # Settings my $RENAME_FILE_MATCH = 0.55; my $RENAME_CONTENT_MATCH = 0.85; my $MOVE_CONTENT_MATCH = 0.90; my $MOVE_DEPTH = 4; my $DEFAULT_WIDTH = 75; my $DIFF_PRE_LINES = 10; my $EXACT_DIFF_SIZE = 256*1024; my $EXACT_DIFF_RATE = 0.1; my %Group = ( "Count1"=>0, "Count2"=>0 ); my %FormatInfo = (); my %FileFormat = (); my %TermFormat = (); my %DirFormat = (); my %BytesFormat = (); # Cache my %Cache; # Modes my $CheckMode = "Single"; # Packages my %TargetPackages; my %PackageFiles; my %PathName; my %FileChanges; my %PackageInfo; my %InfoChanges; my %PackageUsage; my %TotalUsage; my %RemovePrefix; # Deps my %PackageDeps; my %TotalDeps; my %DepChanges; # Files my %AddedFiles; my %RemovedFiles; my %ChangedFiles; my %StableFiles; my %RenamedFiles; my %RenamedFiles_R; my %MovedFiles; my %MovedFiles_R; my %ChangeRate; my %SkipFiles; # Symbols my %AddedSymbols; my %RemovedSymbols; # Report my $REPORT_PATH; my $REPORT_DIR; my %RESULT; my $STAT_LINE; # ABI my %ABI_Change; # Other my %ArchiveFormats = ( "TAR.GZ" => ["tar.gz", "tgz", "tar.Z", "taz"], "TAR.XZ" => ["tar.xz", "txz"], "TAR.BZ2" => ["tar.bz2", "tbz2", "tbz", "tb2"], "TAR.LZMA" => ["tar.lzma", "tlzma"], "TAR.LZ" => ["tar.lz", "tlz"], "ZIP" => ["zip", "zae"], "TAR" => ["tar"], "LZMA" => ["lzma"], "GZ" => ["gz"], "XZ" => ["xz"], "JAR" => ["jar", "war", "ear"] ); my $ARCHIVE_EXT = getArchivePattern(); sub get_Modules() { my $TOOL_DIR = get_dirname($0); if(not $TOOL_DIR) { $TOOL_DIR = "."; } my @SEARCH_DIRS = ( # tool's directory abs_path($TOOL_DIR), # relative path to modules abs_path($TOOL_DIR)."/../share/pkgdiff", # system directory 'MODULES_INSTALL_PATH' ); foreach my $DIR (@SEARCH_DIRS) { if($DIR!~/\A\//) { # relative path $DIR = abs_path($TOOL_DIR)."/".$DIR; } if(-d $DIR."/modules") { return $DIR."/modules"; } } exitStatus("Module_Error", "can't find modules"); } sub readModule($$) { my ($Module, $Name) = @_; my $Path = $MODULES_DIR."/Internals/$Module/".$Name; if(not -f $Path) { exitStatus("Module_Error", "can't access \'$Path\'"); } return readFile($Path); } sub readBytes($) { # ELF: 7f454c46 sysopen(FILE, $_[0], O_RDONLY); sysread(FILE, my $Header, 4); close(FILE); my @Bytes = map { sprintf('%02x', ord($_)) } split (//, $Header); return join("", @Bytes); } sub readSymbols($) { my $Path = $_[0]; my %Symbols = (); open(LIB, "readelf -WhlSsdA \"$Path\" 2>\"$TMP_DIR/null\" |"); my $symtab = undef; # indicates that we are processing 'symtab' section of 'readelf' output while() { if(defined $symtab) { # do nothing with symtab if(index($_, "'.dynsym'")!=-1) { # dynamic table $symtab = undef; } } elsif(index($_, "'.symtab'")!=-1) { # symbol table $symtab = 1; } elsif(my @Info = readline_ELF($_)) { my ($Bind, $Ndx, $Symbol) = ($Info[3], $Info[5], $Info[6]); if($Ndx ne "UND" and $Bind ne "WEAK") { # only imported symbols $Symbols{$Symbol} = 1; } } } close(LIB); return %Symbols; } my %ELF_BIND = map {$_=>1} ( "WEAK", "GLOBAL" ); my %ELF_TYPE = map {$_=>1} ( "FUNC", "IFUNC", "OBJECT", "COMMON" ); my %ELF_VIS = map {$_=>1} ( "DEFAULT", "PROTECTED" ); sub readline_ELF($) { # read the line of 'readelf' output corresponding to the symbol my @Info = split(/\s+/, $_[0]); # Num: Value Size Type Bind Vis Ndx Name # 3629: 000b09c0 32 FUNC GLOBAL DEFAULT 13 _ZNSt12__basic_fileIcED1Ev@@GLIBCXX_3.4 shift(@Info); # spaces shift(@Info); # num if($#Info!=6) { # other lines return (); } return () if(not defined $ELF_TYPE{$Info[2]}); return () if(not defined $ELF_BIND{$Info[3]}); return () if(not defined $ELF_VIS{$Info[4]}); if($Info[5] eq "ABS" and $Info[0]=~/\A0+\Z/) { # 1272: 00000000 0 OBJECT GLOBAL DEFAULT ABS CXXABI_1.3 return (); } if(index($Info[2], "0x") == 0) { # size == 0x3d158 $Info[2] = hex($Info[2]); } return @Info; } sub compareSymbols($$) { my ($P1, $P2) = @_; my %Symbols1 = readSymbols($P1); my %Symbols2 = readSymbols($P2); my $Changed = 0; foreach my $Symbol (keys(%Symbols1)) { if(not defined $Symbols2{$Symbol}) { $Changed = 1; if(defined $AddedSymbols{$Symbol}) { # moved delete($AddedSymbols{$Symbol}); } else { # removed $RemovedSymbols{$Symbol} = 1; } } } foreach my $Symbol (keys(%Symbols2)) { if(not defined $Symbols1{$Symbol}) { $Changed = 1; if(defined $RemovedSymbols{$Symbol}) { # moved delete($RemovedSymbols{$Symbol}) } else { # added $AddedSymbols{$Symbol} = 1; } } } return $Changed; } sub compareFiles($$$$) { my ($P1, $P2, $N1, $N2) = @_; if(not -f $P1 or not -f $P2) { if(not -l $P1) { # broken symlinks return (0, "", "", 0, {}); } } my $Format = getFormat($P1); if($Format ne getFormat($P2)) { return (0, "", "", 0, {}); } if(getSize($P1) == getSize($P2)) { # equal size if(compare($P1, $P2)==0) { # equal content return (-1, "", "", 0, {}); } } if($QuickMode) { # --quick return (3, "", "", 1, {}); } if(skipFileCompare($P1, 1)) { # return (2, "", "", 1, {}); } if(defined $SizeLimit) { if(getSize($P1) > $SizeLimit*1024 or getSize($P2) > $SizeLimit*1024) { return (2, "", "", 1, {}); } } my ($Changed, $DLink, $RLink, $Rate, $Adv) = (0, "", "", 0, {}); if(not $ShowDetails) { if($Format eq "SHARED_OBJECT" or $Format eq "KERNEL_MODULE" or $Format eq "DEBUG_INFO") { if(not compareSymbols($P1, $P2)) { # equal sets of symbols return (0, "", "", 0, {}); } } } if(defined $FormatInfo{$Format}{"Format"} and $FormatInfo{$Format}{"Format"} eq "Text") { ($DLink, $Rate) = diffFiles($P1, $P2, getRPath("diffs", $N1)); } elsif($Format eq "LICENSE" or $Format eq "CHANGELOG" or $Format eq "README" or $Format eq "INFORM") { if($P1=~/\.($ARCHIVE_EXT)\Z/i) { # changelog.Debian.gz my $Page1 = showFile($P1, "ARCHIVE", 1); my $Page2 = showFile($P2, "ARCHIVE", 2); ($DLink, $Rate) = diffFiles($Page1, $Page2, getRPath("diffs", $N1)); # clean space unlink($Page1); unlink($Page2); } else { ($DLink, $Rate) = diffFiles($P1, $P2, getRPath("diffs", $N1)); } } elsif($Format eq "SHARED_OBJECT" or $Format eq "KERNEL_MODULE" or $Format eq "DEBUG_INFO" or $Format eq "STATIC_LIBRARY" or $Format eq "COMPILED_OBJECT" or $Format eq "SHARED_LIBRARY" or $Format eq "EXE" or $Format eq "MANPAGE" or $Format eq "INFODOC" or $Format eq "SYMLINK" or $Format eq "JAVA_CLASS") { my $Page1 = showFile($P1, $Format, 1); my $Page2 = showFile($P2, $Format, 2); if($Format eq "SYMLINK") { if(readFile($Page1) eq readFile($Page2)) { # clean space unlink($Page1); unlink($Page2); return (0, "", "", 0, {}); } } ($DLink, $Rate) = diffFiles($Page1, $Page2, getRPath("diffs", $N1)); # clean space unlink($Page1); unlink($Page2); } else { $Changed = 1; $Rate = checkDiff($P1, $P2); } if($DLink or $Changed) { if($ShowDetails) { # --details if($ACC and $ABI_DUMPER) { if($Format eq "SHARED_OBJECT" or $Format eq "KERNEL_MODULE" or $Format eq "DEBUG_INFO" or $Format eq "STATIC_LIBRARY") { ($RLink, $Adv) = compareABIs($P1, $P2, $N1, $N2, getRPath("details", $N1)); } } } $DLink=~s/\A\Q$REPORT_DIR\E\///; $RLink=~s/\A\Q$REPORT_DIR\E\///; return (1, $DLink, $RLink, $Rate, $Adv); } return (0, "", "", 0, {}); } sub hexDump($) { my $Path = $_[0]; my ($Hex, $Byte) = (); open(FILE, "<", $Path); while(my $Size = read(FILE, $Byte, 16*1024)) { foreach my $Pos (0 .. $Size-1) { $Hex .= sprintf('%02x', ord(substr($Byte, $Pos, 1)))."\n"; } } close(FILE); return $Hex; } sub checkDiff($$) { my ($P1, $P2) = @_; my $Size1 = getSize($P1); if(not $Size1) { # empty return 1; } my $Size2 = getSize($P2); my $Rate = abs($Size1 - $Size2)/$Size1; my $AvgSize = ($Size1 + $Size2)/2; if($AvgSize<$EXACT_DIFF_SIZE and $Rate<$EXACT_DIFF_RATE) { if(-T $P1) { # Text my $TDiff = $TMP_DIR."/txtdiff"; system("diff -Bw \"$P1\" \"$P2\" >$TDiff 2>$TMP_DIR/null"); $Rate = getRate($P1, $P2, $TDiff); unlink($TDiff); } else { # Binary my $TDiff = $TMP_DIR."/txtdiff"; my $T1 = $TMP_DIR."/tmp1.txt"; my $T2 = $TMP_DIR."/tmp2.txt"; writeFile($T1, hexDump($P1)); writeFile($T2, hexDump($P2)); system("diff -Bw \"$T1\" \"$T2\" >$TDiff 2>$TMP_DIR/null"); unlink($T1); unlink($T2); $Rate = getRate($P1, $P2, $TDiff); unlink($TDiff); } } if($Rate>1) { $Rate=1; } return $Rate; } sub showFile($$$) { my ($Path, $Format, $Version) = @_; my ($Dir, $Name) = separate_path($Path); my $Cmd = ""; if($Format eq "MANPAGE") { $Name=~s/\.(gz|bz2|xz)\Z//; $Cmd = "man \"$Path\" 2>&1|col -bfx"; } elsif($Format eq "INFODOC") { $Name=~s/\.(gz|bz2|xz)\Z//; $Path=~s/\.(gz|bz2|xz)\Z//; $Cmd = "info \"$Path\""; } elsif($Format eq "ARCHIVE") { my $Unpack = $TMP_DIR."/unpack/"; rmtree($Unpack); unpackArchive($Path, $Unpack); my @Contents = listDir($Unpack); if($#Contents==0) { $Cmd = "cat \"$Unpack/".$Contents[0]."\""; } else { return ""; } } elsif($Format eq "SHARED_OBJECT" or $Format eq "KERNEL_MODULE" or $Format eq "DEBUG_INFO" or $Format eq "EXE" or $Format eq "COMPILED_OBJECT" or $Format eq "STATIC_LIBRARY") { $Cmd = "readelf -Wa \"$Path\""; } elsif($Format eq "SHARED_LIBRARY") { $Cmd = "otool -TVL \"$Path\""; } elsif($Format eq "SYMLINK") { $Cmd = "file -b \"$Path\""; } elsif($Format eq "JAVA_CLASS") { if(not check_Cmd("javap")) { return ""; } $Name=~s/\.class\Z//; $Name=~s/\$/./; $Path = $Name; $Cmd = "javap \"$Path\""; # -s -c -private -verbose chdir($Dir); } my $SPath = $TMP_DIR."/fmt/".$Format."/".$Version."/".$Name; mkpath(get_dirname($SPath)); system($Cmd." >\"".$SPath."\" 2>$TMP_DIR/null"); if($Format eq "JAVA_CLASS") { chdir($ORIG_DIR); } if($Format eq "SHARED_OBJECT" or $Format eq "KERNEL_MODULE" or $Format eq "DEBUG_INFO" or $Format eq "EXE" or $Format eq "COMPILED_OBJECT" or $Format eq "STATIC_LIBRARY") { my $Content = readFile($SPath); # 00bf608c 00000008 R_386_RELATIVE # 00bf608c 00000008 R_386_NONE $Content=~s/[0-9a-f]{8}\s+[0-9a-f]{8}\s+R_386_(RELATIVE|NONE)\s*//g; # 0000000000210028 0000000000000008 R_X86_64_RELATIVE 0000000000210028 $Content=~s/[0-9a-f]{16}\s+[0-9a-f]{16}\s+R_X86_64_RELATIVE\s+[0-9a-f]{16}\s*//g; # 00be77ec 0001d507 R_386_JUMP_SLOT 00000000 dlclose # 0000000000210348 0000001800000007 R_X86_64_JUMP_SLOT 0000000000000000 mq_receive + 0 $Content=~s/\n([0-9a-f]{8}|[0-9a-f]{16})\s+([0-9a-f]{8}|[0-9a-f]{16}) /\nXXX YYY /g; $Content=~s/ [0-9a-f]{16} / ZZZ /g; # 563: 00000000 0 FUNC GLOBAL DEFAULT UND FT_New_Face # 17: 0000000000000000 0 FUNC GLOBAL DEFAULT UND sem_trywait@GLIBC_2.2.5 (2) $Content=~s/\n\s*\d+:(\s+[0-9a-f]{8}|\s+[0-9a-f]{16})\s+\d+\s+/\nN: XXX W /g; $Content=~s/\Q$Dir\E\///g; # Build ID: 88a916b3973e1a27b027706385af41c553a94061 $Content=~s/\s+Build ID: \w+\s+//g; writeFile($SPath, uniqStr($Content)); } return $SPath; } sub uniqStr($) { my $Str = $_[0]; my ($Prev, $Res) = ("", ""); foreach my $Line (split(/\n/, $Str)) { if($Line ne $Prev) { $Prev = $Line; $Res .= $Line."\n"; } } return $Res; } sub getRPath($$) { my ($Prefix, $N) = @_; $N=~s/\A\///g; my $RelPath = $Prefix."/".$N."-diff.html"; my $Path = $REPORT_DIR."/".$RelPath; return $Path; } sub compareABIs($$$$$) { my ($P1, $P2, $N1, $N2, $Path) = @_; my $Sect = `readelf -S \"$P1\" 2>\"$TMP_DIR/error\"`; my $Name = get_filename($P1); if($Sect!~/\.debug_info/) { # No DWARF info printMsg("WARNING", "No debug info in ".$Name); return ("", {}); } mkpath(get_dirname($Path)); my $Adv = {}; $Name=~s/\.debug\Z//; printMsg("INFO", "Compare ABIs of ".$Name." (".show_number(getSize($P1)/1048576)."M) ..."); $N1=~s/\A\///; $N2=~s/\A\///; my $Cmd = undef; my $Ret = undef; my $D1 = $REPORT_DIR."/abi_dumps/".$Group{"V1"}."/".$N1."-ABI.dump"; my $D2 = $REPORT_DIR."/abi_dumps/".$Group{"V2"}."/".$N2."-ABI.dump"; $Adv->{"ABIDump"}{1} = $D1; $Adv->{"ABIDump"}{2} = $D2; $Adv->{"ABIDump"}{1}=~s/\A\Q$REPORT_DIR\E\///; $Adv->{"ABIDump"}{2}=~s/\A\Q$REPORT_DIR\E\///; $Cmd = $ABI_DUMPER." \"$P1\" -lver \"".$Group{"V1"}."\" -o \"$D1\" -sort"; if($Debug) { $Cmd .= " -extra-info \"$TMP_DIR/extra-info\""; printMsg("INFO", "Running $Cmd"); } system($Cmd." >\"$TMP_DIR/output\""); $Ret = $?>>8; if($Ret!=0) { # error printMsg("ERROR", "Failed to run ABI Dumper ($Ret)"); return ("", {}); } if($Debug) { my $DP = $REPORT_DIR."/dwarf_dumps/".$Group{"V1"}."/".$N1."-DWARF.dump"; mkpath(get_dirname($DP)); move("$TMP_DIR/extra-info/debug_info", $DP); $Adv->{"DWARFDump"}{1} = $DP; $Adv->{"DWARFDump"}{1}=~s/\A\Q$REPORT_DIR\E\///; } $Cmd = $ABI_DUMPER." \"$P2\" -lver \"".$Group{"V2"}."\" -o \"$D2\" -sort"; if($Debug) { $Cmd .= " -extra-info \"$TMP_DIR/extra-info\""; printMsg("INFO", "Running $Cmd"); } system($Cmd." >\"$TMP_DIR/output\""); $Ret = $?>>8; if($Ret!=0) { # error printMsg("ERROR", "Failed to run ABI Dumper ($Ret)"); return ("", {}); } if($Debug) { my $DP = $REPORT_DIR."/dwarf_dumps/".$Group{"V2"}."/".$N2."-DWARF.dump"; mkpath(get_dirname($DP)); move("$TMP_DIR/extra-info/debug_info", $DP); $Adv->{"DWARFDump"}{2} = $DP; $Adv->{"DWARFDump"}{2}=~s/\A\Q$REPORT_DIR\E\///; } # clean space rmtree("$TMP_DIR/extra-info"); $Cmd = $ACC." -d1 \"$D1\" -d2 \"$D2\""; $Cmd .= " -l \"".$Name."\""; $Cmd .= " --report-path=\"$Path\""; $Cmd .= " -quiet"; if($Debug) { printMsg("INFO", "Running $Cmd"); } system($Cmd); $Ret = $?>>8; if($Ret!=0 and $Ret!=1) { # error printMsg("ERROR", "Failed to run ABI Compliance Checker ($Ret)"); return ("", {}); } my ($Bin, $Src) = (0, 0); if(my $Meta = readFilePart($Path, 2)) { my @Info = split(/\n/, $Meta); if($Info[0]=~/affected:([\d\.]+)/) { $Bin = $1; } if($Info[1]=~/affected:([\d\.]+)/) { $Src = $1; } } $ABI_Change{"Bin"} += $Bin; $ABI_Change{"Src"} += $Src; $ABI_Change{"Total"} += 1; return ($Path, $Adv); } sub getSize($) { my $Path = $_[0]; if(not $Path) { return 0; } if($Cache{"getSize"}{$Path}) { return $Cache{"getSize"}{$Path}; } if(-l $Path) { # symlinks return ($Cache{"getSize"}{$Path} = length(`file -b \"$Path\"`)); } return ($Cache{"getSize"}{$Path} = -s $Path); } sub diffFiles($$$) { my ($P1, $P2, $Path) = @_; if(not $P1 or not $P2) { return ""; } mkpath(get_dirname($Path)); my $TmpPath = $TMP_DIR."/diff"; unlink($TmpPath); my $Cmd = "sh $DIFF --width $DiffWidth --stdout"; $Cmd .= " --tmpdiff \"$TmpPath\" --prelines $DiffLines"; if($IgnoreSpaceChange) { $Cmd .= " --ignore-space-change"; } if($IgnoreAllSpace) { $Cmd .= " --ignore-all-space"; } if($IgnoreBlankLines) { $Cmd .= " --ignore-blank-lines"; } if($Minimal) { # diff --minimal $Cmd .= " --minimal"; } $Cmd .= " \"".$P1."\" \"".$P2."\" >\"".$Path."\" 2>$TMP_DIR/null"; $Cmd=~s/\$/\\\$/g; system($Cmd); if(getSize($Path)<3500) { # may be identical if(readFilePart($Path, 2)=~/The files are identical/) { unlink($Path); return ""; } } if(getSize($Path)<3100) { # may be identical or non-text if(index(readFile($Path), "No changes")!=-1) { unlink($Path); return ""; } } my $Rate = getRate($P1, $P2, $TmpPath); # clean space unlink($TmpPath); return ($Path, $Rate); } sub getRate($$$) { my ($P1, $P2, $PatchPath) = @_; my $Size1 = getSize($P1); if(not $Size1) { return 1; } my $Size2 = getSize($P2); my $Rate = 1; # count removed/changed bytes my $Patch = readFile($PatchPath); $Patch=~s/(\A|\n)([^\-]|[\+]{3}|[\-]{3}).*//g; $Rate = length($Patch); # count added bytes if($Size2>$Size1) { $Rate += $Size2-$Size1; } $Rate /= $Size1; if($Rate>1) { $Rate=1; } return $Rate; } sub readFilePart($$) { my ($Path, $Num) = @_; return "" if(not $Path or not -f $Path); open (FILE, $Path); my $Lines = ""; foreach (1 ... $Num) { $Lines .= ; } close(FILE); return $Lines; } sub getType($) { my $Path = $_[0]; if(not $Path or not -e $Path) { return ""; } if($Cache{"getType"}{$Path}) { return $Cache{"getType"}{$Path}; } return ($Cache{"getType"}{$Path} = `file -b \"$Path\"`); } sub isRenamed($$$) { my ($P1, $P2, $Match) = @_; my ($D1, $N1) = separate_path($P1); my ($D2, $N2) = separate_path($P2); if($D1 ne $D2) { return 0; } if($N1 eq $N2) { return 0; } my $L1 = length($N1); my $L2 = length($N2); if($L1<=8) { # too short names if($N1=~/\.(\w+)\Z/) { # with equal extensions my $E = $1; if($N2=~s/\.\Q$E\E\Z//g) { # compare without extensions $N1=~s/\.\Q$E\E\Z//g; } } } $Match/=$RENAME_FILE_MATCH; my $HL = ($L1+$L2)/$Match; return (getBaseLen($N1, $N2)>=$HL); } sub minNum($$) { if($_[0]<$_[1]) { return $_[0]; } else { return $_[1]; } } sub get_depth($) { return ($_[0]=~tr![\/]!!); } sub getBaseLen($$) { my ($Str1, $Str2) = @_; if(defined $Cache{"getBaseLen"}{$Str1}{$Str2}) { return $Cache{"getBaseLen"}{$Str1}{$Str2}; } if($Str1 eq $Str2) { return length($Str1); } my $BLen = 0; my $Len1 = length($Str1); my $Len2 = length($Str2); my $Min = minNum($Len1, $Len2) - 1; foreach my $Pos (0 .. $Min) { my $S1 = substr($Str1, $Pos, 1); my $S2 = substr($Str2, $Pos, 1); if($S1 eq $S2) { $BLen+=1; } else { last; } } foreach my $Pos (0 .. $Min) { my $S1 = substr($Str1, $Len1-$Pos-1, 1); my $S2 = substr($Str2, $Len2-$Pos-1, 1); if($S1 eq $S2) { $BLen+=1; } else { last; } } return ($Cache{"getBaseLen"}{$Str1}{$Str2}=$BLen); } sub isMoved($$) { my ($P1, $P2) = @_; my ($D1, $N1) = separate_path($P1); my ($D2, $N2) = separate_path($P2); if($N1 eq $N2 and $D1 ne $D2) { return 1; } return 0; } sub writeExtraInfo() { my $FILES = ""; $FILES .= "\n ".$RESULT{"affected"}."\n\n\n"; if(my @Added = sort {lc($a) cmp lc($b)} keys(%AddedFiles)) { $FILES .= "\n ".join("\n ", @Added)."\n\n\n"; } if(my @Removed = sort {lc($a) cmp lc($b)} keys(%RemovedFiles)) { $FILES .= "\n ".join("\n ", @Removed)."\n\n\n"; } if(my @Moved = sort {lc($a) cmp lc($b)} keys(%MovedFiles)) { $FILES .= "\n"; foreach (@Moved) { $FILES .= " ".$_.";".$MovedFiles{$_}." (".show_number($ChangeRate{$_}*100)."%)\n"; } $FILES .= "\n\n"; } if(my @Renamed = sort {lc($a) cmp lc($b)} keys(%RenamedFiles)) { $FILES .= "\n"; foreach (@Renamed) { $FILES .= " ".$_.";".$RenamedFiles{$_}." (".show_number($ChangeRate{$_}*100)."%)\n"; } $FILES .= "\n\n"; } if(my @Changed = sort {lc($a) cmp lc($b)} keys(%ChangedFiles)) { foreach (0 .. $#Changed) { $Changed[$_] .= " (".show_number($ChangeRate{$Changed[$_]}*100)."%)"; } $FILES .= "\n ".join("\n ", @Changed)."\n\n\n"; } writeFile($ExtraInfo."/files.xml", $FILES); my $SYMBOLS = ""; if(my @AddedSymbols = sort {lc($a) cmp lc($b)} keys(%AddedSymbols)) { $SYMBOLS .= "\n ".join("\n ", @AddedSymbols)."\n\n\n"; } if(my @RemovedSymbols = sort {lc($a) cmp lc($b)} keys(%RemovedSymbols)) { $SYMBOLS .= "\n ".join("\n ", @RemovedSymbols)."\n\n\n"; } writeFile($ExtraInfo."/symbols.xml", $SYMBOLS); } sub skipFile($) { my $Name = $_[0]; if(defined $SkipPattern) { if($Name=~/($SkipPattern)/) { printMsg("INFO", "skipping (pattern match) ".$Name); return 1; } } return 0; } sub detectChanges() { mkpath($REPORT_DIR."/diffs"); mkpath($REPORT_DIR."/info-diffs"); mkpath($REPORT_DIR."/details"); foreach my $Format (keys(%FormatInfo)) { %{$FileChanges{$Format}} = ( "Total"=>0, "Added"=>0, "Removed"=>0, "Changed"=>0, "Size"=>0, "SizeDelta"=>0 ); } my (%AddedByDir, %RemovedByDir, %AddedByName, %RemovedByName, %AddedByPrefix, %RemovedByPrefix) = (); foreach my $Name (sort keys(%{$PackageFiles{1}})) { # checking old files my $Format = getFormat($PackageFiles{1}{$Name}); if(not defined $PackageFiles{2}{$Name}) { # removed files $RemovedFiles{$Name}=1; $RemovedByDir{get_dirname($Name)}{$Name}=1; $RemovedByName{get_filename($Name)}{$Name}=1; foreach (get_Prefixes($Name, $MOVE_DEPTH)) { $RemovedByPrefix{$_}{$Name} = 1; } } else { $StableFiles{$Name}=1; } } foreach my $Name (keys(%{$PackageFiles{2}})) { # checking new files my $Format = getFormat($PackageFiles{2}{$Name}); if(not defined $PackageFiles{1}{$Name}) { # added files $AddedFiles{$Name}=1; $AddedByDir{get_dirname($Name)}{$Name}=1; $AddedByName{get_filename($Name)}{$Name}=1; foreach (get_Prefixes($Name, $MOVE_DEPTH)) { $AddedByPrefix{$_}{$Name}=1; } } } foreach my $Name (sort keys(%RemovedFiles)) { # checking removed files my $Path = $PackageFiles{1}{$Name}; my $Format = getFormat($Path); $FileChanges{$Format}{"Total"} += 1; $FileChanges{$Format}{"Removed"} += 1; if(my $Size = getSize($Path)) { $FileChanges{$Format}{"SizeDelta"} += $Size; $FileChanges{$Format}{"Size"} += $Size; } $FileChanges{$Format}{"Details"}{$Name}{"Status"} = "removed"; } foreach my $Name (sort {get_depth($b)<=>get_depth($a)} sort keys(%RemovedFiles)) { # checking moved files my $Format = getFormat($PackageFiles{1}{$Name}); my $FileName = get_filename($Name); my @Removed = keys(%{$RemovedByName{$FileName}}); my @Added = keys(%{$AddedByName{$FileName}}); my @Removed = grep {not defined $MovedFiles{$_}} @Removed; my @Added = grep {not defined $MovedFiles_R{$_}} @Added; if($#Added!=0 or $#Removed!=0) { my $Found = 0; foreach my $Prefix (get_Prefixes($Name, $MOVE_DEPTH)) { my @RemovedPrefix = keys(%{$RemovedByPrefix{$Prefix}}); my @AddedPrefix = keys(%{$AddedByPrefix{$Prefix}}); my @RemovedPrefix = grep {not defined $MovedFiles{$_}} @RemovedPrefix; my @AddedPrefix = grep {not defined $MovedFiles_R{$_}} @AddedPrefix; if($#AddedPrefix==0 and $#RemovedPrefix==0) { @Added = @AddedPrefix; $Found = 1; } } if(not $Found) { next; } } foreach my $File (@Added) { if($Format ne getFormat($PackageFiles{2}{$File})) { # different formats next; } if(defined $MovedFiles_R{$File}) { next; } if(isMoved($Name, $File)) { $MovedFiles{$Name} = $File; $MovedFiles_R{$File} = $Name; last; } } } foreach my $Name (sort keys(%RemovedFiles)) { # checking renamed files if(defined $MovedFiles{$Name}) { # moved next; } my $Format = getFormat($PackageFiles{1}{$Name}); my @Removed = keys(%{$RemovedByDir{get_dirname($Name)}}); my @Added = keys(%{$AddedByDir{get_dirname($Name)}}); my $Match = 2; if($#Removed==0 and $#Added==0) { $Match *= 2; } my $FName = get_filename($Name); my $Len = length($FName); foreach my $File (sort {getBaseLen($FName, get_filename($b)) <=> getBaseLen($FName, get_filename($a))} sort { abs(length(get_filename($a))-$Len) <=> abs(length(get_filename($b))-$Len) } @Added) { if($Format ne getFormat($PackageFiles{2}{$File})) { # different formats next; } if(defined $RenamedFiles_R{$File} or defined $MovedFiles_R{$File}) { # renamed or moved next; } if(isRenamed($Name, $File, $Match)) { $RenamedFiles{$Name} = $File; $RenamedFiles_R{$File} = $Name; last; } } } foreach my $Name (sort (keys(%StableFiles), keys(%RenamedFiles), keys(%MovedFiles))) { # checking files my $Path = $PackageFiles{1}{$Name}; my $Size = getSize($Path); if($Debug) { printMsg("INFO", $Name); } my ($NewPath, $NewName) = ($PackageFiles{2}{$Name}, $Name); my $Format = getFormat($Path); if($StableFiles{$Name}) { # stable files $FileChanges{$Format}{"Total"} += 1; $FileChanges{$Format}{"Size"} += $Size; } elsif($NewName = $RenamedFiles{$Name}) { # renamed files $NewPath = $PackageFiles{2}{$NewName}; } elsif($NewName = $MovedFiles{$Name}) { # moved files $NewPath = $PackageFiles{2}{$NewName}; } my ($Changed, $DLink, $RLink, $Rate, $Adv) = compareFiles($Path, $NewPath, $Name, $NewName); my %Details = %{$Adv}; if($Changed==1 or $Changed==3) { if($NewName eq $Name) { # renamed and moved files should # not be shown in the summary $FileChanges{$Format}{"Changed"} += 1; $FileChanges{$Format}{"Rate"} += $Rate; $FileChanges{$Format}{"SizeDelta"} += $Size*$Rate; } $Details{"Status"} = "changed"; if($Changed==1) { $Details{"Rate"} = $Rate; $Details{"Diff"} = $DLink; $Details{"Report"} = $RLink; $ChangeRate{$Name} = $Rate; } $ChangedFiles{$Name} = 1; } elsif($Changed==2) { $Details{"Status"} = "changed"; $Details{"Skipped"} = 1; } elsif($Changed==-1) { $Details{"Status"} = "unchanged"; $Details{"Empty"} = 1; $Details{"Rate"} = 0; } else { $Details{"Status"} = "unchanged"; $Details{"Rate"} = 0; } if($NewName = $RenamedFiles{$Name}) { # renamed files if($Rate<$RENAME_CONTENT_MATCH) { $Details{"Status"} = "renamed"; } else { %Details = ( "Status"=>"removed" ); delete($RenamedFiles_R{$RenamedFiles{$Name}}); delete($RenamedFiles{$Name}); delete($ChangedFiles{$Name}); unlink($REPORT_DIR."/".$DLink); } } elsif($NewName = $MovedFiles{$Name}) { # moved files if($Rate<$MOVE_CONTENT_MATCH) { $Details{"Status"} = "moved"; } else { %Details = ( "Status"=>"removed" ); delete($MovedFiles_R{$MovedFiles{$Name}}); delete($MovedFiles{$Name}); delete($ChangedFiles{$Name}); unlink($REPORT_DIR."/".$DLink); } } %{$FileChanges{$Format}{"Details"}{$Name}} = %Details; } foreach my $Name (keys(%AddedFiles)) { # checking added files my $Path = $PackageFiles{2}{$Name}; my $Format = getFormat($Path); $FileChanges{$Format}{"Total"} += 1; $FileChanges{$Format}{"Added"} += 1; if(my $Size = getSize($Path)) { $FileChanges{$Format}{"SizeDelta"} += $Size; $FileChanges{$Format}{"Size"} += $Size; } $FileChanges{$Format}{"Details"}{$Name}{"Status"} = "added"; } # Deps foreach my $Kind (keys(%{$PackageDeps{1}})) { # removed/changed deps %{$DepChanges{$Kind}} = ( "Added"=>0, "Removed"=>0, "Changed"=>0, "Total"=>0, "Size"=>0, "SizeDelta"=>0 ); foreach my $Name (keys(%{$PackageDeps{1}{$Kind}})) { my $Size = length($Name); $DepChanges{$Kind}{"Total"} += 1; $DepChanges{$Kind}{"Size"} += $Size; if(not defined($PackageDeps{2}{$Kind}) or not defined($PackageDeps{2}{$Kind}{$Name})) { # removed deps $DepChanges{$Kind}{"Details"}{$Name}{"Status"} = "removed"; $DepChanges{$Kind}{"Removed"} += 1; $DepChanges{$Kind}{"SizeDelta"} += $Size; next; } my %Info1 = %{$PackageDeps{1}{$Kind}{$Name}}; my %Info2 = %{$PackageDeps{2}{$Kind}{$Name}}; if($Info1{"Op"} and $Info1{"V"} and ($Info1{"Op"} ne $Info2{"Op"} or $Info1{"V"} ne $Info2{"V"})) { $DepChanges{$Kind}{"Details"}{$Name}{"Status"} = "changed"; $DepChanges{$Kind}{"Changed"} += 1; $DepChanges{$Kind}{"SizeDelta"} += $Size; } else { $DepChanges{$Kind}{"Details"}{$Name}{"Status"} = "unchanged"; } } } foreach my $Kind (keys(%{$PackageDeps{2}})) { # added deps foreach my $Name (keys(%{$PackageDeps{2}{$Kind}})) { if(not defined($PackageDeps{1}{$Kind}) or not defined($PackageDeps{1}{$Kind}{$Name})) { $DepChanges{$Kind}{"Total"} += 1; $DepChanges{$Kind}{"Details"}{$Name}{"Status"} = "added"; $DepChanges{$Kind}{"Added"} += 1; if(my $Size = length($Name)) { $DepChanges{$Kind}{"Size"} += $Size; $DepChanges{$Kind}{"SizeDelta"} += $Size; } } } } # Info %InfoChanges = ( "Added"=>0, "Removed"=>0, "Changed"=>0, "Total"=>0, "Size"=>0, "SizeDelta"=>0 ); my $OldPkgs = keys(%{$TargetPackages{1}}); my $NewPkgs = keys(%{$TargetPackages{2}}); if(keys(%PackageInfo)==2 and $OldPkgs==1 and $NewPkgs==1) { # renamed? my @Names = keys(%PackageInfo); my $N1 = $Names[0]; my $N2 = $Names[1]; if(defined $PackageInfo{$N1}{"V2"}) { $PackageInfo{$N2}{"V2"} = $PackageInfo{$N1}{"V2"}; delete($PackageInfo{$N1}); } elsif(defined $PackageInfo{$N2}{"V2"}) { $PackageInfo{$N1}{"V2"} = $PackageInfo{$N2}{"V2"}; delete($PackageInfo{$N2}); } } foreach my $Package (sort keys(%PackageInfo)) { my $Old = $PackageInfo{$Package}{"V1"}; my $New = $PackageInfo{$Package}{"V2"}; my $OldSize = length($Old); my $NewSize = length($New); $InfoChanges{"Total"} += 1; if($Old and not $New) { $InfoChanges{"Details"}{$Package}{"Status"} = "removed"; $InfoChanges{"Removed"} += 1; $InfoChanges{"Size"} += $OldSize; $InfoChanges{"SizeDelta"} += $OldSize; } elsif(not $Old and $New) { $InfoChanges{"Details"}{$Package}{"Status"} = "added"; $InfoChanges{"Added"} += 1; $InfoChanges{"Size"} += $NewSize; $InfoChanges{"SizeDelta"} += $NewSize; } elsif($Old ne $New) { my $P1 = $TMP_DIR."/1/".$Package."-info"; my $P2 = $TMP_DIR."/2/".$Package."-info"; writeFile($P1, $Old); writeFile($P2, $New); my ($DLink, $Rate) = diffFiles($P1, $P2, getRPath("info-diffs", $Package."-info")); # clean space rmtree($TMP_DIR."/1/"); rmtree($TMP_DIR."/2/"); $DLink =~s/\A\Q$REPORT_DIR\E\///; my %Details = (); $Details{"Status"} = "changed"; $Details{"Rate"} = $Rate; $Details{"Diff"} = $DLink; %{$InfoChanges{"Details"}{$Package}} = %Details; $InfoChanges{"Changed"} += 1; $InfoChanges{"Rate"} += $Rate; $InfoChanges{"Size"} += $OldSize; $InfoChanges{"SizeDelta"} += $OldSize*$Rate; } else { $InfoChanges{"Details"}{$Package}{"Status"} = "unchanged"; $InfoChanges{"Size"} += $OldSize; $InfoChanges{"SizeDelta"} += $OldSize; } } $STAT_LINE .= "added:".keys(%AddedFiles).";"; $STAT_LINE .= "removed:".keys(%RemovedFiles).";"; $STAT_LINE .= "moved:".keys(%MovedFiles).";"; $STAT_LINE .= "renamed:".keys(%RenamedFiles).";"; $STAT_LINE .= "changed:".keys(%ChangedFiles).";"; } sub htmlSpecChars($) { my $Str = $_[0]; $Str=~s/\&([^#])/&$1/g; $Str=~s//>/g; $Str=~s/\"/"/g; $Str=~s/\'/'/g; return $Str; } sub get_Report_Usage() { if(not keys(%PackageUsage)) { return ""; } my $Report = "
\n"; $Report .= "

Usage Analysis


\n"; $Report .= "\n"; $Report .= "\n"; foreach my $Package (sort keys(%PackageUsage)) { my $Num = keys(%{$PackageUsage{$Package}{"UsedBy"}}); $Report .= "\n"; $Report .= "\n"; if($Num) { $Report .= "\n"; if($Num==1) { $Report .= "\n"; } else { $Report .= "\n"; } } else { $Report .= "\n"; $Report .= "\n"; } $Report .= "\n"; } $Report .= "
PackageStatusUsed By
$Packageused$Num package$Num packagesunused
\n"; return $Report; } sub get_Report_Headers() { if(not keys(%PackageInfo)) { return ""; } my $Report = "\n"; $Report .= "

Changes In Package Info


\n"; $Report .= "\n"; $Report .= "\n"; my %Details = %{$InfoChanges{"Details"}}; foreach my $Package (sort keys(%Details)) { my $Status = $Details{$Package}{"Status"}; $Report .= "\n"; if($Status eq "removed") { $Report .= "\n"; $Report .= "\n"; } elsif($Status eq "added") { $Report .= "\n"; $Report .= "\n"; } else { $Report .= "\n"; if($Status eq "changed") { $Report .= "\n"; $Report .= "\n"; $Report .= "\n"; # style='color:Blue;' } else { $Report .= "\n"; $Report .= "\n"; } } $Report .= "\n"; } $Report .= "
PackageStatusDeltaVisual Diff
$Packageremoved$Packageadded$Packagechanged".show_number($Details{$Package}{"Rate"}*100)."%diffunchanged0%
\n"; return $Report; } sub get_Report_Deps() { my $Report = "\n"; foreach my $Kind (sort keys(%DepChanges)) { my @Names = keys(%{$DepChanges{$Kind}{"Details"}}); if(not @Names) { next; } $Report .= "

Changes In \"".ucfirst($Kind)."\" Dependencies


\n"; $Report .= "\n"; $Report .= "\n"; foreach my $Name (sort {lc($a) cmp lc($b)} @Names) { my $Status = $DepChanges{$Kind}{"Details"}{$Name}{"Status"}; my $Color = ""; if($Status eq "removed") { $Color = " failed"; } elsif($Status eq "added") { $Color = " new"; } $Report .= "\n"; $Report .= "\n"; if($Status eq "changed") { $Report .= "\n"; } elsif($Status eq "removed") { $Report .= "\n"; } elsif($Status eq "added") { $Report .= "\n"; } else { $Report .= "\n"; } if($PackageDeps{1}{$Kind}{$Name}) { my %Info1 = %{$PackageDeps{1}{$Kind}{$Name}}; $Report .= "\n"; } else { $Report .= "\n"; } if($PackageDeps{2}{$Kind}{$Name}) { my %Info2 = %{$PackageDeps{2}{$Kind}{$Name}}; $Report .= "\n"; } else { $Report .= "\n"; } $Report .= "\n"; } $Report .= "
NameStatusOld
Version
New
Version
$Name".$Status."".$Status."".$Status."".$Status."".htmlSpecChars(showOp($Info1{"Op"}).$Info1{"V"})."".htmlSpecChars(showOp($Info2{"Op"}).$Info2{"V"})."
\n"; } return $Report; } sub showOp($) { my $Op = $_[0]; #$Op=~s/<=/≤/g; #$Op=~s/>=/≥/g; if($Op eq "=") { # do not show "=" $Op=""; } if($Op) { $Op = $Op." "; } return $Op; } sub createFileView($$$) { my ($File, $V, $Dir) = @_; my $Path = $PackageFiles{$V}{$File}; if(not -T $Path) { return undef; } my $Name = get_filename($File); my $Content = readFile($Path); my $CssStyles = readModule("Styles", "View.css"); $Content = htmlSpecChars($Content); if($Name=~/\.patch\Z/) { while($Content=~s&(\A|\n)(\+.*?)(\n|\Z)&$1$2$3&mg){}; while($Content=~s&(\A|\n)(\-.*?)(\n|\Z)&$1$2$3&mg){}; } $Content = "
".$Content."
\n"; $Content = "\n\n\n\n\n\n\n
\n".$Name."plain
\n".$Content."
\n"; $Content = composeHTML_Head($Name, "", "View file ".$File, $CssStyles, "")."\n\n".$Content; $Content .= ""; my $R = $Dir."/".$File."-view.html"; writeFile($REPORT_DIR."/".$R, $Content); # plain copy copy($Path, $REPORT_DIR."/".$Dir."/".get_dirname($File)."/"); return $R; } sub get_Report_Files() { my $Report = ""; my $JSort = "title='sort' onclick='javascript:sort(this, 1)' style='cursor:pointer'"; foreach my $Format (sort {$FormatInfo{$b}{"Weight"}<=>$FormatInfo{$a}{"Weight"}} sort {lc($FormatInfo{$a}{"Summary"}) cmp lc($FormatInfo{$b}{"Summary"})} keys(%FileChanges)) { my $Total = $FileChanges{$Format}{"Total"}; if($HideUnchanged) { $Total = $FileChanges{$Format}{"Added"} + $FileChanges{$Format}{"Removed"} + $FileChanges{$Format}{"Changed"}; } if(not $Total) { next; } if($HideUnchanged) { if(not $Total) { # do not show unchanged files next; } $FileChanges{$Format}{"Total"} = $Total; } $Report .= "\n"; $Report .= "

".$FormatInfo{$Format}{"Title"}." (".$FileChanges{$Format}{"Total"}.")


\n"; $Report .= "\n"; $Report .= "\n"; $Report .= "\n"; $Report .= "\n"; if($Format ne "DIR") { $Report .= "\n"; $Report .= "\n"; if($ShowDetails) { $Report .= "\n"; if($Format eq "SHARED_OBJECT" or $Format eq "KERNEL_MODULE" or $Format eq "DEBUG_INFO" or $Format eq "STATIC_LIBRARY") { $Report .= "\n"; if($Debug) { $Report .= "\n"; } } } } $Report .= "\n"; my %Details = %{$FileChanges{$Format}{"Details"}}; foreach my $File (sort {lc($a) cmp lc($b)} keys(%Details)) { if($RenamedFiles_R{$File} or $MovedFiles_R{$File}) { next; } my %Info = %{$Details{$File}}; if($HideUnchanged) { if($Info{"Status"} eq "unchanged") { # do not show unchanged files next; } } my ($Join, $Color1, $Color2) = ("", "", ""); if($Info{"Status"} eq "renamed" or $Info{"Status"} eq "moved") { $Join = " rowspan='2'"; $Color1 = " failed"; $Color2 = " new"; } elsif($Info{"Status"} eq "added") { $Color1 = " new"; } elsif($Info{"Status"} eq "removed") { $Color1 = " failed"; } my $ShowFile = $File; if(defined $ListAddedRemoved and $Info{"Status"}=~/added|removed/) { my $FN = get_filename($ShowFile); if($Info{"Status"} eq "added") { if(my $View = createFileView($File, 2, "view/added")) { $ShowFile=~s&(\A|/)(\Q$FN\E)\Z&$1$2&; } } elsif($Info{"Status"} eq "removed") { if(my $View = createFileView($File, 1, "view/removed")) { $ShowFile=~s&(\A|/)(\Q$FN\E)\Z&$1$2&; } } } $Report .= "\n"; $Report .= "\n"; if($Info{"Status"} eq "changed") { $Report .= "\n"; } elsif($Info{"Status"} eq "unchanged") { $Report .= "\n"; } elsif($Info{"Status"} eq "removed") { $Report .= "\n"; } elsif($Info{"Status"} eq "added") { $Report .= "\n"; } elsif($Info{"Status"} eq "renamed") { $Report .= "\n"; } elsif($Info{"Status"} eq "moved") { $Report .= "\n"; } else { $Report .= "\n"; } if($Format ne "DIR") { if(not $QuickMode and not $Info{"Skipped"} and $Info{"Status"}=~/\A(changed|moved|renamed)\Z/) { $Report .= "\n"; } else { $Report .= "\n"; } if(my $Link = $Info{"Diff"}) { $Report .= "diff\n"; # style='color:Blue;' } elsif($Info{"Empty"}) { $Report .= "\n"; } elsif($Info{"Skipped"}) { $Report .= "skipped\n"; } else { $Report .= "\n"; } if($ShowDetails) { if(my $Link = $Info{"Report"}) { $Report .= "report\n"; # style='color:Blue;' } else { $Report .= "\n"; } if($Format eq "SHARED_OBJECT" or $Format eq "KERNEL_MODULE" or $Format eq "DEBUG_INFO" or $Format eq "STATIC_LIBRARY") { if(defined $Info{"ABIDump"}) { my $Link1 = $Info{"ABIDump"}{1}; my $Link2 = $Info{"ABIDump"}{2}; $Report .= "1, 2\n"; # style='color:Blue;' } else { $Report .= "\n"; } if($Debug) { if(defined $Info{"DWARFDump"}) { my $Link1 = $Info{"DWARFDump"}{1}; my $Link2 = $Info{"DWARFDump"}{2}; $Report .= "1, 2\n"; # style='color:Blue;' } else { $Report .= "\n"; } } } } } $Report .= "\n"; if(my $RenamedTo = $RenamedFiles{$File}) { $Report .= "\n"; } elsif(my $MovedTo = $MovedFiles{$File}) { $Report .= "\n"; } } $Report .= "
NameStatusDeltaVisual
Diff
Detailed
Report
ABI
Dumps
DWARF
Dumps
$ShowFile".$Info{"Status"}."".$Info{"Status"}."".$Info{"Status"}."".$Info{"Status"}."".$Info{"Status"}."".$Info{"Status"}."unknown".show_number($Info{"Rate"}*100)."%
".$RenamedTo."
".$MovedTo."
\n"; } return $Report; } sub appendFile($$) { my ($Path, $Content) = @_; return if(not $Path); if(my $Dir = get_dirname($Path)) { mkpath($Dir); } open(FILE, ">>", $Path) || die ("can't open file \'$Path\': $!\n"); print FILE $Content; close(FILE); } sub writeFile($$) { my ($Path, $Content) = @_; return if(not $Path); if(my $Dir = get_dirname($Path)) { mkpath($Dir); } open(FILE, ">", $Path) || die ("can't open file \'$Path\': $!\n"); print FILE $Content; close(FILE); } sub readFile($) { my $Path = $_[0]; return "" if(not $Path or not -f $Path); open(FILE, "<", $Path); local $/ = undef; my $Content = ; close(FILE); return $Content; } sub get_Prefixes($$) { my @Parts = split(/[\/]+/, $_[0]); my $Prefix = $Parts[$#Parts]; my @Res = (); foreach (1 .. $_[1]) { if($_<$#Parts) { $Prefix = $Parts[$#Parts-$_]."/".$Prefix; push(@Res, $Prefix); } } return @Res; } sub get_filename($) { # much faster than basename() from File::Basename module if($_[0]=~/([^\/]+)[\/]*\Z/) { return $1; } return ""; } sub get_dirname($) { # much faster than dirname() from File::Basename module if($_[0]=~/\A(.*?)[\/]+[^\/]*[\/]*\Z/) { return $1; } return ""; } sub separate_path($) { return (get_dirname($_[0]), get_filename($_[0])); } sub exitStatus($$) { my ($Code, $Msg) = @_; printMsg("ERROR", $Msg); exit($ERROR_CODE{$Code}); } sub printMsg($$) { my ($Type, $Msg) = @_; if($Type!~/\AINFO/) { $Msg = $Type.": ".$Msg; } if($Type!~/_C\Z/) { $Msg .= "\n"; } if($Type eq "ERROR") { print STDERR $Msg; } else { print $Msg; } } sub cut_path_prefix($$) { my ($Path, $Prefix) = @_; return $Path if(not $Prefix); $Prefix=~s/[\/]+\Z//; $Path=~s/\A\Q$Prefix\E([\/]+|\Z)//; return $Path; } sub get_abs_path($) { # abs_path() should NOT be called for absolute inputs # because it can change them (symlinks) my $Path = $_[0]; if($Path!~/\A\//) { $Path = abs_path($Path); } return $Path; } sub cmd_find($;$$$$) { my ($Path, $Type, $Name, $MaxDepth, $UseRegex) = @_; return () if(not $Path or not -e $Path); if(not check_Cmd("find")) { exitStatus("Not_Found", "can't find a \"find\" command"); } $Path = get_abs_path($Path); if(-d $Path and -l $Path and $Path!~/\/\Z/) { # for directories that are symlinks $Path.="/"; } my $Cmd = "find \"$Path\""; if($MaxDepth) { $Cmd .= " -maxdepth $MaxDepth"; } if($Type) { $Cmd .= " -type $Type"; } if($Name and not $UseRegex) { # wildcards $Cmd .= " -name \"$Name\""; } my $Res = `$Cmd 2>\"$TMP_DIR/null\"`; if($?) { printMsg("ERROR", "problem with \'find\' utility ($?): $!"); } my @Files = split(/\n/, $Res); if($Name and $UseRegex) { # regex @Files = grep { /\A$Name\Z/ } @Files; } return @Files; } sub generateTemplate() { writeFile("VERSION.xml", $DescriptorTemplate."\n"); printMsg("INFO", "XML-descriptor template ./VERSION.xml has been generated"); } sub isSCM_File($) { # .svn, .git, .bzr, .hg and CVS my ($Dir, $Name) = separate_path($_[0]); if($Dir=~/(\A|[\/\\]+)\.(svn|git|bzr|hg)([\/\\]+|\Z)/) { return uc($2); } elsif($Name=~/\A\.(git|cvs|hg).*/) { # .gitignore, .gitattributes, .gitmodules, etc. # .cvsignore return uc($1); } elsif($Dir=~/(\A|[\/\\]+)(CVS)([\/\\]+|\Z)/) { return uc($2); } return ""; } sub identifyFile($$) { my ($Name, $Type) = @_; if($Type eq "iName" or $Type eq "iExt") { $Name=lc($Name); } if($Type eq "Name" or $Type eq "iName") { if(my $ID = $FileFormat{$Type}{$Name}) { # Exact name return $ID; } } if($Type eq "Ext" or $Type eq "iExt") { if($Name=~/\.(\w+\.\w+)(\.in|)\Z/i) { # Double extension if(my $ID = $FileFormat{$Type}{$1}) { return $ID; } } if($Name=~/\.(\w+)(\.in|)\Z/i) { # Single extension if(my $ID = $FileFormat{$Type}{$1}) { return $ID; } } } return ""; } sub getFormat($) { my $Path = $_[0]; return "" if(not $Path); if(defined $Cache{"getFormat"}{$Path}) { return $Cache{"getFormat"}{$Path}; } my $Format = getFormat_($Path); if($Format=~/\A(OTHER|INFORM|DATA|TEXT)\Z/) { # by directory if(my $Dir = get_dirname($PathName{$Path})) { my $ID = undef; # by dir foreach (reverse(split(/\//, $Dir))) { if($ID = $DirFormat{$_}) { $Format = $ID; last; } } if(not defined $ID) { # by subdir foreach (keys(%DirFormat)) { next if(index($_, "/")==-1); if(index($Dir, $_)!=-1) { if($Dir=~/(\A|\/)\Q$_\E(\/|\Z)/) { $Format = $DirFormat{$_}; last; } } } } } } if($Format eq "OTHER") { my $Bytes = readBytes($Path); if(my $ID = $BytesFormat{$Bytes}) { $Format = $ID; } my $Ext = getExt($Path); if(not $Ext and $Bytes eq "7f454c46") { # ELF executable $Format = "EXE"; } } if($Format eq "OTHER") { # semi-automatic if(my $Info = getType($Path)) { # by terms my @Terms = getTerms($Info); foreach my $Term (@Terms) { if($Term eq "TEXT" or $Term eq "DATA") { next; } if(defined $FormatInfo{$Term} and my $ID = $FormatInfo{$Term}{"ID"}) { $Format = $ID; last; } elsif(my $ID2 = $TermFormat{$Term}) { $Format = $ID2; last; } } } } if($Format eq "OTHER") { # automatic if(my $Info = getType($Path)) { if($Info=~/compressed/i) { $Format = "ARCHIVE"; } elsif($Info=~/data/i) { $Format = "DATA"; } elsif($Info=~/text/i) { $Format = "TEXT"; } elsif($Info=~/executable/i) { $Format = "EXE"; } elsif($Info=~/\AELF\s/) { $Format = "ELF_BINARY"; } } } if($Format eq "SHARED_OBJECT") { if(getType($Path)=~/ASCII/i) { if(readFilePart($Path, 1)=~/GNU ld script/i) { $Format = "GNU_LD_SCRIPT"; } else { $Format = "TEXT"; } } } if($Format eq "SHARED_OBJECT" or $Format eq "KERNEL_MODULE" or $Format eq "DEBUG_INFO") { # double-check if(readBytes($Path) ne "7f454c46") { $Format = "OTHER"; } } if(not defined $FormatInfo{$Format} or not $FormatInfo{$Format}{"Summary"}) { # Unknown $Format = "OTHER"; } return ($Cache{"getFormat"}{$Path}=$Format); } sub getFormat_($) { my $Path = $_[0]; my ($Dir, $Name) = separate_path($Path); $Name=~s/\~\Z//g; # backup files if(-l $Path) { return "SYMLINK"; } elsif(-d $Path) { return "DIR"; } elsif(my $ID = identifyFile(get_filename($Path), "Name")) { # check by exact name (case sensitive) return $ID; } elsif(my $ID2 = identifyFile(get_filename($Path), "iName")) { # check by exact name (case insensitive) return $ID2; } elsif(my $Kind = isSCM_File($Path)) { return $Kind; } elsif($Name!~/\.(\w+)\Z/i and $Dir=~/(\A|\/)(include|includes)(\/|\Z)/) { # include/QtWebKit/QWebSelectData # includes/KService return "HEADER"; } elsif($Name=~/\.(so)(|\.\d[0-9\-\.\_]*)\Z/i) { # Shared library return "SHARED_OBJECT"; } elsif($Name=~/\A(readme)(\W|\_|\Z)/i or $Name=~/(\W|\_)(readme)(\.txt|)\Z/i) { return "README"; } elsif($Name=~/\A(license|licenses|licence|copyright|copying)(\W|\_|\Z)/i or $Name=~/\.(license|licence)\Z/i or $Name=~/\A(gpl|lgpl|bsd|qpl|artistic)(\W|\_|)(v|)([\d\.]+|)(\.txt|)\Z/i) { # APACHE.license # LGPL.license # COPYRIGHT.IBM return "LICENSE"; } elsif($Name=~/\A(changelog|changes|relnotes)(\W|\Z)/i or $Name=~/\A(NEWS)(\W|\Z)/) { # ChangeLog-2003-10-25 # docs/CHANGES # freetype/ChangeLog return "CHANGELOG"; } elsif($Name=~/\A(INSTALL|TODO)(\.|\-|\Z)/ or $Name=~/\A[A-Z\-\_]+(\.txt|\.TXT|\Z)/ or $Name=~/\A[A-Z\_]+\.[A-Z\_]+\Z/) { # HOWTO.DEBUG, BUGS, WISHLIST.TXT return "INFORM"; } elsif((($Name=~/\.(gz|xz|lzma)\Z/i or $Name=~/\.(\d+)\Z/i) and $Dir=~/\/(man\d*|manpages)(\/|\Z)/) or ($Name=~/\.(\d+)\Z/i and $Dir=~/\/(doc|docs|src|libs|utils)(\/|\Z)/) or $Name=~/\.(man)\Z/ or $Name=~/[a-z]{3,}\.(\d+)\Z/i) { # harmattan/manpages/uic.1 # t1utils-1.36/t1asm.1 return "MANPAGE"; } elsif($Name=~/\.info(|\-\d+)(|\.(gz|xz|lzma))\Z/i and $Dir=~/\/(info|share|doc|docs)(\/|\Z)/) { # /usr/share/info/libc.info-1.gz return "INFODOC"; } elsif($Name=~/\ADoxyfile(\W|\Z)/i) { return "DOXYGEN"; } elsif($Name=~/(make|conf)[^\.]*(\.in)+\Z/i) { return "AUTOMAKE"; } elsif($Name=~/\A(g|)(makefile)(\.|\Z)/i) { return "MAKEFILE"; } elsif($Name=~/\A(CMakeLists.*\.txt)\Z/i) { return "CMAKE"; } elsif(my $ID3 = identifyFile(get_filename($Path), "Ext")) { # check by extension (case sensitive) return $ID3; } elsif(my $ID4 = identifyFile(get_filename($Path), "iExt")) { # check by extension (case insensitive) return $ID4; } return "OTHER"; } sub getTerms($) { my $Str = $_[0]; my %Terms = (); my ($Prev, $Num) = ("", 0); while($Str=~s/([\w\-]+)//) { if($Prev) { $Terms{uc($Prev."_".$1)}=$Num++; } $Terms{uc($1)}=$Num++; $Prev = $1; } return sort {$Terms{$a}<=>$Terms{$b}} keys(%Terms); } sub parseTag($$) { my ($CodeRef, $Tag) = @_; return "" if(not $CodeRef or not ${$CodeRef} or not $Tag); if(${$CodeRef}=~s/\<\Q$Tag\E\>((.|\n)+?)\<\/\Q$Tag\E\>//) { my $Content = $1; $Content=~s/(\A\s+|\s+\Z)//g; return $Content; } else { return ""; } } sub readDescriptor($$) { my ($Version, $Path) = @_; return if(not -f $Path); my $Content = readFile($Path); if(not $Content) { exitStatus("Error", "XML-descriptor is empty"); } if($Content!~/\//g; if(my $GV = parseTag(\$Content, "version")) { $Group{"V$Version"} = $GV; } else { exitStatus("Error", "version in the XML-descriptor is not specified ( section)"); } if(my $GN = parseTag(\$Content, "group")) { $Group{"Name$Version"} = $GN; } else { exitStatus("Error", "group name in the XML-descriptor is not specified ( section)"); } if(my $Pkgs = parseTag(\$Content, "packages")) { foreach my $Path (split(/\s*\n\s*/, $Pkgs)) { if(not -e $Path) { exitStatus("Access_Error", "can't access \'".$Path."\'"); } if(-d $Path) { my @Files = cmd_find($Path, "f", "*.rpm"); @Files = (@Files, cmd_find($Path, "f", "*.src.rpm")); if(not @Files) { # search for DEBs @Files = (@Files, cmd_find($Path, "f", "*.deb")); } foreach (@Files) { registerPackage($_, $Version); } } else { registerPackage($Path, $Version); } } } else { exitStatus("Error", "packages in the XML-descriptor are not specified ( section)"); } foreach my $Path (split(/\s*\n\s*/, parseTag(\$Content, "skip_files"))) { my ($CPath, $Type) = classifyPath($Path); $SkipFiles{$Version}{$Type}{$CPath} = 1; } } sub classifyPath($) { my $Path = $_[0]; if($Path=~/[\*\[]/) { # wildcard $Path=~s/\*/.*/g; $Path=~s/\\/\\\\/g; return ($Path, "Pattern"); } elsif($Path=~/[\/\\]/) { # directory or relative path return ($Path, "Path"); } return ($Path, "Name"); } sub skipFileCompare($$) { my ($Path, $Version) = @_; return 0 if(not $Path or not $Version); my $Name = get_filename($Path); if($SkipFiles{$Version}{"Name"}{$Name}) { return 1; } foreach my $Dir (keys(%{$SkipFiles{$Version}{"Path"}})) { if($Path=~/\Q$Dir\E/) { return 1; } } foreach my $Pattern (keys(%{$SkipFiles{$Version}{"Pattern"}})) { if($Name=~/($Pattern)/) { return 1; } if($Pattern=~/[\/\\]/ and $Path=~/($Pattern)/) { return 1; } } return 0; } sub sepDep($) { my $Dep = $_[0]; if($Dep=~/\A(.+?)(\s+|\[|\()(=|==|<=|>=|<|>)\s+(.+?)(\]|\)|\Z)/) { my ($N, $O, $V) = ($1, $3, $4); # canonify version (1:3.2.5-5:2011.0) $V=~s/\A[^\-\:]+\://;# cut prefix (1:) return ($N, $O, $V); } else { return ($Dep, "", ""); } } sub registerPackage(@) { my ($Path, $Version, $Ph) = @_; if(not $Path) { return (); } if($CompareDirs) { if(not -d $Path) { return (); } } else { if(not -f $Path) { return (); } } my $PkgName = get_filename($Path); my $PkgFormat = getFormat($Path); my ($CPath, $Attr) = (); if($Ph) { # already opened ($CPath, $Attr) = ($Ph->{"CPath"}, $Ph->{"Attr"}); } else { # not opened ($CPath, $Attr) = readPackage($Path, $Version); } $TargetPackages{$Version}{$PkgName} = 1; $Group{"Count$Version"} += 1; my @Files = cmd_find($CPath); foreach my $File (sort @Files) { # search for all files my $FName = cut_path_prefix($File, $CPath); if($PkgFormat eq "RPM" or $PkgFormat eq "DEB") { # files installed to the system $FName = "/".$FName; } elsif($PkgFormat eq "ARCHIVE") { if($RemovePrefix{$Version}) { # cut common prefix from all files $FName = cut_path_prefix($FName, $RemovePrefix{$Version}); } } if(not $FName) { next; } if(defined $SkipPattern) { if(skipFile($FName)) { next; } } $PackageFiles{$Version}{$FName} = $File; $PathName{$File} = $FName; if(not get_dirname($FName) and getFormat($File) eq "ARCHIVE" and not defined $SkipSubArchives) { # go into archives (for SRPM) my $SubDir = "$TMP_DIR/xcontent$Version/$FName"; unpackArchive($File, $SubDir); my @SubContents = listDir($SubDir); if($#SubContents==0 and -d $SubDir."/".$SubContents[0]) { # libsample-x.y.z.tar.gz/libsample-x.y.z $SubDir .= "/".$SubContents[0]; } foreach my $SubFile (cmd_find($SubDir)) { # search for all files in archive my $SFName = cut_path_prefix($SubFile, $SubDir); if(not $SFName) { next; } if(defined $SkipPattern) { if(skipFile($SFName)) { next; } } $PackageFiles{$Version}{$SFName} = $SubFile; } } } delete($PackageFiles{$Version}{"/"}); if($CheckUsage) { checkUsage($Attr->{"Name"}); } return $Attr; } sub checkUsage($) { my $Name = $_[0]; if($PackageManager eq "urpm") { foreach my $Pkg (split(/\s*\n\s*/, `urpmq --whatrequires $Name`)) { $PackageUsage{$Name}{"UsedBy"}{$Pkg} = 1; $TotalUsage{$Pkg}=1; } } } sub listDir($) { my $Path = $_[0]; return () if(not $Path or not -d $Path); opendir(my $DH, $Path); return () if(not $DH); my @Contents = grep { $_ ne "." && $_ ne ".." } readdir($DH); return @Contents; } sub getArchiveFormat($) { my $Pkg = get_filename($_[0]); foreach (sort {length($b)<=>length($a)} keys(%ArchiveFormats)) { my $P = $ArchiveFormats{$_}; if($Pkg=~/\.($P)\Z/) { return $_; } } return ""; } sub unpackArchive($$) { # TODO: tar -xf for all tar.* formats my ($Pkg, $OutDir) = @_; mkpath($OutDir); my $Cmd = ""; my $Format = getArchiveFormat($Pkg); if($Format=~/TAR\.\w+/i or $Format eq "TAR") { $Cmd = "tar -xf \"$Pkg\" --directory=\"$OutDir\""; } elsif($Format eq "GZ") { $Cmd = "cp -f \"$Pkg\" \"$OutDir\" && cd \"$OutDir\" && gunzip \"".get_filename($Pkg)."\""; } elsif($Format eq "LZMA") { $Cmd = "cp -f \"$Pkg\" \"$OutDir\" && cd \"$OutDir\" && unlzma \"".get_filename($Pkg)."\""; } elsif($Format eq "XZ") { $Cmd = "cp -f \"$Pkg\" \"$OutDir\" && cd \"$OutDir\" && unxz \"".get_filename($Pkg)."\""; } elsif($Format eq "ZIP") { $Cmd = "unzip -o \"$Pkg\" -d \"$OutDir\""; } elsif($Format eq "JAR") { $Cmd = "cd \"$OutDir\" && jar -xf \"$Pkg\""; } else { return ""; } system($Cmd." >$TMP_DIR/output 2>&1"); } sub readPackage($$) { my ($Path, $Version) = @_; if(not $Path) { return (); } if($CompareDirs) { if(not -d $Path) { return (); } } else { if(not -f $Path) { return (); } } my $CDir = "$TMP_DIR/content$Version"; my $CPath = $CDir."/".get_filename($Path); my %Attributes = (); my $Format = getFormat($Path); if($CompareDirs and $Format eq "DIR") { mkpath($CDir); qx/cp -fr $Path $CDir/; return ($CDir."/".get_filename($Path), {}); } elsif($Format eq "DEB") { # Deb package if(not check_Cmd("dpkg-deb")) { exitStatus("Not_Found", "can't find \"dpkg-deb\""); } mkpath($CPath); system("dpkg-deb --extract \"$Path\" \"$CPath\""); if($?) { exitStatus("Error", "can't extract package v$Version"); } if(not check_Cmd("dpkg")) { exitStatus("Not_Found", "can't find \"dpkg\""); } my $Info = `dpkg -f $Path`; if($Info=~/Version\s*:\s*(.+)/) { $Attributes{"Version"} = $1; } if($Info=~/Package\s*:\s*(.+)/) { $Attributes{"Name"} = $1; } if($Info=~/Architecture\s*:\s*(.+)/) { $Attributes{"Arch"} = $1; } foreach my $Kind ("Depends", "Provides") { if($Info=~/$Kind\s*:\s*(.+)/) { foreach my $Dep (split(/\s*,\s*/, $1)) { my ($N, $Op, $V) = sepDep($Dep); %{$PackageDeps{$Version}{$Kind}{$N}} = ( "Op"=>$Op, "V"=>$V ); $TotalDeps{$Kind." ".$N} = 1; } } } $PackageInfo{$Attributes{"Name"}}{"V$Version"} = $Info; $Group{"Format"}{$Format} = 1; } elsif($Format eq "RPM" or $Format eq "SRPM") { # RPM or SRPM package if(not check_Cmd("rpm")) { # rpm and rpm2cpio exitStatus("Not_Found", "can't find \"rpm\""); } if(not check_Cmd("cpio")) { exitStatus("Not_Found", "can't find \"cpio\""); } mkpath($CPath); system("cd \"$CPath\" && rpm2cpio \"".abs_path($Path)."\" | cpio -id --quiet"); if($?) { exitStatus("Error", "can't extract package v$Version"); } ($Attributes{"Version"}, $Attributes{"Release"}, $Attributes{"Name"}, $Attributes{"Arch"}) = split(",", queryRPM($Path, "--queryformat \%{version},\%{release},\%{name},\%{arch}")); if($Attributes{"Release"}) { $Attributes{"Version"} .= "-".$Attributes{"Release"}; } foreach my $Kind ("requires", "provides", "suggests") { foreach my $D (split("\n", queryRPM($Path, "--".$Kind))) { my ($N, $Op, $V) = sepDep($D); %{$PackageDeps{$Version}{$Kind}{$N}} = ( "Op"=>$Op, "V"=>$V ); $TotalDeps{$Kind." ".$N} = 1; } } $PackageInfo{$Attributes{"Name"}}{"V$Version"} = queryRPM($Path, "--info"); $Group{"Format"}{$Format} = 1; } elsif($Format eq "ARCHIVE") { # TAR.GZ and others unpackArchive(abs_path($Path), $CPath); if(my ($N, $V) = parseVersion(get_filename($Path))) { ($Attributes{"Name"}, $Attributes{"Version"}) = ($N, $V); } if(not $Attributes{"Version"}) { # default version $Attributes{"Version"} = $Version==1?"X":"Y"; } if(not $Attributes{"Name"}) { # default name $Attributes{"Name"} = get_filename($Path); $Attributes{"Name"}=~s/\.($ARCHIVE_EXT)\Z//; } $Group{"Format"}{uc(getExt($Path))} = 1; } return ($CPath, \%Attributes); } sub parseVersion($) { my $Name = $_[0]; if(my $Extension = getExt($Name)) { $Name=~s/\.(\Q$Extension\E)\Z//; } if($Name=~/\A(.+[a-z])[\-\_](v|ver|)(\d.+?)\Z/i) { # libsample-N # libsample-vN return ($1, $3); } elsif($Name=~/\A([\d\.\-]+)\Z/i) { # X.Y-Z return ("", $Name); } elsif($Name=~/\A(.+?)[\-\_]*(\d[\d\.\-]*)\Z/i) { # libsampleN # libsampleN-X.Y return ($1, $2); } elsif($Name=~/\A(.+)[\-\_](v|ver|)(.+?)\Z/i) { # libsample-N # libsample-vN return ($1, $3); } elsif($Name=~/\A([a-z_\-]+)(\d.+?)\Z/i) { # libsampleNb return ($1, $2); } return (); } sub getExt($) { if($_[0]=~/\.($ARCHIVE_EXT)\Z/) { return $1; } elsif($_[0]=~/\.(\w+)(\.in|)\Z/) { return $1; } return ""; } sub queryRPM($$) { my ($Path, $Query) = @_; return `rpm -qp $Query \"$Path\" 2>$TMP_DIR/null`; } sub composeHTML_Head($$$$$) { my ($Title, $Keywords, $Description, $Styles, $Scripts) = @_; return " $Title "; } sub get_Title() { if($TargetTitle) { return $TargetTitle; } return $Group{"Name"}; } sub get_Header() { my $Header = ""; if($CompareDirs and not $TargetName) { $Header = "Changes report between ".$Group{"Name1"}."/ and ".$Group{"Name2"}."/ directories"; } elsif($CheckMode eq "Group") { $Header = "Changes report for the ".get_Title()." group of packages between ".$Group{"V1"}." and ".$Group{"V2"}." versions"; } else { # single package $Header = "Changes report for the ".get_Title()." package between ".$Group{"V1"}." and ".$Group{"V2"}." versions"; } if($HideUnchanged) { $Header .= " (hidden unchanged files)"; } return "

".$Header."

"; } sub show_number($) { if($_[0]) { my $Num = cut_off_number($_[0], 2, 0); if($Num eq "0") { foreach my $P (3 .. 7) { $Num = cut_off_number($_[0], $P, 1); if($Num ne "0") { last; } } } if($Num eq "0") { $Num = $_[0]; } return $Num; } return $_[0]; } sub cut_off_number($$$) { my ($num, $digs_to_cut, $z) = @_; if($num!~/\./) { $num .= "."; foreach (1 .. $digs_to_cut-1) { $num .= "0"; } } elsif($num=~/\.(.+)\Z/ and length($1)<$digs_to_cut-1) { foreach (1 .. $digs_to_cut - 1 - length($1)) { $num .= "0"; } } elsif($num=~/\d+\.(\d){$digs_to_cut,}/) { $num=sprintf("%.".($digs_to_cut-1)."f", $num); } $num=~s/\.[0]+\Z//g; if($z) { $num=~s/(\.[1-9]+)[0]+\Z/$1/g; } return $num; } sub get_Summary() { my $TestInfo = "

Test Info


\n"; $TestInfo .= "\n"; if(not $CompareDirs or $TargetName) { if($CheckMode eq "Group") { $TestInfo .= "\n"; } else { $TestInfo .= "\n"; } } if(not $CompareDirs) { my @Formats = sort keys(%{$Group{"Format"}}); $TestInfo .= "\n"; if($Group{"Arch"}) { $TestInfo .= "\n"; } } $TestInfo .= "\n"; $TestInfo .= "\n"; if($QuickMode) { $TestInfo .= "\n"; } $TestInfo .= "
Group Name".$Group{"Name"}."
Package Name".get_Title()."
Package Format".join(", ", @Formats)."
Package Arch".$Group{"Arch"}."
Version #1".$Group{"V1"}."
Version #2".$Group{"V2"}."
ModeQuick
\n"; my $TestResults = "

Test Results


\n"; $TestResults .= "\n"; if(not $CompareDirs) { my $Packages_Link = "0"; my %TotalPackages = map {$_=>1} (keys(%{$TargetPackages{1}}), keys(%{$TargetPackages{2}})); if(keys(%TotalPackages)>0) { $Packages_Link = "".keys(%TotalPackages).""; } $TestResults .= "\n"; } my $Deps_Link = "0"; if(keys(%TotalDeps)>0) { $Deps_Link = "".keys(%TotalDeps).""; } if($Group{"Format"}{"DEB"} or $Group{"Format"}{"RPM"} or $Group{"Format"}{"SRPM"}) { $TestResults .= "\n"; } my $Files_Link = "0"; my %TotalFiles = map {$_=>1} (keys(%{$PackageFiles{1}}), keys(%{$PackageFiles{2}})); if(keys(%TotalFiles)>0) { $Files_Link = "".keys(%TotalFiles).""; } $TestResults .= "\n"; if(my $UsedBy = keys(%TotalUsage)) { $TestResults .= "\n"; } my ($TotalChanged, $Total) = (0, 0); # Files foreach my $Format (keys(%FileChanges)) { $TotalChanged += $FileChanges{$Format}{"SizeDelta"}; $Total += $FileChanges{$Format}{"Size"}; } # Deps foreach my $Kind (keys(%DepChanges)) { $TotalChanged += $DepChanges{$Kind}{"SizeDelta"}; $Total += $DepChanges{$Kind}{"Size"}; } # Info $TotalChanged += $InfoChanges{"SizeDelta"}; $Total += $InfoChanges{"Size"}; my $Affected = 0; if($Total) { $Affected = 100*$TotalChanged/$Total; } $Affected = show_number($Affected); if($Affected>=100) { $Affected = 100; } $RESULT{"affected"} = $Affected; my $Verdict = ""; if($TotalChanged) { $Verdict = "Changed
(".$Affected."%)
"; $RESULT{"status"} = "Changed"; } else { $Verdict = "Unchanged"; $RESULT{"status"} = "Unchanged"; } $TestResults .= "\n"; $TestResults .= "
Total Packages".$Packages_Link."
Total Dependencies".$Deps_Link."
Total Files".$Files_Link."
Usage In Other
Packages
$UsedBy
Verdict$Verdict
\n"; if(defined $ABI_Change{"Total"}) { $TestResults .= "

ABI Status


\n"; $TestResults .= "\n"; $TestResults .= "\n"; my $Status = $ABI_Change{"Bin"}/$ABI_Change{"Total"}; if($Status==100) { $TestResults .= "\n"; } else { $TestResults .= "\n"; } $TestResults .= "
Total Objects
(with debug-info)
".$ABI_Change{"Total"}."
ABI Compatibility100%
ABI Compatibility".show_number(100-$Status)."%
\n"; } my $FileChgs = "

Changes In Files


\n"; if(keys(%TotalFiles)) { $FileChgs .= "\n"; $FileChgs .= ""; $FileChgs .= ""; $FileChgs .= ""; $FileChgs .= ""; $FileChgs .= ""; $FileChgs .= ""; $FileChgs .= "\n"; foreach my $Format (sort {$FormatInfo{$b}{"Weight"}<=>$FormatInfo{$a}{"Weight"}} sort {lc($FormatInfo{$a}{"Summary"}) cmp lc($FormatInfo{$b}{"Summary"})} keys(%FormatInfo)) { my $Total = $FileChanges{$Format}{"Total"}; if($HideUnchanged) { $Total = $FileChanges{$Format}{"Added"} + $FileChanges{$Format}{"Removed"} + $FileChanges{$Format}{"Changed"}; } if(not $Total) { next; } if($HideUnchanged) { if(not $Total) { # do not show unchanged files next; } $FileChanges{$Format}{"Total"} = $Total; } $FileChgs .= "\n"; $FileChgs .= "\n"; foreach ("Total", "Added", "Removed", "Changed") { if($FileChanges{$Format}{$_}>0) { my $Link = "".$FileChanges{$Format}{$_}.""; if($_ eq "Added") { $FileChgs .= "\n"; } elsif($_ eq "Removed") { $FileChgs .= "\n"; } elsif($_ eq "Changed") { $FileChgs .= "\n"; } else { $FileChgs .= "\n"; } } else { $FileChgs .= "\n"; } } $FileChgs .= "\n"; } $FileChgs .= "
File TypeTotalAddedRemovedChanged
".$FormatInfo{$Format}{"Summary"}."".$Link."".$Link."".$Link."".$Link."0
\n"; } else { $FileChgs .= "No files\n"; } return $TestInfo.$TestResults.get_Report_Headers().get_Report_Deps().$FileChgs; } sub get_Source() { my $Packages = "\n"; my %Pkgs = map {$_=>1} (keys(%{$TargetPackages{1}}), keys(%{$TargetPackages{2}})); $Packages .= "

Packages (".keys(%Pkgs).")


\n"; $Packages .= "
\n"; foreach my $Name (sort keys(%Pkgs)) { $Packages .= $Name."
\n"; } $Packages .= "
\n"; return $Packages; } sub createReport($) { my $Path = $_[0]; my $CssStyles = readModule("Styles", "Index.css"); my $JScripts = readModule("Scripts", "Sort.js"); printMsg("INFO", "creating report ..."); my $Title = undef; my $Keywords = undef; if($CompareDirs and not $TargetName) { $Title = "Changes report between ".$Group{"Name1"}."/ and ".$Group{"Name2"}."/ directories"; $Keywords = $Group{"Name1"}.", ".$Group{"Name2"}.", changes, report"; } else { $Title = get_Title().": ".$Group{"V1"}." to ".$Group{"V2"}." changes report"; $Keywords = get_Title().", changes, report"; } my $Header = get_Header(); my $Description = $Header; $Description=~s/<[^<>]+>//g; my $Report = $Header."\n"; my $MainReport = get_Report_Files(); my $Legend = "
addedunchanged
changedremoved
\n"; $Report .= $Legend; $Report .= get_Summary(); $Report .= $MainReport; if(not $CompareDirs) { $Report .= get_Report_Usage(); $Report .= get_Source(); } $Report .= "
to the top
\n"; $STAT_LINE = "changed:".$RESULT{"affected"}.";".$STAT_LINE."tool_version:".$TOOL_VERSION; $Report = "\n".composeHTML_Head($Title, $Keywords, $Description, $CssStyles, $JScripts)."\n\n
\n".$Report; $Report .= "
\n



\n"; # footer $Report .= "
\n"; $Report .= ""; writeFile($Path, $Report); if($RESULT{"status"} eq "Changed") { printMsg("INFO", "result: CHANGED (".$RESULT{"affected"}."%)"); } else { printMsg("INFO", "result: UNCHANGED"); } printMsg("INFO", "report: $Path"); } sub check_Cmd($) { my $Cmd = $_[0]; return "" if(not $Cmd); if(defined $Cache{"check_Cmd"}{$Cmd}) { return $Cache{"check_Cmd"}{$Cmd}; } foreach my $Path (sort {length($a)<=>length($b)} split(/:/, $ENV{"PATH"})) { if(-x $Path."/".$Cmd) { return ($Cache{"check_Cmd"}{$Cmd} = 1); } } return ($Cache{"check_Cmd"}{$Cmd} = 0); } sub readFileTypes() { my $FileTypes = readFile($MODULES_DIR."/FileType.xml"); while(my $FileType = parseTag(\$FileTypes, "type")) { my $ID = parseTag(\$FileType, "id"); if(not $ID) { next; } $FormatInfo{$ID}{"ID"} = $ID; if(my $Summary = parseTag(\$FileType, "summary")) { $FormatInfo{$ID}{"Summary"} = $Summary; } if(my $Title = parseTag(\$FileType, "title")) { $FormatInfo{$ID}{"Title"} = $Title; } $FormatInfo{$ID}{"Weight"} = parseTag(\$FileType, "weight"); if(my $Anchor = parseTag(\$FileType, "anchor")) { $FormatInfo{$ID}{"Anchor"} = $Anchor; } if(my $Format = parseTag(\$FileType, "format")) { $FormatInfo{$ID}{"Format"} = $Format; } foreach my $Ext (split(/\s*(\n|,)\s*/, parseTag(\$FileType, "extensions"))) { $FormatInfo{$ID}{"Ext"}{$Ext} = 1; $FileFormat{"Ext"}{$Ext} = $ID; } foreach my $Ext (split(/\s*(\n|,)\s*/, parseTag(\$FileType, "iextensions"))) { $FormatInfo{$ID}{"iExt"}{lc($Ext)} = 1; $FileFormat{"iExt"}{lc($Ext)} = $ID; } foreach my $Name (split(/\s*(\n|,)\s*/, parseTag(\$FileType, "names"))) { $FormatInfo{$ID}{"Name"}{$Name} = 1; $FileFormat{"Name"}{$Name} = $ID; } foreach my $Name (split(/\s*(\n|,)\s*/, parseTag(\$FileType, "inames"))) { $FormatInfo{$ID}{"iName"}{lc($Name)} = 1; $FileFormat{"iName"}{lc($Name)} = $ID; } foreach my $Term (split(/\s*(\n|,)\s*/, parseTag(\$FileType, "terms"))) { $Term=~s/\s+/_/g; $TermFormat{uc($Term)} = $ID; } foreach my $Dir (split(/\s*(\n|,)\s*/, parseTag(\$FileType, "dirs"))) { $DirFormat{$Dir} = $ID; } foreach my $Bytes (split(/\s*(\n|,)\s*/, parseTag(\$FileType, "bytes"))) { $BytesFormat{$Bytes} = $ID; } } foreach my $Format (keys(%FormatInfo)) { if(not $FormatInfo{$Format}{"Title"}) { $FormatInfo{$Format}{"Title"} = autoTitle($FormatInfo{$Format}{"Summary"}); } if(not $FormatInfo{$Format}{"Anchor"}) { $FormatInfo{$Format}{"Anchor"} = autoAnchor($FormatInfo{$Format}{"Title"}); } } } sub autoTitle($) { my $Summary = $_[0]; my $Title = $Summary; while($Title=~/ ([a-z]+)/) { my ($W, $UW) = ($1, ucfirst($1)); $Title=~s/ $W/ $UW/g; } if($Title!~/data\Z/i) { if(not $Title=~s/y\Z/ies/) { # scripts, files, libraries if(not $Title=~s/ss\Z/sses/) { # classes if(not $Title=~s/ch\Z/ches/) { # patches $Title .= "s"; } } } } return $Title; } sub autoAnchor($) { my $Title = $_[0]; my $Anchor = $Title; $Anchor=~s/\+\+/PP/g; $Anchor=~s/C#/CS/g; $Anchor=~s/\W//g; return $Anchor; } sub get_dumpversion($) { my $Cmd = $_[0]; return `$Cmd -dumpversion 2>$TMP_DIR/null`; } sub cmpVersions($$) { # compare two versions in dotted-numeric format my ($V1, $V2) = @_; return 0 if($V1 eq $V2); return undef if($V1!~/\A\d+[\.\d+]*\Z/); return undef if($V2!~/\A\d+[\.\d+]*\Z/); my @V1Parts = split(/\./, $V1); my @V2Parts = split(/\./, $V2); for (my $i = 0; $i <= $#V1Parts && $i <= $#V2Parts; $i++) { return -1 if(int($V1Parts[$i]) < int($V2Parts[$i])); return 1 if(int($V1Parts[$i]) > int($V2Parts[$i])); } return -1 if($#V1Parts < $#V2Parts); return 1 if($#V1Parts > $#V2Parts); return 0; } sub getArchivePattern() { my @Groups = (); foreach (sort {length($b)<=>length($a)} keys(%ArchiveFormats)) { my @Fmts = @{$ArchiveFormats{$_}}; $ArchiveFormats{$_} = join("|", @Fmts); $ArchiveFormats{$_}=~s/\./\\./g; push(@Groups, $ArchiveFormats{$_}); } return join("|", @Groups); } sub scenario() { if($Help) { HELP_MESSAGE(); exit(0); } if($ShowVersion) { printMsg("INFO", "Package Changes Analyzer (PkgDiff) $TOOL_VERSION\nCopyright (C) 2016 Andrey Ponomarenko's ABI Laboratory\nLicense: GNU GPL \nThis program is free software: you can redistribute it and/or modify it.\n\nWritten by Andrey Ponomarenko."); exit(0); } if($DumpVersion) { printMsg("INFO", $TOOL_VERSION); exit(0); } if($GenerateTemplate) { generateTemplate(); exit(0); } if(not $DiffWidth) { $DiffWidth = $DEFAULT_WIDTH; } if(not $DiffLines) { $DiffLines = $DIFF_PRE_LINES; } if($CheckUsage) { if(not $PackageManager) { exitStatus("Error", "-pkg-manager option is not specified"); } } if(not -f $DIFF) { exitStatus("Not_Found", "can't access \"$DIFF\""); } if(not check_Cmd("wdiff")) { print STDERR "WARNING: wdiff is not installed\n"; } if(not $LinksTarget) { $LinksTarget = "_blank"; } else { if($LinksTarget!~/\A(_blank|_self)\Z/) { exitStatus("Error", "incorrect value of links target"); } } if($ShowDetails) { if(my $V = get_dumpversion($ACC)) { if(cmpVersions($V, $ACC_VER)==-1) { printMsg("ERROR", "the version of ABI Compliance Checker should be $ACC_VER or newer"); $ACC = undef; } } else { printMsg("ERROR", "cannot find ABI Compliance Checker"); $ACC = undef; } if(my $V = get_dumpversion($ABI_DUMPER)) { if(cmpVersions($V, $ABI_DUMPER_VER)==-1) { printMsg("ERROR", "the version of ABI Dumper should be $ABI_DUMPER_VER or newer"); $ABI_DUMPER = undef; } } else { printMsg("ERROR", "cannot find ABI Dumper"); $ABI_DUMPER = undef; } } if(not $Descriptor{1}) { exitStatus("Error", "-old option is not specified"); } if(not $Descriptor{2}) { exitStatus("Error", "-new option is not specified"); } if($CompareDirs) { if(not -d $Descriptor{1}) { exitStatus("Access_Error", "can't access directory \'".$Descriptor{1}."\'"); } if(not -d $Descriptor{2}) { exitStatus("Access_Error", "can't access directory \'".$Descriptor{2}."\'"); } } else { if(not -f $Descriptor{1}) { exitStatus("Access_Error", "can't access file \'".$Descriptor{1}."\'"); } if(not -f $Descriptor{2}) { exitStatus("Access_Error", "can't access file \'".$Descriptor{2}."\'"); } } readFileTypes(); if($CompareDirs) { printMsg("INFO", "reading directories ..."); } else { printMsg("INFO", "reading packages ..."); } my $Fmt1 = getFormat($Descriptor{1}); my $Fmt2 = getFormat($Descriptor{2}); my ($Ph1, $Ph2) = (); if($CompareDirs and $Fmt1 eq "DIR") { $RemovePrefix{1} = get_dirname($Descriptor{1}); $RemovePrefix{2} = get_dirname($Descriptor{2}); } elsif($Fmt1 eq "ARCHIVE" and $Fmt2 eq "ARCHIVE") { # check if we can remove a common prefix from files of BOTH packages ($Ph1->{"CPath"}, $Ph1->{"Attr"}) = readPackage($Descriptor{1}, 1); ($Ph2->{"CPath"}, $Ph2->{"Attr"}) = readPackage($Descriptor{2}, 2); my @Cnt1 = listDir($Ph1->{"CPath"}); my @Cnt2 = listDir($Ph2->{"CPath"}); if($#Cnt1==0 and $#Cnt2==0) { if(-d $Ph1->{"CPath"}."/".$Cnt1[0] and -d $Ph2->{"CPath"}."/".$Cnt2[0]) { $RemovePrefix{1} = $Cnt1[0]; $RemovePrefix{2} = $Cnt2[0]; } } } if($CompareDirs and $Fmt1 eq "DIR") { registerPackage($Descriptor{1}, 1); $Group{"Name1"} = get_filename($Descriptor{1}); if($TargetVersion{1}) { $Group{"V1"} = $TargetVersion{1}; } else { $Group{"V1"} = "X"; } } elsif($Fmt1=~/\A(RPM|SRPM|DEB|ARCHIVE)\Z/) { my $Attr = registerPackage($Descriptor{1}, 1, $Ph1); $Group{"Name1"} = $Attr->{"Name"}; $Group{"V1"} = $Attr->{"Version"}; $Group{"Arch1"} = $Attr->{"Arch"}; if(defined $TargetVersion{1}) { $Group{"V1"} = $TargetVersion{1}; } } else { if($Descriptor{1}=~/\.(\w+)\Z/) { if($1 ne "xml") { exitStatus("Error", "unknown format \"$1\""); } } readDescriptor(1, $Descriptor{1}); } if($CompareDirs and $Fmt1 eq "DIR") { registerPackage($Descriptor{2}, 2); $Group{"Name2"} = get_filename($Descriptor{2}); if($TargetVersion{2}) { $Group{"V2"} = $TargetVersion{2}; } else { $Group{"V2"} = "Y"; } } elsif($Fmt2=~/\A(RPM|SRPM|DEB|ARCHIVE)\Z/) { my $Attr = registerPackage($Descriptor{2}, 2, $Ph2); $Group{"Name2"} = $Attr->{"Name"}; $Group{"V2"} = $Attr->{"Version"}; $Group{"Arch2"} = $Attr->{"Arch"}; if(defined $TargetVersion{2}) { $Group{"V2"} = $TargetVersion{2}; } } else { if($Descriptor{2}=~/\.(\w+)\Z/) { if($1 ne "xml") { exitStatus("Error", "unknown format \"$1\""); } } readDescriptor(2, $Descriptor{2}); } if($Group{"Count1"}>1 or $Group{"Count2"}>1) { $CheckMode = "Group"; } if($CompareDirs) { if($TargetName) { $Group{"Name"} = $TargetName; } } else { $Group{"Name"} = $Group{"Name1"}; if($Group{"Name1"} ne $Group{"Name2"}) { if($CheckMode eq "Group") { printMsg("WARNING", "different group names in descriptors (\"".$Group{"Name1"}."\" and \"".$Group{"Name2"}."\")"); } else { printMsg("WARNING", "different package names (\"".$Group{"Name1"}."\" and \"".$Group{"Name2"}."\")"); } } } if($Group{"Count1"} ne $Group{"Count2"}) { printMsg("WARNING", "different number of packages in descriptors"); } if(defined $Group{"Arch1"} and defined $Group{"Arch2"}) { $Group{"Arch"} = $Group{"Arch1"}; if($Group{"Arch1"} ne $Group{"Arch2"}) { printMsg("WARNING", "different architectures of packages (\"".$Group{"Arch1"}."\" and \"".$Group{"Arch2"}."\")"); } } if(defined $Group{"Format"}{"DEB"} and defined $Group{"Format"}{"RPM"}) { printMsg("WARNING", "incompatible package formats: RPM and DEB"); } if($OutputReportPath) { # user-defined path $REPORT_PATH = $OutputReportPath; $REPORT_DIR = get_dirname($REPORT_PATH); if(not $REPORT_DIR) { $REPORT_DIR = "."; } } else { if($CompareDirs and not $TargetName) { $REPORT_DIR = "pkgdiff_reports/".$Group{"Name1"}."_to_".$Group{"Name2"}; } else { $REPORT_DIR = "pkgdiff_reports/".$Group{"Name"}."/".$Group{"V1"}."_to_".$Group{"V2"}; } $REPORT_PATH = $REPORT_DIR."/changes_report.html"; if(-d $REPORT_DIR) { rmtree($REPORT_DIR."/info-diffs"); rmtree($REPORT_DIR."/diffs"); rmtree($REPORT_DIR."/details"); } } printMsg("INFO", "comparing packages ..."); detectChanges(); createReport($REPORT_PATH); if($ExtraInfo) { writeExtraInfo(); } if($CustomTmpDir) { cleanTmp(); } exit($ERROR_CODE{$RESULT{"status"}}); } scenario();