File indexing completed on 2024-04-28 09:37:08

0001 #!/usr/bin/perl -w
0002 # vim:sw=2:et
0003 # tries to reduce the number of includes in KDE source files
0004 # (c) 2001-2007 Dirk Mueller <mueller@kde.org>
0005 
0006 use File::Basename;
0007 use Cwd;
0008 
0009 # declaration of useful subroutines
0010 sub find_src_includes($);
0011 sub find_fixable_sources ($);
0012 sub find_fixable_headers($);
0013 sub find_removable_includes ($);
0014 sub warn_before_modifying ($);
0015 sub remove_include ($$$);
0016 sub replace_include ($$$);
0017 sub replace_include_type ($$);
0018 sub fix_duplicates($);
0019 sub fix_compat_includes($);
0020 sub fix_unnecessary($);
0021 sub fix_include_type($);
0022 sub copy_file($$);
0023 sub process_source_file($);
0024 sub extract_gcc_error($);
0025 
0026 # some global variables
0027 $verbose = 0; # turns on debugging
0028 $modify = 0;  # if 1 it should try to fix the files as well
0029 $experimental = 0; # try&error if an include is obsolete (slow!!)
0030 @explicitfiles = (); # filled in if passing files on the command line
0031 
0032 # statistic variables
0033 $exp_success = 0;
0034 $exp_failure = 0;
0035 
0036 while (defined ($ARGV[0]))
0037 {
0038   $_ = shift;
0039   if (/^--help$|^-h$/)  {
0040     print "Usage: fixkdeincludes [--verbose | -v] [--experimental | -e ] [--modify | -m ]\n";
0041     exit 0;
0042   }
0043   elsif (/^--verbose$|^-v$/) {
0044     $verbose = 1;       # Oh is there a problem...?
0045   }
0046   elsif (/^--modify$|^-m$/) {
0047     $modify = 1;
0048   }
0049   elsif (/^--experimental$|^-e$/) {
0050     $modify = 1;
0051     $experimental = 1;
0052   }
0053   elsif (!/^-/) {
0054     push @explicitfiles, $_;
0055   }
0056 }
0057 
0058 $cppExt     = "(cpp|cc|cxx|C|c\\+\\+)";
0059 $hExt       = "(h|H|hh|hxx|hpp|h\\+\\+)";
0060 
0061 # list of compat headers. scroll down ... much of boring stuff here..
0062 %compatmap = (
0063  'qapp.h'        =>  "qapplication.h",
0064  'qarray.h'      =>  "qmemarray.h",
0065  'qbitarry.h'    =>  "qbitarray.h",
0066  'qbttngrp.h'    =>  "qbuttongroup.h",
0067  'qchkbox.h'     =>  "qcheckbox.h",
0068  'qclipbrd.h'    =>  "qclipboard.h",
0069  'qcollect.h'    =>  "qptrcollection.h",
0070  'qcollection.h' =>  "qptrcollection.h",
0071  'qcombo.h'      =>  "qcombobox.h",
0072  'qconnect.h'    =>  "qconnection.h",
0073  'qdatetm.h'     =>  "qdatetime.h",
0074  'qdrawutl.h'    =>  "qdrawutil.h",
0075  'qdstream.h'    =>  "qdatastream.h",
0076  'qfiledef.h'    =>  "private/qfiledefs_p.h",
0077  'qfiledlg.h'    =>  "qfiledialog.h",
0078  'qfileinf.h'    =>  "qfileinfo.h",
0079  'qfontdta.h'    =>  "qfontdata.h",
0080  'qfontinf.h'    =>  "qfontinfo.h",
0081  'qfontmet.h'    =>  "qfontmetrics.h",
0082  'qgrpbox.h'     =>  "qgroupbox.h",
0083  'qintcach.h'    =>  "qintcache.h",
0084  'qiodev.h'      =>  "qiodevice.h",
0085  'qlcdnum.h'     =>  "qlcdnumber.h",
0086  'qlined.h'      =>  "qlineedit.h",
0087  'qmenudta.h'    =>  "qmenudata.h",
0088  'qmetaobj.h'    =>  "qmetaobject.h",
0089  'qmlined.h'     =>  "qtmultilineedit.h",
0090  'qmsgbox.h'     =>  "qmessagebox.h",
0091  'qmultilinedit.h' =>  "qmultilineedit.h",
0092  'qobjcoll.h'    =>  "qobjectlist.h>\n\#include <qobjectdict.h",
0093  'qobjdefs.h'    =>  "qobjectdefs.h",
0094  'qpaintd.h'     =>  "qpaintdevice.h",
0095  'qpaintdc.h'    =>  "qpaintdevicedefs.h",
0096  'qpdevmet.h'    =>  "qpaintdevicemetrics.h",
0097  'qpmcache.h'    =>  "qpixmapcache.h",
0098  'qpntarry.h'    =>  "qpointarray.h",
0099  'qpopmenu.h'    =>  "qpopupmenu.h",
0100  'qprndlg.h'     =>  "qprintdialog.h",
0101  'qprogbar.h'    =>  "qprogressbar.h",
0102  'qprogdlg.h'    =>  "qprogressdialog.h",
0103  'qpsprn.h'      =>  "private/qpsprinter_p.h",
0104  'qpushbt.h'     =>  "qpushbutton.h",
0105  'qqueue.h'      =>  "qptrqueue.h",
0106  'qradiobt.h'    =>  "qradiobutton.h",
0107  'qrangect.h'    =>  "qrangecontrol.h",
0108  'qscrbar.h'     =>  "qscrollbar.h",
0109  'qsocknot.h'    =>  "qsocketnotifier.h",
0110  'qstack.h'      =>  "qptrstack.h",
0111  'qtabdlg.h'     =>  "qtabdialog.h",
0112  'qtstream.h'    =>  "qtextstream.h",
0113  'qwidcoll.h'    =>  "qwidgetlist.h>\n\#include <qwidgetintdict.h",
0114  'qwindefs.h'    =>  "qwindowdefs.h",
0115 
0116 # and now the KDE specific compat includes
0117  'kapp.h'        =>  "kapplication.h",
0118  'kstddirs.h'    =>  "kstandarddirs.h",
0119  'kuniqueapp.h'  =>  "kuniqueapplication.h",
0120  'ktmainwindow.h'=>  "kmainwindow.h",
0121  'kcolorbtn.h'   =>  "kcolorbutton.h",
0122  'kcolordlg.h'   =>  "kcolordialog.h",
0123  'kxmlgui.h'     =>  "kxmlguifactory.h",
0124  'kdebugclasses.h' => "kdebug.h",
0125 
0126 # Qt5/KF5 list
0127  'QtTest/QtTest' => "QtTest/QTest", # oops, don't include the full module, which includes all of QtCore
0128 );
0129 
0130 
0131 # now it starts to get interesting again
0132 
0133 # Look for source files in the given directory ($dir, first parameter)
0134 sub find_fixable_sources ($)
0135 {
0136   # for now I grep the directory (requires srcdir==builddir)
0137   # ideally it should read the CMakeLists.txt to find out the sources actually used
0138 
0139   my ( $dir ) = @_;
0140 
0141   opendir (DIR, "$dir") || die "Couldn't read '$dir'\n";
0142   my @sources = grep { /^.*\.$cppExt$/o } readdir(DIR);
0143   closedir(DIR);
0144 
0145   push @sources, "CMakeLists.txt" if ( -f "CMakeLists.txt" );
0146 
0147   print "found sources: [ " . join(' ', @sources) . " ] in $dir\n" if ($verbose);
0148 
0149   # prefix them with $dir
0150   my @retsources = ();
0151   foreach $source(@sources) {
0152     # skip platform-specific sources. We assume unix, so we skip win and mac
0153     next if ($source =~ /_mac.cpp/ || $source =~ /_win.cpp/);
0154     push @retsources, "$dir/$source";
0155   }
0156   
0157   return @retsources;
0158 }
0159 
0160 # Look for header files in the given directory ($dir, first parameter)
0161 sub find_fixable_headers ($)
0162 {
0163   # for now I grep the directory (requires srcdir==builddir)
0164   # actually it should read the Makefile and
0165   # find the _HEADERS tags that are put there by
0166 
0167   my ( $dir ) = @_;
0168 
0169   opendir (DIR, "$dir") || die "Couldn't read '$dir'\n";
0170   my @headers = grep { /^.*\.$hExt$/o } readdir(DIR);
0171   closedir(DIR);
0172   
0173   print "found headers: [ " . join(' ', @headers) . " ] in $dir\n" if ($verbose);
0174 
0175   # prefix them with $dir
0176   my @retheaders = ();
0177   foreach $source(@headers) {
0178     push @retheaders, "$dir/$source";
0179   }
0180   
0181   return @retheaders;
0182 }
0183 
0184 sub find_removable_includes ($)
0185 {
0186   my $srcfile = shift @_;
0187   open(SRC, "< $srcfile") || die "find_removable_includes: couldn't open '$srcfile'\n";
0188   
0189   my @includes = ();
0190 
0191   # we skip all includes that are somehow ifdefed
0192 
0193   my $cpplevel = 0;
0194   $cpplevel = -1 if ($srcfile=~m/^.*\.$hExt$/); # plan for header-protection #ifndef/#define/#endif
0195   while (<SRC>) {
0196     # support for removing libs from CMakeLists.txt
0197     if ($srcfile =~ /CMakeLists.txt/ && !/add_library/) {
0198       while (s/(KF5::\w+)//) {
0199         push @includes, $1;
0200       }
0201       next;
0202     }
0203     if ($_ =~ m/^\#if/) {
0204       $cpplevel = $cpplevel + 1;
0205       next;
0206     }
0207     if ($_ =~ m/^\#endif/) {
0208       $cpplevel = $cpplevel - 1;
0209       next;
0210     }
0211     if ($cpplevel == 0 && $_ =~ m/^\#include\s*[\"<](.*)[\">]\S*/) {
0212     # We only look for foo.h and q* and k*
0213     #if ($cpplevel == 0 && (($_ =~ m/^\#include\s*\"(\S+\.h)\"\S*/) || ($_ =~ m/^\#include\s*\<([qk]\S+)\>\S*/)))  {
0214       push @includes, $1;
0215       next;
0216     }
0217   }
0218   close SRC;
0219 
0220   print "No fixable includes found in $srcfile\n" if ($verbose and not @includes);
0221   print "found includes: [ " . join(' ', @includes) . " ]\n" if ($verbose and @includes);
0222 
0223   return @includes;
0224 }
0225 
0226 sub find_installed_headers($)
0227 {
0228   my $sdir = shift @_;
0229   my @includes = ();
0230 
0231   open(I, "<$sdir/CMakeLists.txt") || die "couldn't open $sdir/CMakeLists.txt $!";
0232 
0233   my $data = join('', <I>);
0234   $data =~ s/\s*\n/ /g;
0235 
0236   # now search for not installed headers
0237   while($data =~ /install\s*\(\s*FILES\s*([^()]*)\)/ig) {
0238   my $list = $1;
0239   foreach my $i (split (' ', $list)) {
0240       next if ($i !~ m/\.h$/);
0241       push @includes, $i;
0242     }
0243   }
0244   close(I);
0245   return @includes;
0246 }
0247 
0248 # first parameter: srcfile
0249 # second parameter: include to remove
0250 # third parameter is the duplicate level: this include is removed $level times
0251 sub remove_include ($$$)
0252 {
0253   my $srcfile = shift @_;
0254   my $include = quotemeta(shift @_);
0255   my $level = shift @_;
0256 
0257   die "$srcfile is not read/writeable!\n" if( ! -r $srcfile || ! -w $srcfile);
0258   open(I, "< $srcfile") or die "remove_include: couldn't open '$srcfile'\n";
0259   my @contents = <I>;
0260   close(I);
0261 
0262   # ok, CPU time doesn't count so we do it the lazy way
0263   # we should remove the last occurence of the include in the
0264   # file because in case it is a duplicate removing the first
0265   # one could make a difference. 
0266   my @revcontents = reverse @contents;
0267   @contents = ();
0268 
0269   # we skip all includes that are somehow ifdefed
0270   # note the logic is reversed because it operates
0271   # on reversed lines :)
0272   my $cpplevel = 0;
0273   $cpplevel = -1 if ($srcfile=~m/^.*\.$hExt$/); # plan for header-protection #ifndef/#define/#endif
0274   foreach $line (@revcontents) {
0275     if ($line =~ m/^\#if/) {
0276       $cpplevel = $cpplevel - 1;
0277       push @contents, $line;
0278       next;
0279     }
0280 
0281     if ($line =~ m/^\#endif/) {
0282       $cpplevel = $cpplevel + 1;
0283       push @contents, $line;
0284       next;
0285     }
0286 
0287     if ($srcfile =~ /CMakeLists.txt/ && $line !~ /add_library/) {
0288       # Remove library, keep the rest of the line
0289       $line =~ s/ $include//;
0290     } elsif ($level && $cpplevel == 0 &&
0291        (($line =~ m/^\#include\s*\"$include\"\S*/) || ($line =~ m/^\#include\s*\<$include\>\S*/)))  {
0292       $level = $level - 1;
0293       # skipping the line..
0294       next;
0295     }
0296 
0297     push @contents, $line;
0298   }
0299 
0300   # now we have the fixed contents in @contents, although in wrong order
0301   open(O, "> $srcfile") || die "remove_include: couldn't open '$srcfile' for writing\n";
0302   print O reverse @contents;
0303   close (O);
0304 }
0305 
0306 # first parameter: srcfile
0307 # second parameter: include to replace
0308 # third parameter the include file to replace it with
0309 sub replace_include ($$$)
0310 {
0311   my $srcfile = shift @_;
0312   my $include = quotemeta(shift @_);
0313   my $destinclude = shift @_;
0314 
0315   die "$srcfile is not read/writeable!\n" if( ! -r $srcfile || ! -w $srcfile);
0316   open(I, "< $srcfile") or die "replace_include: couldn't open '$srcfile'\n";
0317   my @contents = <I>;
0318   close(I);
0319 
0320   # ok, CPU time doesn't count so we do it the lazy way
0321   my @revcontents = reverse @contents;
0322   @contents = ();
0323 
0324   # we skip all includes that are somehow ifdefed
0325   # note the logic is reversed because it operates
0326   # on reversed lines :)
0327   my $cpplevel = 0;
0328   $cpplevel = -1 if ($srcfile=~m/^.*\.$hExt$/); # plan for header-protection #ifndef/#define/#endif
0329   foreach $line (@revcontents) {
0330     if ($line =~ m/^\#if/) {
0331       $cpplevel = $cpplevel - 1;
0332       push @contents, $line;
0333       next;
0334     }
0335 
0336     if ($line =~ m/^\#endif/) {
0337       $cpplevel = $cpplevel + 1;
0338       push @contents, $line;
0339       next;
0340     }
0341 
0342     if ($cpplevel == 0 &&
0343       (($line =~ m/^\#include\s*\"$include\"\S*/) || ($line =~ m/^\#include\s*\<$include\>\S*/)))
0344     {
0345       print "HAH! found $include to replace in $srcfile!\n" if($verbose);
0346       $line =~ s/(\#include\s*[\"<])$include([\">]\S*)/$1$destinclude$2/;
0347     }
0348 
0349     push @contents, $line;
0350   }
0351 
0352   # now we have the fixed contents in @contents
0353   open(O, "> $srcfile") || die "replace_include: couldn't open '$srcfile' for writing\n";
0354   print O reverse @contents;
0355   close (O);
0356 }
0357 
0358 # fixes #include <foo.h> -> #include "foo.h"
0359 sub replace_include_type ($$)
0360 {
0361   my ($srcfile, $include) = @_;
0362 
0363   die "$srcfile is not read/writeable!\n" if( ! -r $srcfile || ! -w $srcfile);
0364   open(I, "< $srcfile") or die "replace_include: couldn't open '$srcfile'\n";
0365   my @contents = <I>;
0366   close(I);
0367 
0368   grep(s/^(\#include)\s*<$include>(.*)$/$1 \"$include\"$2/, @contents);
0369 
0370   # now we have the fixed contents in @contents
0371   open(O, "> $srcfile") || die "replace_include: couldn't open '$srcfile' for writing\n";
0372   print O @contents;
0373   close (O);
0374 }
0375 
0376 sub fix_duplicates($)
0377 {
0378   my $srcfile = shift @_;
0379 
0380   return if ($srcfile =~ /CMakeLists.txt/);
0381 
0382   my @includes = &find_removable_includes($srcfile);
0383 
0384   my %inclMap = ();
0385 
0386   # initialize
0387   foreach $include (@includes) {
0388     $inclMap{$include} = 0;
0389   }
0390   
0391   # count number of occurences
0392   foreach $include (@includes) {
0393     $inclMap{$include} = $inclMap{$include} + 1;
0394   }
0395   
0396   # check for duplicates
0397   foreach $include (keys %inclMap) {
0398     next if $inclMap{$include} <= 1;
0399     
0400     print "$srcfile: duplicate level ". $inclMap{$include} .": ". $include ."\n";
0401 
0402     &remove_include($srcfile, $include, $inclMap{$include} - 1) if($modify);
0403   }
0404 }
0405 
0406 sub extract_gcc_error($)
0407 {
0408   my $out = shift;
0409  
0410  # print "out: $out\n";
0411 
0412   while ($out =~ m/^(.*?):([0-9]+):(.*)$/mg) # filename:lineno:message
0413   {
0414       my $field1 = $1 || "";
0415       my $field2 = $2 || "";
0416       my $field3 = $3 || "";
0417 
0418  #     print "f1: $field1, f2: $field2, f3: $field3\n";
0419 
0420       next if ($field3 =~ m/\s+warning:.*/);
0421       next if ($field3 =~ m/^\s*$/);
0422       return basename($field1);
0423   }
0424   return "BUG!";
0425 }
0426 
0427 sub fix_compat_includes($)
0428 {
0429   my $srcfile = shift @_;
0430 
0431   my @includes = &find_removable_includes($srcfile);
0432 
0433   my %inclMap = ();
0434 
0435   # initialize
0436   foreach $include (@includes) {
0437     $inclMap{$include} = 0;
0438   }
0439   
0440   # count number of occurences
0441   foreach $include (@includes) {
0442     $inclMap{$include} = $inclMap{$include} + 1;
0443   }
0444   
0445   # check for compat headers
0446   foreach $include (keys %inclMap) {
0447     if( defined $compatmap{$include}) {
0448       print "$srcfile: compat header: $include, to be replaced by ". $compatmap{$include} ."\n";
0449       &replace_include($srcfile, $include, $compatmap{$include}) if($modify);
0450     }
0451   }
0452 }
0453 
0454 sub fix_include_type($)
0455 {
0456   my $srcfile = shift @_;
0457   my $srcdir = dirname($srcfile);
0458 
0459   open(I, "<$srcfile") || die "couldn't open $srcfile in _fix_include_type";
0460   my @bracketincs = grep s/^\s*\#include\s*<([^>]+)>\s*$/$1/, <I>;
0461   close(I);
0462 
0463   foreach my $include (@bracketincs) {
0464     next if (!(-r "$srcdir/$include"));
0465     next if (grep (/^$include$/, @instheaders));
0466     next if ($include eq "config.h"); # oh don't get me started on that
0467 
0468     print "$srcfile: #include <$include> should use #include \"...\"\n";
0469     &replace_include_type($srcfile, $include) if($modify);
0470   }
0471 }
0472 
0473 # copies a file from src to dest, overwrites destination if exists
0474 sub copy_file($$)
0475 {
0476   my $src = shift(@_);
0477   my $dst = shift(@_);
0478 
0479   open(I, "< $src") or die "copy_file: can't open $src for input\n";
0480   my @fcontents = <I>;
0481   close(I);
0482   open(O, "> $dst") or die "copy_file: can't open $dst for output\n";
0483   print O @fcontents;
0484   close(O);
0485 }
0486 
0487 # interrupt handler for fix_unnecessary
0488 sub sighandler_fix_unnecessary()
0489 {
0490   my($sig) = @_;
0491   print "Caught a SIG$sig--shutting down after restoring $srcfile\n";
0492   chdir($srcdir);
0493   unlink $srcfile || warn "couldn't unlink $srcfile";
0494   rename $localbackup, $srcfile || warn "couldn't rename $localbackup to $srcfile";
0495   system("touch $srcfile") if ($srcfile =~ /CMakeLists.txt/);
0496   exit(1);
0497 }
0498 
0499 sub fix_unnecessary($)
0500 {
0501   local $srcfile = shift @_;
0502   local $srcdir = dirname($srcfile);
0503 
0504   # find canonical path for srcdir
0505   my $origdir = cwd;
0506   chdir($srcdir);
0507   $srcdir = cwd;
0508   print "srcdir=$srcdir\n" if($verbose);
0509 
0510   my $builddir = $srcdir;
0511   my $makecmd = "make";
0512   if (defined $ENV{"OBJ_REPLACEMENT"})
0513   {
0514     # we have to use sed here, because perl can't do s#a#b#
0515     $builddir = `echo $srcdir | sed -e "\$OBJ_REPLACEMENT"`;
0516     chomp $builddir;
0517     $makecmd = "makeobj";
0518   }
0519   print "builddir=$builddir\n" if($verbose);
0520 
0521   my $tot = $exp_success + $exp_failure;
0522   print "=============== $srcfile (successes: $exp_success; total: $tot)\n";
0523 
0524   $srcfile = basename($srcfile);
0525 
0526   # first figure out some details
0527   my @includes = &find_removable_includes($srcfile);
0528 
0529   my $blanksrc = $srcfile;
0530   $blanksrc =~ s/(.*)\.[^\.]+/$1/;
0531 
0532   print "Checking for initial compilation: ";
0533   chdir($builddir);
0534   my $objextension = "BUG";
0535   my $lastcommand = "";
0536   if($srcfile =~ /\.$hExt$/o || $srcfile =~ /CMakeLists.txt/) {
0537     $lastcommand = "$makecmd all";
0538     $output = `$lastcommand 2>&1`;
0539     $objextension = "all" if ( 0 == ($? >> 8));
0540   }
0541   else {
0542     unlink "$blanksrc.o";
0543     $lastcommand = "$makecmd $blanksrc.o";
0544     $output = `$lastcommand 2>&1`;
0545     $objextension = ".o" if ( 0 == ($? >> 8));
0546   }
0547   if($objextension eq "BUG") {
0548     warn "can't figure out right compile command for $srcfile :-(\n" .
0549          "??? unused, or didn't compile in the first place?\n" .
0550          "Tried: $lastcommand, and got:\n" .
0551          "$output";
0552     chdir($origdir);
0553     warn "Aborting!\n";
0554     exit 1;
0555   }
0556 
0557   print "worked with $objextension\n";
0558 
0559   my $initialwarnings = grep(/warning:/, $output);
0560   print "got warnings: $initialwarnings\n" if ($verbose);
0561 
0562   # now try to drop some includes 
0563   foreach $include (@includes) {
0564     # kdatastream is special because
0565     # it will break the application if removed even
0566     # if it continues to compile
0567     next if( $include eq "kdatastream.h");
0568     # I also like to have kdebug.h still in
0569     # so that it's easy to add kDebug calls
0570     next if( $include eq "kdebug.h");
0571     # avoid this one as it might cause
0572     # certain code parts to be disabled from compilation
0573     next if( $include eq "qmodules.h");
0574     # don't remove this one either. causes conditional
0575     # code to be compiled incorrectly
0576     next if( $include eq "kdeversion.h");
0577     # don't remove the config.h include
0578     # conditional code may depend on this file
0579     next if( $include eq "config.h");
0580     # check if it is its own header file
0581     my $blankhdr = $include;
0582     $blankhdr =~ s/(.*)\.[^\.]+/$1/;
0583     next if ($blankhdr eq $blanksrc);   
0584 
0585     chdir($srcdir);
0586 
0587     local $localbackup = $srcfile . "#fixkdeincludes";
0588 
0589     # preserve timestamp if possible for CVS
0590     unlink $localbackup;
0591     rename $srcfile, $localbackup;
0592     copy_file($localbackup, $srcfile);
0593 
0594     # revert to backup in case of interrupt (Ctrl+C)
0595     $SIG{'INT'} = \&sighandler_fix_unnecessary;
0596 
0597     # check if it still compiles
0598     if($verbose) {
0599       chdir($builddir);
0600       # testing headers? need to compile everything
0601       if($objextension eq "all") {
0602         # wait a second for makefile timestamp comparisons
0603         sleep 1;
0604         `$makecmd all 2>&1`;
0605       }
0606       else {
0607         unlink "$blanksrc$objextension";
0608         `$makecmd $blanksrc$objextension 2>&1`;
0609       }
0610       die "unexpected error $output\nexitcode=" . ($? >> 8) if($? >> 8);
0611       chdir($srcdir);
0612     }
0613 
0614     # duplicates have to be nuked here , so it will be dropped maximum once
0615     print "trying without $include: ";
0616     &remove_include($srcfile, $include, 1);
0617 
0618     chdir($builddir);
0619 
0620     # try if it compiles
0621     if($objextension eq "all") {
0622       sleep 1;
0623       $output=`$makecmd $objextension 2>&1`;
0624     }
0625     else {
0626       unlink "$builddir/$blanksrc$objextension";
0627       $output=`$makecmd $blanksrc$objextension 2>&1`;
0628     }
0629     my $retcode = ($? >> 8);
0630     #print "retcode=$retcode\n$output" if ($verbose);
0631 
0632     my $warnings = grep(/warning:/, $output);
0633     print "got warnings: $warnings\n" if ($verbose);
0634 
0635     chdir($srcdir);
0636     if($retcode == 0 and $warnings == $initialwarnings) {
0637       # wow, it worked, lets continue!
0638       print "SUCCESS!\n";
0639       $SIG{'INT'} = 'DEFAULT';
0640       unlink $localbackup;
0641       $exp_success = $exp_success + 1;
0642     }
0643     else {
0644       # is this a fixable error?
0645       if($objextension eq "all" and 
0646          &extract_gcc_error($output) ne $srcfile) {
0647         print "failed (error in " . &extract_gcc_error($output) . ")\n";
0648         # FIXME: implement fixup of the compilation error
0649         # so that we can be much more agressive in removing
0650         # unneeded includes from headers
0651       }
0652       else
0653       {
0654         # better luck next time
0655         if ($retcode == 0 and $warnings != $initialwarnings) {
0656           print "FAILED, introduces " . ($warnings - $initialwarnings) . " new warnings\n";
0657         } else {
0658           print "FATALLY failed\n";
0659         }
0660       }
0661       unlink $srcfile;
0662       rename $localbackup, $srcfile;
0663       system("touch $srcfile") if ($srcfile =~ /CMakeLists.txt/);
0664       $SIG{'INT'} = 'DEFAULT';
0665 
0666       $exp_failure = $exp_failure + 1;
0667     }
0668   }
0669 
0670   print "\n";
0671 
0672   chdir($origdir);
0673 }
0674 
0675 sub process_source_file($)
0676 {
0677   local $file = shift @_;
0678   my $pure = basename($file);
0679   print "Checking: $file\n" if($verbose);
0680   #&fix_include_type($file);
0681   &fix_compat_includes($file);
0682   &fix_duplicates($file);
0683   &fix_unnecessary($file) if ($experimental && !grep (/^$pure$/, @instheaders));
0684   print "\n" if ($verbose);
0685 }
0686 
0687 sub check_for_cmake_srcdir($)
0688 {
0689   my $dir = shift;
0690 
0691   return 0 if !( -r "$dir/CMakeLists.txt");
0692   return 1 if !( -r "$dir/CMakeFiles");
0693 
0694   # ok, now it is either srcdir with srcdir==builddir, or it is builddir,
0695   # which we don't want. 
0696 
0697   open(I, "<$dir/Makefile") || die "couldn't read $dir/Makefile";
0698   while(<I>) {
0699     if(/^srcdir\s*=\s*(\S+)/) {
0700       close(I);
0701 
0702       if($1 ne ".") {
0703         print "Skipping build dir: $dir\n" if($verbose);
0704         return 0;
0705       }
0706       return 1;
0707     }
0708   }
0709   
0710   close(I);
0711   # ok, this makefile isn't generated by cmake, we don't want that
0712   return 0;
0713 }
0714 
0715 sub init_compat_includelist()
0716 {
0717   # We identify identical headers by their md5sum
0718   open(L, "md5sum /usr/include/Qt{3Support,Core,DBus,Gui,Network," .
0719           "OpenGL,Script,Sql,Svg,Test,Xml}/* | sort |") || return;
0720 
0721   my $oldsum = "<invalid>";
0722   my $oldname;
0723 
0724   while(<L>) {
0725     m/^(\S+)\s+(\S+)/;
0726 
0727     my ($sum, $fname) = ($1, $2);
0728 
0729     $fname = $1 if ($fname =~ /\/(Qt.*)/); # remove /usr/include/, keep only QtFoo/Bar
0730 
0731     if ($oldsum eq $sum) {
0732       $compatmap{$fname} = $oldname;
0733       if ($fname =~ /\/(.*)/) {
0734           $compatmap{$1} = $oldname;
0735       }
0736       next;
0737     }
0738 
0739     $oldname = $fname;
0740     $oldsum = $sum;
0741   }
0742   close(L);
0743 }
0744 
0745 #############################################################################
0746 # here is the main logic
0747 #
0748 
0749 &init_compat_includelist();
0750 
0751 # warn about modified files - TODO port to git diff --quiet, but the exit code seems broken
0752 #if($modify) {
0753 #`cvscheck | grep '^[MmC]'`;
0754 #print "WARNING: you have pending local changes. You might commit them by accident!\n\n" if($? >> 8 == 0);
0755 #}
0756 
0757 if($experimental) {
0758   print "WARNING: The experimental mode is indeed experimental.\n";
0759   print "It tries to reduce includes by testing if it would compile\n";
0760   print "without a particular include. It might introduce subtle bugs\n";
0761   print "or break compilation for make check or make final.\n\n";
0762   print "This operation mode is known to be unsafe. You've been warned.\n";
0763 }
0764 
0765 # process files from the command line, if any
0766 if ( $#explicitfiles >= 0 ) {
0767   foreach $file( @explicitfiles ) {
0768     &process_source_file( $file );
0769   }
0770   exit 0;
0771 }
0772 
0773 # first generate a list of subdirectories
0774 @dirlist = ();
0775 push @dirlist, "." if (&check_for_cmake_srcdir("."));
0776 die "current directory isn't srcdir!" if (!scalar @dirlist);
0777 foreach $dir ( @dirlist ) {
0778  opendir (DIR, "$dir") || warn "Couldn't read '$dir'";
0779  my $subdir = "";
0780  while( $subdir = readdir(DIR)) {
0781    next if ($subdir =~ /^\./);
0782    next if !( -d "$dir/$subdir");
0783    next if (! &check_for_cmake_srcdir("$dir/$subdir"));
0784 
0785    push @dirlist, "$dir/$subdir";
0786  }
0787  closedir(DIR);
0788 }
0789 
0790 # now iterate over all subdirs
0791 foreach $dir(@dirlist) {
0792 
0793   # check if this directory wants not to be fixed
0794   if(open(M, "$dir/CMakeLists.txt")) {
0795     my @mcontents = grep /(\-UQT_NO_COMPAT|\-UKDE_NO_COMPAT)/, <M>;
0796     close(M);
0797     if ( @mcontents ) {
0798       print "Skipping directory: $dir\n";
0799       next;
0800     }
0801   }
0802 
0803   @headers = &find_fixable_headers($dir);
0804   @instheaders = &find_installed_headers($dir);
0805   foreach $file(@headers) {
0806     &process_source_file($file);
0807   }
0808   @sources = &find_fixable_sources($dir);
0809   foreach $file(@sources) {
0810     &process_source_file($file);
0811   }
0812 }