Remove unnecessary #include headers from C files

This Perl program finds C header files in a C program which are unnecessary. It works by editing the file to remove the header files one by one and then seeing if the file still compiles. It assumes that you have a "Makefile" and it tries to compile the C file using "make".

#!/home/ben/software/install/bin/perl
use warnings;
use strict;
use Carp;
use C::Tokenize '0.18', '$include';
use File::Temp 'tempfile';
use IPC::Run3;
use Cwd;
use File::Slurper qw!read_text write_text!;

sub minimize_headers
{
    my ($file, %options) = @_;
    my $verbose = $options{verbose};
    if ($file !~ /\.c$/) {
        croak "'$file' doesn't look like a C file";
    }
    my $ofile = $file;
    $ofile =~ s/\.c$/\.o/;
    my $dir = getcwd ();
    if ($ofile =~ m!^(.*)/([^/]+)$!) {
        $dir = $1;
        $ofile = $2;
    }
    my $make = $options{make};
    if (! $make) {
        $make = 'make';
    }
    if (-f $ofile) {
        unlink $ofile or die "unlink $ofile failed: $!";
    }
    my $command = "$make -C $dir $ofile";
    my $status = run3 ($command, undef, \my $output, \my $errors);
    if (-f $ofile) {
        unlink $ofile or die "unlink $ofile failed: $!";
    }
    if ($errors) {
        warn "$command failed, fix that and run again";
        return;
    }
    my (undef, $tempfile) = tempfile ("$file.XXXXX");
    if (! rename $file, $tempfile) {
        croak "Could not rename $file to $tempfile: $!";
    }
    my $ctext = read_text ($tempfile);
    my @includes;
    while ($ctext =~ /$include/g) {
        my $include = $1;
        chomp ($include);
        push @includes, $include;
    }
    if (-f $file) {
        unlink ($file) or die "Could not remove $file: $!";
    }
    my %uniques;
    for my $include (@includes) {
        if ($include !~ /#include\s*[<"](.*)[>"]/) {
            warn "Strange include '$include'";
        }
        else {
            my $ifile = $1;
            if ($uniques{$ifile}) {
                warn "File '$ifile' seems to be included more than once.\n";
            }
            else {
                $uniques{$ifile} = 1;
            }
        }
    }
    for my $include (@includes) {
        my $newtext = $ctext;
        $newtext =~ s!\Q$include!// C:MinimizeHeaders removed this: $include!g;
        write_text ($file, $newtext);
        run3 ("$make -C $dir $ofile", undef, \my $output, \my $errors);
        if (! $errors) {
            print "$file: '$include' may be unnecessary since it compiles without this.\n";
        }
        else {
            if ($verbose) {
                print "$file: '$include' seems to be necessary.\n";
                print "The make errors were as follows:\n$errors";
            }
        }
        if (! unlink ($file)) {
            croak "Could not remove $file: $!";
        }
        if (-f $ofile) {
            unlink $ofile or die "unlink $ofile failed: $!";
        }
    }
    if (! rename $tempfile, $file) {
        croak "Could not rename $tempfile to $file: $!";
    }
    return;
}

for my $file (@ARGV) {
    eval {
        minimize_headers ($file);
    };
    if ($@) {
        print "Minimize failed for '$file': $@";
    }
}
exit;

# Local Variables:
# mode: perl
# End:

(download)

The program requires some Perl modules to be installed. File::Temp, Cwd, and Carp come with Perl, but you need to install C::Tokenize, File::Slurper, and IPC::Run3 using a command such as

cpan install C::Tokenize IPC::Run3 File::Slurper

or

cpanm C::Tokenize IPC::Run3 File::Slurper

if you have App::cpanminus installed already.

Web links


Copyright © Ben Bullock 2009-2024. All rights reserved. For comments, questions, and corrections, please email Ben Bullock (benkasminbullock@gmail.com). / Privacy / Disclaimer