aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xscripts/clean-header-guards.pl213
1 files changed, 213 insertions, 0 deletions
diff --git a/scripts/clean-header-guards.pl b/scripts/clean-header-guards.pl
new file mode 100755
index 0000000000..54ab99ae29
--- /dev/null
+++ b/scripts/clean-header-guards.pl
@@ -0,0 +1,213 @@
+#!/usr/bin/perl -w
+#
+# Clean up include guards in headers
+#
+# Copyright (C) 2016 Red Hat, Inc.
+#
+# Authors:
+# Markus Armbruster <armbru@redhat.com>
+#
+# This work is licensed under the terms of the GNU GPL, version 2 or
+# (at your option) any later version. See the COPYING file in the
+# top-level directory.
+#
+# Usage: scripts/clean-header-guards.pl [OPTION]... [FILE]...
+# -c CC Use a compiler other than cc
+# -n Suppress actual cleanup
+# -v Show which files are cleaned up, and which are skipped
+#
+# Does the following:
+# - Header files without a recognizable header guard are skipped.
+# - Clean up any untidy header guards in-place. Warn if the cleanup
+# renames guard symbols, and explain how to find occurences of these
+# symbols that may have to be updated manually.
+# - Warn about duplicate header guard symbols. To make full use of
+# this warning, you should clean up *all* headers in one run.
+# - Warn when preprocessing a header with its guard symbol defined
+# produces anything but whitespace. The preprocessor is run like
+# "cc -E -DGUARD_H -c -P -", and fed the test program on stdin.
+
+use strict;
+use Getopt::Std;
+
+# Stuff we don't want to clean because we import it into our tree:
+my $exclude = qr,^(disas/libvixl/|include/standard-headers/
+ |linux-headers/|pc-bios/|tests/tcg/|tests/multiboot/),x;
+# Stuff that is expected to fail the preprocessing test:
+my $exclude_cpp = qr,^include/libdecnumber/decNumberLocal.h,;
+
+my %guarded = ();
+my %old_guard = ();
+
+our $opt_c = "cc";
+our $opt_n = 0;
+our $opt_v = 0;
+getopts("c:nv");
+
+sub skipping {
+ my ($fname, $msg, $line1, $line2) = @_;
+
+ return if !$opt_v or $fname =~ $exclude;
+ print "$fname skipped: $msg\n";
+ print " $line1" if defined $line1;
+ print " $line2" if defined $line2;
+}
+
+sub gripe {
+ my ($fname, $msg) = @_;
+ return if $fname =~ $exclude;
+ print STDERR "$fname: warning: $msg\n";
+}
+
+sub slurp {
+ my ($fname) = @_;
+ local $/; # slurp
+ open(my $in, "<", $fname)
+ or die "can't open $fname for reading: $!";
+ return <$in>;
+}
+
+sub unslurp {
+ my ($fname, $contents) = @_;
+ open (my $out, ">", $fname)
+ or die "can't open $fname for writing: $!";
+ print $out $contents
+ or die "error writing $fname: $!";
+ close $out
+ or die "error writing $fname: $!";
+}
+
+sub fname2guard {
+ my ($fname) = @_;
+ $fname =~ tr/a-z/A-Z/;
+ $fname =~ tr/A-Z0-9/_/cs;
+ return $fname;
+}
+
+sub preprocess {
+ my ($fname, $guard) = @_;
+
+ open(my $pipe, "-|", "$opt_c -E -D$guard -c -P - <$fname")
+ or die "can't run $opt_c: $!";
+ while (<$pipe>) {
+ if ($_ =~ /\S/) {
+ gripe($fname, "not blank after preprocessing");
+ last;
+ }
+ }
+ close $pipe
+ or gripe($fname, "preprocessing failed ($opt_c exit status $?)");
+}
+
+for my $fname (@ARGV) {
+ my $text = slurp($fname);
+
+ $text =~ m,\A(\s*\n|\s*//\N*\n|\s*/\*.*?\*/\s*\n)*|,msg;
+ my $pre = $&;
+ unless ($text =~ /\G(.*\n)/g) {
+ $text =~ /\G.*/;
+ skipping($fname, "no recognizable header guard", "$&\n");
+ next;
+ }
+ my $line1 = $1;
+ unless ($text =~ /\G(.*\n)/g) {
+ $text =~ /\G.*/;
+ skipping($fname, "no recognizable header guard", "$&\n");
+ next;
+ }
+ my $line2 = $1;
+ my $body = substr($text, pos($text));
+
+ unless ($line1 =~ /^\s*\#\s*(if\s*\!\s*defined(\s*\()?|ifndef)\s*
+ ([A-Za-z0-9_]+)/x) {
+ skipping($fname, "no recognizable header guard", $line1, $line2);
+ next;
+ }
+ my $guard = $3;
+ unless ($line2 =~ /^\s*\#\s*define\s+([A-Za-z0-9_]+)/) {
+ skipping($fname, "no recognizable header guard", $line1, $line2);
+ next;
+ }
+ my $guard2 = $1;
+ unless ($guard2 eq $guard) {
+ skipping($fname, "mismatched header guard ($guard vs. $guard2) ",
+ $line1, $line2);
+ next;
+ }
+
+ unless ($body =~ m,\A((.*\n)*)
+ (\s*\#\s*endif\s*(/\*\s*.*\s*\*/\s*)?\n?)
+ (\n|\s)*\Z,x) {
+ skipping($fname, "can't find end of header guard");
+ next;
+ }
+ $body = $1;
+ my $line3 = $3;
+ my $endif_comment = $4;
+
+ my $oldg = $guard;
+
+ unless ($fname =~ $exclude) {
+ my @issues = ();
+ $guard =~ tr/a-z/A-Z/
+ and push @issues, "contains lowercase letters";
+ $guard =~ s/^_+//
+ and push @issues, "is a reserved identifier";
+ $guard =~ s/(_H)?_*$/_H/
+ and $& ne "_H" and push @issues, "doesn't end with _H";
+ unless ($guard =~ /^[A-Z][A-Z0-9_]*_H/) {
+ skipping($fname, "can't clean up odd guard symbol $oldg\n",
+ $line1, $line2);
+ next;
+ }
+
+ my $exp = fname2guard($fname =~ s,.*/,,r);
+ unless ($guard =~ /\Q$exp\E\Z/) {
+ $guard = fname2guard($fname =~ s,^include/,,r);
+ push @issues, "doesn't match the file name";
+ }
+ if (@issues and $opt_v) {
+ print "$fname guard $oldg needs cleanup:\n ",
+ join(", ", @issues), "\n";
+ }
+ }
+
+ $old_guard{$guard} = $oldg
+ if $guard ne $oldg;
+
+ if (exists $guarded{$guard}) {
+ gripe($fname, "guard $guard also used by $guarded{$guard}");
+ } else {
+ $guarded{$guard} = $fname;
+ }
+
+ unless ($fname =~ $exclude) {
+ my $newl1 = "#ifndef $guard\n";
+ my $newl2 = "#define $guard\n";
+ my $newl3 = "#endif\n";
+ $newl3 =~ s,\Z, /* $guard */, if defined $endif_comment;
+ if ($line1 ne $newl1 or $line2 ne $newl2 or $line3 ne $newl3) {
+ $pre =~ s/\n*\Z/\n\n/ if $pre =~ /\N/;
+ $body =~ s/\A\n*/\n/;
+ if ($opt_n) {
+ print "$fname would be cleaned up\n" if $opt_v;
+ } else {
+ unslurp($fname, "$pre$newl1$newl2$body$newl3");
+ print "$fname cleaned up\n" if $opt_v;
+ }
+ }
+ }
+
+ preprocess($fname, $opt_n ? $oldg : $guard)
+ unless $fname =~ $exclude or $fname =~ $exclude_cpp;
+}
+
+if (%old_guard) {
+ print STDERR "warning: guard symbol renaming may break things\n";
+ for my $guard (sort keys %old_guard) {
+ print STDERR " $old_guard{$guard} -> $guard\n";
+ }
+ print STDERR "To find uses that may have to be updated try:\n";
+ print STDERR " git grep -Ew '", join("|", sort values %old_guard),
+ "'\n";
+}