[util] Don't explode on unexpected format in fix_include_guard.py

The existing code assumed that there was an #ifndef line somewhere in
the file and exploded if not (calling None.group()). This patch
handles these cases more gracefully by having a notion of "unfixable"
files. It also changes the output format a bit to make the CI
wrapper's job a bit easier.

Signed-off-by: Rupert Swarbrick <rswarbrick@lowrisc.org>
diff --git a/ci/scripts/include-guard.sh b/ci/scripts/include-guard.sh
index 7577221..ffc6076 100755
--- a/ci/scripts/include-guard.sh
+++ b/ci/scripts/include-guard.sh
@@ -22,19 +22,11 @@
 }
 echo "Checking header guards on headers changed since $merge_base"
 
-TMPFILE="$(mktemp)" || {
-    echo >&2 "Failed to create temporary file"
-    exit 1
-}
-trap 'rm -f "$TMPFILE"' EXIT
-
 set -o pipefail
 git diff --name-only --diff-filter=ACMRTUXB "$merge_base" -- \
   "*.h" ':!*/vendor/*' | \
-    xargs -r util/fix_include_guard.py --dry-run | \
-    tee "$TMPFILE"
-if [ -s "$TMPFILE" ]; then
+    xargs -r util/fix_include_guard.py --dry-run || {
     echo -n "##vso[task.logissue type=error]"
     echo "Include guard check failed. Please run util/fix_include_guard.py on the above files."
     exit 1
-fi
+}
diff --git a/util/fix_include_guard.py b/util/fix_include_guard.py
index 2de10df..2bddda8 100755
--- a/util/fix_include_guard.py
+++ b/util/fix_include_guard.py
@@ -13,10 +13,10 @@
 #
 # This script will write the names of all affected files to stdout; if no such
 # output is present, all files have the correct guards. This script is
-# idempotent.
+# idempotent. If it cannot fix a file, the script will write the file name to
+# stderr and will exit at the end with a nonzero status code.
 
 import argparse
-import os
 import sys
 import re
 
@@ -42,7 +42,9 @@
         help='headers to fix guards for')
     args = parser.parse_args()
 
-    total_fixes = 0
+    total_unfixable = 0
+    total_fixable = 0
+
     for header_path in args.headers:
         header = Path(header_path).resolve().relative_to(REPO_TOP)
         if header.suffix != '.h' or 'vendor' in header.parts:
@@ -55,8 +57,27 @@
         header_text = header.read_text()
         header_original = header_text
 
+        # Find the first non-comment, non-whitespace line in the file
+        first_line_match = re.search(r'^\s*([^/].*)', header_text, re.MULTILINE)
+        if first_line_match is None:
+            total_unfixable += 1
+            print('No non-comment line found in `{}\'.'.format(header),
+                  file=sys.stderr)
+            continue
+
+        first_line = first_line_match.group(1).rstrip()
+
         # Find the old guard name, which will be the first #ifndef in the file.
-        old_guard = re.search(r'#ifndef +(\w+)', header_text).group(1)
+        # This should be an #ifndef line. If it isn't, we can't fix things
+        ifndef_match = re.match(r'#ifndef\s+(\w+)$', first_line)
+        if ifndef_match is None:
+            total_unfixable += 1
+            print('Unsupported first non-comment line in `{}\': {!r}.'
+                  .format(header, first_line),
+                  file=sys.stderr)
+            continue
+
+        old_guard = ifndef_match.group(1)
 
         # Fix the guards at the top, which are guaranteed to be there.
         header_text = re.sub('#(ifndef|define) +%s' % (old_guard, ),
@@ -70,12 +91,21 @@
 
         if header_text != header_original:
             print('Fixing header: "%s"' % (header, ), file=sys.stdout)
-            total_fixes += 1
+            total_fixable += 1
             if not args.dry_run:
                 header.write_text(header_text)
 
-    print('Fixed %d files.' % (total_fixes, ), file=sys.stderr)
+    if total_fixable:
+        verb = 'Would have fixed' if args.dry_run else 'Fixed'
+        print('{} {} files.'.format(verb, total_fixable), file=sys.stderr)
+    if total_unfixable:
+        print('Failed to fix {} files.'.format(total_unfixable),
+              file=sys.stderr)
+
+    # Pass if we fixed everything or there was nothing to fix.
+    unfixed = total_unfixable + (total_fixable if args.dry_run else 0)
+    return 1 if unfixed else 0
 
 
 if __name__ == '__main__':
-    main()
+    sys.exit(main())