diff options
Diffstat (limited to 'scripts/fixkdeincludes')
-rw-r--r-- | scripts/fixkdeincludes | 756 |
1 files changed, 756 insertions, 0 deletions
diff --git a/scripts/fixkdeincludes b/scripts/fixkdeincludes new file mode 100644 index 00000000..54e4e9f6 --- /dev/null +++ b/scripts/fixkdeincludes @@ -0,0 +1,756 @@ +#!/usr/bin/perl -w +# tries to reduce the number of includes in KDE source files +# (c) 2001-2003 Dirk Mueller <mueller@kde.org> + +use File::Basename; +use Cwd; + +# declaration of useful subroutines +sub find_src_includes($); +sub find_fixable_sources ($); +sub find_fixable_headers($); +sub find_removable_includes ($); +sub warn_before_modifying ($); +sub remove_include ($$$); +sub replace_include ($$$); +sub replace_include_type ($$); +sub fix_duplicates($); +sub fix_compat_includes($); +sub fix_unnecessary($); +sub fix_include_type($); +sub copy_file($$); +sub process_source_file($); +sub extract_gcc_error($); + +# some global variables +$verbose = 0; # turns on debugging +$modify = 0; # if 1 it should try to fix the files as well +$experimental = 0; # try&error if an include is obsolete (slow!!) +@explicitfiles = (); # filled in if passing files on the command line + +# statistic variables +$exp_success = 0; +$exp_failure = 0; + +while (defined ($ARGV[0])) +{ + $_ = shift; + if (/^--help$|^-h$/) { + print "Usage: fixkdeincludes [--verbose | -v] [--experimental | -e ] [--modify | -m ]\n"; + exit 0; + } + elsif (/^--verbose$|^-v$/) { + $verbose = 1; # Oh is there a problem...? + } + elsif (/^--modify$|^-m$/) { + $modify = 1; + } + elsif (/^--experimental$|^-e$/) { + $modify = 1; + $experimental = 1; + } + elsif (!/^-/) { + push @explicitfiles, $_; + } +} + +$cppExt = "(cpp|cc|cxx|C|c\\+\\+)"; +$hExt = "(h|H|hh|hxx|hpp|h\\+\\+)"; + +# list of compat headers. scroll down ... much of boring stuff here.. +%compatmap = ( + 'qapp.h' => "qapplication.h", + 'qarray.h' => "qmemarray.h", + 'qbitarry.h' => "qbitarray.h", + 'qbttngrp.h' => "qbuttongroup.h", + 'qchkbox.h' => "qcheckbox.h", + 'qclipbrd.h' => "qclipboard.h", + 'qcollect.h' => "qptrcollection.h", + 'qcollection.h' => "qptrcollection.h", + 'qcombo.h' => "qcombobox.h", + 'qconnect.h' => "qconnection.h", + 'qdatetm.h' => "qdatetime.h", + 'qdrawutl.h' => "qdrawutil.h", + 'qdstream.h' => "qdatastream.h", + 'qfiledef.h' => "private/qfiledefs_p.h", + 'qfiledlg.h' => "qfiledialog.h", + 'qfileinf.h' => "qfileinfo.h", + 'qfontdta.h' => "qfontdata.h", + 'qfontinf.h' => "qfontinfo.h", + 'qfontmet.h' => "qfontmetrics.h", + 'qgrpbox.h' => "qgroupbox.h", + 'qintcach.h' => "qintcache.h", + 'qiodev.h' => "qiodevice.h", + 'qlcdnum.h' => "qlcdnumber.h", + 'qlined.h' => "qlineedit.h", + 'qlist.h' => "qptrlist.h", + 'qmenudta.h' => "qmenudata.h", + 'qmetaobj.h' => "qmetaobject.h", + 'qmlined.h' => "qtmultilineedit.h", + 'qmsgbox.h' => "qmessagebox.h", + 'qmultilinedit.h' => "qmultilineedit.h", + 'qobjcoll.h' => "qobjectlist.h>\n\#include <qobjectdict.h", + 'qobjdefs.h' => "qobjectdefs.h", + 'qpaintd.h' => "qpaintdevice.h", + 'qpaintdc.h' => "qpaintdevicedefs.h", + 'qpdevmet.h' => "qpaintdevicemetrics.h", + 'qpmcache.h' => "qpixmapcache.h", + 'qpntarry.h' => "qpointarray.h", + 'qpopmenu.h' => "qpopupmenu.h", + 'qprndlg.h' => "qprintdialog.h", + 'qprogbar.h' => "qprogressbar.h", + 'qprogdlg.h' => "qprogressdialog.h", + 'qpsprn.h' => "private/qpsprinter_p.h", + 'qpushbt.h' => "qpushbutton.h", + 'qqueue.h' => "qptrqueue.h", + 'qradiobt.h' => "qradiobutton.h", + 'qrangect.h' => "qrangecontrol.h", + 'qscrbar.h' => "qscrollbar.h", + 'qsocknot.h' => "qsocketnotifier.h", + 'qstack.h' => "qptrstack.h", + 'qtabdlg.h' => "qtabdialog.h", + 'qtstream.h' => "qtextstream.h", + 'qvector.h' => "qptrvector.h", + 'qwidcoll.h' => "qwidgetlist.h>\n\#include <qwidgetintdict.h", + 'qwindefs.h' => "qwindowdefs.h", + +# and now the KDE specific compat includes + 'kapp.h' => "kapplication.h", + 'kstddirs.h' => "kstandarddirs.h", + 'kuniqueapp.h' => "kuniqueapplication.h", + 'ktmainwindow.h'=> "kmainwindow.h", + 'kcolorbtn.h' => "kcolorbutton.h", + 'kcolordlg.h' => "kcolordialog.h", + 'kxmlgui.h' => "kxmlguifactory.h", + 'kdebugclasses.h' => "kdebug.h", +); + + +# now it starts to get interesting again + +# Look for source files in the given directory ($dir, first parameter) +sub find_fixable_sources ($) +{ + # for now I grep the directory (requires srcdir==builddir) + # actually it should read the Makefile and + # find the _SOURCES / _OBJECTS tags that are put there by + # automake and am_edit, but thats an excercise to the reader ;-) + + my ( $dir ) = @_; + + opendir (DIR, "$dir") || die "Couldn't read '$dir'\n"; + my @sources = grep { /^.*\.$cppExt$/o } readdir(DIR); + closedir(DIR); + + print "found sources: [ " . join(' ', @sources) . " ] in $dir\n" if ($verbose); + + # prefix them with $dir + my @retsources = (); + foreach $source(@sources) { + push @retsources, "$dir/$source"; + } + + return @retsources; +} + +# Look for header files in the given directory ($dir, first parameter) +sub find_fixable_headers ($) +{ + # for now I grep the directory (requires srcdir==builddir) + # actually it should read the Makefile and + # find the _HEADERS tags that are put there by + # automake and am_edit, but thats an excercise to the reader ;-) + + my ( $dir ) = @_; + + opendir (DIR, "$dir") || die "Couldn't read '$dir'\n"; + my @headers = grep { /^.*\.$hExt$/o } readdir(DIR); + closedir(DIR); + + print "found headers: [ " . join(' ', @headers) . " ] in $dir\n" if ($verbose); + + # prefix them with $dir + my @retheaders = (); + foreach $source(@headers) { + push @retheaders, "$dir/$source"; + } + + return @retheaders; +} + +sub find_removable_includes ($) +{ + my $srcfile = shift @_; + open(SRC, "< $srcfile") || die "find_removable_includes: couldn't open '$srcfile'\n"; + + my @includes = (); + + # we skip all includes that are somehow ifdefed + + my $cpplevel = 0; + $cpplevel = -1 if ($srcfile=~m/^.*\.$hExt$/); # plan for header-protection #ifndef/#define/#endif + while (<SRC>) { + if ($_ =~ m/^\#if/) { + $cpplevel = $cpplevel + 1; + next; + } + if ($_ =~ m/^\#endif/) { + $cpplevel = $cpplevel - 1; + next; + } + #if ($cpplevel == 0 && $_ =~ m/^\#include\s*[\"<]([qk]\S*\.h)[\">]\S*/) { + if ($cpplevel == 0 && (($_ =~ m/^\#include\s*\"(\S+\.h)\"\S*/) || ($_ =~ m/^\#include\s*\<([qk]\S+.h)\>\S*/))) { + push @includes, $1; + next; + } + } + close SRC; + + print "No fixable includes found in $srcfile\n" if ($verbose and not @includes); + print "found includes: [ " . join(' ', @includes) . " ]\n" if ($verbose and @includes); + + return @includes; +} + +sub find_installed_headers($) +{ + my $sdir = shift @_; + my @includes = (); + + open(I, "<$sdir/Makefile.am") || die "couldn't open $sdir/Makefile.am $!"; + + my $data = join('', <I>); + $data =~ s/\\\s*\n/ /g; + + # now search for not installed headers + foreach my $line (split /^/, $data) { + if($line =~ /^(\w+)_HEADERS\s*=(.*)$/) { + next if $1 eq "noinst"; + push @includes, split (' ', $2); + } + } + close(I); + return @includes; +} + +# first parameter: srcfile +# second parameter: include to remove +# third parameter is the duplicate level: this include is removed $level times +sub remove_include ($$$) +{ + my $srcfile = shift @_; + my $include = quotemeta(shift @_); + my $level = shift @_; + + die "$srcfile is not read/writeable!\n" if( ! -r $srcfile || ! -w $srcfile); + open(I, "< $srcfile") or die "remove_include: couldn't open '$srcfile'\n"; + my @contents = <I>; + close(I); + + # ok, CPU time doesn't count so we do it the lazy way + # we should remove the last occurence of the include in the + # file because in case its a duplicate removing the first + # one could make a difference. + my @revcontents = reverse @contents; + @contents = (); + + # we skip all inludes that are somehow ifdefed + # note the logic is reversed because it operates + # on reversed lines :) + my $cpplevel = 0; + $cpplevel = -1 if ($srcfile=~m/^.*\.$hExt$/); # plan for header-protection #ifndef/#define/#endif + foreach $line (@revcontents) { + if ($line =~ m/^\#if/) { + $cpplevel = $cpplevel - 1; + push @contents, $line; + next; + } + + if ($line =~ m/^\#endif/) { + $cpplevel = $cpplevel + 1; + push @contents, $line; + next; + } + + if ($level && $cpplevel == 0 && + (($line =~ m/^\#include\s*\"$include\"\S*/) || ($line =~ m/^\#include\s*\<$include\>\S*/))) { + $level = $level - 1; + # skipping the line.. + next; + } + + push @contents, $line; + } + + # now we have the fixed contents in @contents, although in wrong order + open(O, "> $srcfile") || die "remove_include: couldn't open '$srcfile' for writing\n"; + print O reverse @contents; + close (O); +} + +# first parameter: srcfile +# second parameter: include to replace +# third parameter the include file to replace it with +sub replace_include ($$$) +{ + my $srcfile = shift @_; + my $include = quotemeta(shift @_); + my $destinclude = shift @_; + + die "$srcfile is not read/writeable!\n" if( ! -r $srcfile || ! -w $srcfile); + open(I, "< $srcfile") or die "replace_include: couldn't open '$srcfile'\n"; + my @contents = <I>; + close(I); + + # ok, CPU time doesn't count so we do it the lazy way + my @revcontents = reverse @contents; + @contents = (); + + # we skip all inludes that are somehow ifdefed + # note the logic is reversed because it operates + # on reversed lines :) + my $cpplevel = 0; + $cpplevel = -1 if ($srcfile=~m/^.*\.$hExt$/); # plan for header-protection #ifndef/#define/#endif + foreach $line (@revcontents) { + if ($line =~ m/^\#if/) { + $cpplevel = $cpplevel - 1; + push @contents, $line; + next; + } + + if ($line =~ m/^\#endif/) { + $cpplevel = $cpplevel + 1; + push @contents, $line; + next; + } + + if ($cpplevel == 0 && + (($line =~ m/^\#include\s*\"$include\"\S*/) || ($line =~ m/^\#include\s*\<$include\>\S*/))) + { + print "HAH! found $include to replace in $srcfile!\n" if($verbose); + $line =~ s/(\#include\s*[\"<])$include([\">]\S*)/$1$destinclude$2/; + } + + push @contents, $line; + } + + # now we have the fixed contents in @contents + open(O, "> $srcfile") || die "replace_include: couldn't open '$srcfile' for writing\n"; + print O reverse @contents; + close (O); +} + +# fixes #include <foo.h> -> #include "foo.h" +sub replace_include_type ($$) +{ + my ($srcfile, $include) = @_; + + die "$srcfile is not read/writeable!\n" if( ! -r $srcfile || ! -w $srcfile); + open(I, "< $srcfile") or die "replace_include: couldn't open '$srcfile'\n"; + my @contents = <I>; + close(I); + + grep(s/^(\#include)\s*<$include>(.*)$/$1 \"$include\"$2/, @contents); + + # now we have the fixed contents in @contents + open(O, "> $srcfile") || die "replace_include: couldn't open '$srcfile' for writing\n"; + print O @contents; + close (O); +} + +sub fix_duplicates($) +{ + my $srcfile = shift @_; + + my @includes = &find_removable_includes($srcfile); + + my %inclMap = (); + + # initialize + foreach $include (@includes) { + $inclMap{$include} = 0; + } + + # count number of occurences + foreach $include (@includes) { + $inclMap{$include} = $inclMap{$include} + 1; + } + + # check for duplicates + foreach $include (keys %inclMap) { + next if $inclMap{$include} <= 1; + + print "$srcfile: duplicate level ". $inclMap{$include} .": ". $include ."\n"; + + &remove_include($srcfile, $include, $inclMap{$include} - 1) if($modify); + } +} + +sub extract_gcc_error($) +{ + my $out = shift; + + # print "out: $out\n"; + + while ($out =~ m/^(.*?):([0-9]+):(.*)$/mg) # filename:lineno:message + { + my $field1 = $1 || ""; + my $field2 = $2 || ""; + my $field3 = $3 || ""; + + # print "f1: $field1, f2: $field2, f3: $field3\n"; + + next if ($field3 =~ m/\s+warning:.*/); + next if ($field3 =~ m/^\s*$/); + return basename($field1); + } + return "BUG!"; +} + +sub fix_compat_includes($) +{ + my $srcfile = shift @_; + + my @includes = &find_removable_includes($srcfile); + + my %inclMap = (); + + # initialize + foreach $include (@includes) { + $inclMap{$include} = 0; + } + + # count number of occurences + foreach $include (@includes) { + $inclMap{$include} = $inclMap{$include} + 1; + } + + # check for compat headers + foreach $include (keys %inclMap) { + if( defined $compatmap{$include}) { + print "$srcfile: compat header: $include, to be replaced by ". $compatmap{$include} ."\n"; + &replace_include($srcfile, $include, $compatmap{$include}) if($modify); + } + } +} + +sub fix_include_type($) +{ + my $srcfile = shift @_; + my $srcdir = dirname($srcfile); + + open(I, "<$srcfile") || die "couldn't open $srcfile in _fix_include_type"; + my @bracketincs = grep s/^\s*\#include\s*<([^>]+)>\s*$/$1/, <I>; + close(I); + + foreach my $include (@bracketincs) { + next if (!(-r "$srcdir/$include")); + next if (grep (/^$include$/, @instheaders)); + next if ($include eq "config.h"); # oh don't get me started on that + + print "$srcfile: #include <$include> should use #include \"...\"\n"; + &replace_include_type($srcfile, $include) if($modify); + } +} + +# copies a file from src to dest, overwrites destination if exists +sub copy_file($$) +{ + my $src = shift(@_); + my $dst = shift(@_); + + open(I, "< $src") or die "copy_file: can't open $src for input\n"; + my @fcontents = <I>; + close(I); + open(O, "> $dst") or die "copy_file: can't open $dst for output\n"; + print O @fcontents; + close(O); +} + +# interrupt handler for fix_unnecessary +sub sighandler_fix_unnecessary() +{ + my($sig) = @_; + print "Caught a SIG$sig--shutting down after restoring $srcfile\n"; + chdir($srcdir); + unlink $srcfile || warn "couldn't unlink $srcfile"; + rename $localbackup, $srcfile || warn "couldn't rename $localbackup to $srcfile"; + exit(1); +} + +sub fix_unnecessary($) +{ + local $srcfile = shift @_; + local $srcdir = dirname($srcfile); + + # find canonical path for srcdir + my $origdir = cwd; + chdir($srcdir); + $srcdir = cwd; + print "srcdir=$srcdir\n" if($verbose); + + my $builddir = $srcdir; + my $makecmd = "make"; + if (defined $ENV{"OBJ_REPLACEMENT"}) + { + # we have to use sed here, because perl can't do s#a#b# + $builddir = `echo $srcdir | sed -e "\$OBJ_REPLACEMENT"`; + chomp $builddir; + $makecmd = "makeobj"; + } + print "builddir=$builddir\n" if($verbose); + + my $tot = $exp_success + $exp_failure; + print "=============== $srcfile (successes: $exp_success; total: $tot)\n"; + + $srcfile = basename($srcfile); + + # first figure out some details + my @includes = &find_removable_includes($srcfile); + + my $blanksrc = $srcfile; + $blanksrc =~ s/(.*)\.[^\.]+/$1/; + + print "Checking for initial compilation: "; + chdir($builddir); + my $objextension = "BUG"; + if($srcfile =~ /\.$hExt$/o) { + $output = `$makecmd all 2>&1`; + $objextension = "all" if ( 0 == ($? >> 8)); + } + else { + unlink "$blanksrc.lo"; + my $output = `$makecmd $blanksrc.lo 2>&1`; + $objextension = ".lo" if ( 0 == ($? >> 8)); + if($objextension eq "BUG") { + print "failed with .lo... "; + unlink "$blanksrc.o"; + $output = `$makecmd $blanksrc.o 2>&1`; + $objextension = ".o" if ( 0 == ($? >> 8)); + } + if($srcfile =~ /$hExt/) { + $output = `$makecmd $blanksrc.o 2>&1`; + $objextension = ".o" if ( 0 == ($? >> 8)); + } + } + if($objextension eq "BUG") { + warn "can't figure out right compile command for $srcfile :-(\n" . + "??? unused, or didn't compile in the first place?\n" . + "$output"; + chdir($origdir); + exit 1; + } + + print "worked with $objextension\n"; + + # now try to drop some includes + foreach $include (@includes) { + # kdatastream is special because + # it will break the application if removed even + # if it continues to compile + next if( $include eq "kdatastream.h"); + # I also like to have kdebug.h still in + # so that it's easy to add kdDebug calls + next if( $include eq "kdebug.h"); + # avoid this one as it might cause + # certain code parts to be disabled from compilation + next if( $include eq "qmodules.h"); + # don't remove this one either. causes conditional + # code to be compiled incorrectly + next if( $include eq "kdeversion.h"); + # don't remove the config.h include + # conditional code may depend on this file + next if( $include eq "config.h"); + # check if it is its own header file + my $blankhdr = $include; + $blankhdr =~ s/(.*)\.[^\.]+/$1/; + next if ($blankhdr eq $blanksrc); + + chdir($srcdir); + + local $localbackup = $srcfile . "#fixkdeincludes"; + + # preserve timestamp if possible for CVS + unlink $localbackup; + rename $srcfile, $localbackup; + copy_file($localbackup, $srcfile); + + # revert to backup in case of interrupt (Ctrl+C) + $SIG{'INT'} = \&sighandler_fix_unnecessary; + + # check if it still compiles + if($verbose) { + chdir($builddir); + # testing headers? need to compile everything + if($objextension eq "all") { + # wait a second for makefile timestamp comparisons + sleep 1; + `$makecmd all 2>&1`; + } + else { + unlink "$blanksrc$objextension"; + `$makecmd $blanksrc$objextension 2>&1`; + } + die "unexpected error $output\nexitcode=" . ($? >> 8) if($? >> 8); + chdir($srcdir); + } + + # duplicates have to be nuked here , so it will be dropped maximum once + print "trying without $include: "; + &remove_include($srcfile, $include, 1); + + chdir($builddir); + + # try if it compiles + if($objextension eq "all") { + sleep 1; + $output=`$makecmd $objextension 2>&1`; + } + else { + unlink "$builddir/$blanksrc$objextension"; + $output=`$makecmd $blanksrc$objextension 2>&1`; + } + my $retcode = ($? >> 8); + #print "retcode=$retcode\n$output" if ($verbose); + + chdir($srcdir); + if($retcode == 0) { + # wow, it worked, lets continue! + print "SUCCESS!\n"; + $SIG{'INT'} = 'DEFAULT'; + unlink $localbackup; + $exp_success = $exp_success + 1; + } + else { + # is this a fixable error? + if($objextension eq "all" and + &extract_gcc_error($output) ne $srcfile) { + print "failed (error in " . &extract_gcc_error($output) . ")\n"; + # FIXME: implement fixup of the compilation error + # so that we can be much more agressive in removing + # unneeded includes from headers + } + else + { + # better luck next time + print "FATALLY failed\n"; + } + unlink $srcfile; + rename $localbackup, $srcfile; + $SIG{'INT'} = 'DEFAULT'; + + $exp_failure = $exp_failure + 1; + } + } + + print "\n"; + + chdir($origdir); +} + +sub process_source_file($) +{ + local $file = shift @_; + my $pure = basename($file); + print "Checking: $file\n" if($verbose); + &fix_include_type($file); + &fix_compat_includes($file); + &fix_duplicates($file); + &fix_unnecessary($file) if ($experimental && !grep (/^$pure$/, @instheaders)); + print "\n" if ($verbose); +} + +sub check_for_automake_srcdir($) +{ + my $dir = shift; + + return 0 if !( -r "$dir/Makefile.am"); + return 1 if !( -r "$dir/Makefile"); + + # ok, now its either srcdir with srcdir==builddir, or its builddir, + # which we don't want. + + open(I, "<$dir/Makefile") || die "couldn't read $dir/Makefile"; + while(<I>) { + if(/^srcdir\s*=\s*(\S+)/) { + close(I); + + if($1 ne ".") { + print "Skipping build dir: $dir\n" if($verbose); + return 0; + } + return 1; + } + } + + close(I); + # ok, this makefile isn't generated by automake, we don't want that + return 0; +} + +############################################################################# +# here is the main logic +# + +# warn about modified files +if($modify) { + `cvscheck | grep '^[MmC]'`; + print "WARNING: you have pending local changes. You might commit them by accident!\n\n" if($? >> 8 == 0); +} + +if($experimental) { + print "WARNING: The experimental mode is indeed experimental.\n"; + print "It tries to reduce includes by testing if it would compile\n"; + print "without a particular include. It might introduce subtle bugs\n"; + print "or break compilation for make check or make final.\n\n"; + print "This operation mode is known to be unsafe. You've been warned.\n"; +} + +# process files from the command line, if any +if ( $#explicitfiles >= 0 ) { + foreach $file( @explicitfiles ) { + &process_source_file( $file ); + } + exit 0; +} + +# first generate a list of subdirectories +@dirlist = (); +push @dirlist, "." if (&check_for_automake_srcdir(".")); +die "current directory isn't srcdir!" if (!scalar @dirlist); +foreach $dir ( @dirlist ) { + opendir (DIR, "$dir") || warn "Couldn't read '$dir'"; + my $subdir = ""; + while( $subdir = readdir(DIR)) { + next if ($subdir =~ /^\./); + next if !( -d "$dir/$subdir"); + next if (! &check_for_automake_srcdir("$dir/$subdir")); + + push @dirlist, "$dir/$subdir"; + } + closedir(DIR); +} + +# now iterate over all subdirs +foreach $dir(@dirlist) { + + # check if this directory wants not to be fixed + if(open(M, "$dir/Makefile.am")) { + my @mcontents = grep /(\-UQT_NO_COMPAT|\-UKDE_NO_COMPAT)/, <M>; + close(M); + if ( @mcontents ) { + print "Skipping directory: $dir\n"; + next; + } + } + + @headers = &find_fixable_headers($dir); + @instheaders = &find_installed_headers($dir); + foreach $file(@headers) { + &process_source_file($file); + } + @sources = &find_fixable_sources($dir); + foreach $file(@sources) { + &process_source_file($file); + } +} |