#!/usr/bin/env perl ###################################################################### # # This script find duplicates of #include files, ignoring #ifdef's, etc. # from C source files, and (at your command) removes the duplicates. # # It is meant to be run ONLY by FreeRADUS developers, and has nothing # whatsoever to do with RADIUS, FreeRADIUS, or configuring a RADIUS server. # ###################################################################### # # Run as: ./min-includes.pl `find . -name "*.c" -print` # prints out duplicate includes from files. # # ./min-includes.pl +n `find . -name "*.c" -print` # removes the duplicate includes from each file. # Remember to check that it still builds! # # It has to be run from the TOP of the FreeRADIUS build tree, # i.e. where the top-level "configure" script is located. # ###################################################################### # # FIXME: We don't handle include files taken from the current # directory... # # FIXME: we should take -I from the command line. # ###################################################################### # # Copyright (C) 2006 Alan DeKok # # $Id$ # ###################################################################### my %processed; $any_dups = 0; $debug = 0; # # Find the #include's for one file. # sub process($) { my $file = shift; return if ($processed{$file}); $processed{$file}++; open FILE, "<$file" or die "Failed to open $file: $!\n"; $line = 0; while () { $line++; next if (!/^\s*\#\s*include\s+/); if (/^\s*\#\s*include\s+"(.+?)"/) { $refs{$file}{$1} = $line; # FIXME: local header files? # src/foo/bar.c: #include "foo.h" # src/foo/foo.h do stuff.. $include{$1}++; } elsif (/^\s*\#\s*include\s+<(.+?)>/) { $refs{$file}{$1} = $line; $include{$1}++; } } close FILE; } # # Where include files are located. # # FIXME: # @directories = ("src/lib", "src"); $do_it = 0; # # Horrid. # if ($ARGV[0] eq "+n") { shift; $do_it = 1; } # # Bootstrap the basic C files. # foreach $file (@ARGV) { process($file); } # # Process the include files referenced from the C files, to find out # what they include Note that we create a temporary array, rather # than walking over %include, because the process() function adds # entries to the %include hash. # @work = sort keys %include; foreach $inc (@work) { foreach $dir (@directories) { $path = $dir . "/" . $inc; # normalize path $path =~ s:/.*?/\.\.::; $path =~ s:/.*?/\.\.::; next if (! -e $path); process($path); $forward{$inc} = $path; $reverse{$path} = $inc; # ignore system include files next if ((scalar keys %{$refs{$path}}) == 0); # Remember that X includes Y, and push Y onto the list # of files to scan. foreach $inc2 (sort keys %{$refs{$path}}) { $maps{$inc}{$inc2} = 0; push @work, $inc2; } } } # # Process all of the forward refs, so that we have a complete # list of who's referencing who. # # This doesn't find the shortest path from A to B, but it does # find one path. # foreach $inc (sort keys %maps) { foreach $inc2 (sort keys %{$maps{$inc}}) { foreach $inc3 (sort keys %{$maps{$inc2}}) { # map is already there... next if (defined $maps{$inc}{$inc3}); $maps{$inc}{$inc3} = $maps{$inc2}{$inc3} + 1; } } } # # Walk through the files again, looking for includes that are # unnecessary. Note that we process header files, too. # foreach $file (sort keys %refs) { # print out some debugging information. if ($debug > 0) { if (defined $reverse{$file}) { print $file, "\t(", $reverse{$file}, ")\n"; } else { print $file, "\n"; } } # walk of the list of include's in this file foreach $ref (sort keys %{$refs{$file}}) { # walk over the include files we include, or included by # files that we include. foreach $inc2 (sort keys %{$maps{$ref}}) { # # If we include X, and X includes Y, and we include # Y ourselves *after* X, it's a definite dupe. # # Note that this is a *guaranteed* duplicate. # # Sometimes order matters, so we can't always delete X if # we include Y after X, and Y includes X # if (defined $refs{$file}{$inc2} && ($refs{$file}{$inc2} > $refs{$file}{$ref})) { $duplicate{$file}{$inc2} = $ref; # mark the line to be deleted. $delete_line{$file}{$refs{$file}{$inc2}}++; $any_dups++; } } print "\t", $ref, "\n" if ($debug > 0); } } if ($debug > 0) { print "------------------------------------\n"; } # # Maybe just print out the dups so that a person can validate them. # if (!$do_it) { foreach $file (sort keys %duplicate) { print $file, "\n"; foreach $inc (sort keys %{$duplicate{$file}}) { print "\t[", $refs{$file}{$inc}, "] ", $inc, " (", $duplicate{$file}{$inc}, " at ", $refs{$file}{$duplicate{$file}{$inc}}, ")\n"; } } } else { foreach $file (sort keys %duplicate) { open FILE, "<$file" or die "Failed to open $file: $!\n"; open OUTPUT, ">$file.tmp" or die "Failed to create $file.tmp: $!\n"; $line = 0; while () { $line++; # supposed to delete this line, don't print it to the output. next if (defined $delete_line{$file}{$line}); print OUTPUT; } rename "$file.tmp", $file; } } # If we succeeded in re-writing the files, it's OK. exit 0 if ($do_it); # If there are no duplicates, then we're OK. exit 0 if (!$any_dups); # Else there are duplicates, complain. exit 1