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.12', '$include';
use File::Temp 'tempfile';
use IPC::Run3;
use Cwd;

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 = whole ($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: $!";
    }
    for my $include (@includes) {
        my $newtext = $ctext;
        $newtext =~ s!\Q$include!// C:MinimizeHeaders removed this: $include!;
        open my $out, ">:encoding(utf8)", $file or die $!;
        print $out $newtext;
        close $out or die $!;
        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;
}

# Read a whole file into a scalar

sub whole
{
    my ($file) = @_;
    if (! $file || ! -f $file) {
        croak "my \$text = whole ('file');";
    }
    my $text = '';
    open my $in, "<:encoding(utf8)", $file or die "Can't open '$file': $!";
    while (<$in>) {
        $text .= $_;
    }
    close $in or die "Can't close '$file': $!";
    return $text;
}

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 and IPC::Run3 using a command such as

cpan install C::Tokenize IPC::Run3

or

cpanm C::Tokenize IPC::Run3

if you have App::cpanminus installed already.


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