#! /usr/bin/python # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright 2008 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright 2008, 2010, Richard Lowe # # Check that header files conform to our standards # # Standards for all header files (lenient): # # 1) Begin with a comment containing a copyright message # # 2) Enclosed in a guard of the form: # # #ifndef GUARD # #define GUARD # #endif /* [!]GUARD */ # # The preferred form is without the bang character, but either is # acceptable. # # 3) Has a valid ident declaration # # Additional standards for system header files: # # 1) The file guard must take the form '_FILENAME_H[_]', where FILENAME # matches the basename of the file. If it is installed in a # subdirectory, it must be of the form _DIR_FILENAME_H. The form # without the trailing underscore is preferred. # # 2) All #include directives must use the <> form. # # 3) If the header file contains anything besides comments and # preprocessor directives, then it must be enclosed in a C++ guard of # the form: # # #ifdef __cplusplus # extern "C" { # #endif # # #ifdef __cplusplus # } # #endif # import re, os, sys from onbld.Checks.Copyright import is_copyright class HeaderFile(object): def __init__(self, fh, filename=None, lenient=False): self.file = fh self.lenient = lenient self.lineno = 0 self.has_copyright = False self.eof = False if filename: self.filename = filename else: self.filename = fh.name def getline(self): for line in self.file: self.lineno += 1 if not line or line.isspace(): continue else: line = line.rstrip('\r\n') # Recursively join continuation lines if line.endswith('\\'): line = line[0:-1] + self.getline() return line else: self.eof = True return '' # # Optionally take a line to start skipping/processing with # def skipcomments(self, curline=None): line = curline or self.getline() while line: # When lenient, allow C++ comments if self.lenient and re.search(r'^\s*//', line): line = self.getline() continue if not re.search(r'^\s*/\*', line): return line while not re.search(r'\*/', line): # # We explicitly exclude the form used in the # CDDL header rather than attempting to craft # a match for every possibly valid copyright # notice # if is_copyright(line): self.has_copyright = True line = self.getline() if is_copyright(line): self.has_copyright = True line = self.getline() return line def err(stream, msg, hdr): if not hdr.eof: stream.write("%s: line %d: %s\n" % (hdr.filename, hdr.lineno, msg)) else: stream.write("%s: %s\n" % (hdr.filename, msg)) # # Keyword strings (both expanded and literal) for the various SCMs # Be certain to wrap each full expression in parens. # idents = [ # SCCS r'((\%Z\%(\%M\%)\t\%I\%|\%W\%)\t\%E\% SMI)', r'(@\(#\)(\w[-\.\w]+\.h)\t\d+\.\d+(\.\d+\.\d+)?\t\d\d/\d\d/\d\d SMI)', ] IDENT = re.compile(r'(%s)' % '|'.join(idents)) def hdrchk(fh, filename=None, lenient=False, output=sys.stderr): found_ident = False guard = None ret = 0 hdr = HeaderFile(fh, filename=filename, lenient=lenient) # # Step 1: # # Headers must begin with a comment containing a copyright notice. We # don't validate the contents of the copyright, only that it exists # line = hdr.skipcomments() if not hdr.has_copyright: err(output, "Missing copyright in opening comment", hdr) ret = 1 # # Step 2: # # For application header files only, allow the ident string to appear # before the header guard. if lenient and line.startswith("#pragma ident") and IDENT.search(line): found_ident = 1 line = hdr.skipcomments() # # Step 3: Header guards # match = re.search(r'^#ifndef\s([a-zA-Z0-9_]+)$', line) if not match: err(output, "Invalid or missing header guard", hdr) ret = 1 else: guard = match.group(1) if not lenient: guardname = os.path.basename(hdr.filename) # # If we aren't being lenient, validate the name of the # guard # guardname = guardname.upper() guardname = guardname.replace('.', '_').replace('-','_') guardname = guardname.replace('+', "_PLUS") if not re.search(r'^_.*%s[_]?$' % guardname, guard): err(output, "Header guard does not match " "suggested style (_FILEPATH_H_)", hdr) ret = 1 line = hdr.getline() if not re.search(r'#define\s%s$' % guard, line): err(output, "Invalid header guard", hdr) ret = 1 if not line: line = hdr.skipcomments() else: line = hdr.skipcomments() # # Step 4: ident string # # We allow both the keyword and extracted versions # if (not found_ident and line.startswith("#pragma ident") and not IDENT.search(line)): err(output, "Invalid #pragma ident", hdr) ret = 1 else: line = hdr.skipcomments(line) # # Main processing loop # in_cplusplus = False found_endguard = False found_cplusplus = False found_code = False while line: if not (line.startswith('#') or line.startswith('using')): found_code = True line = hdr.getline() continue match = re.search(r'^#include(.*)$', line) if match: # # For system files, make sure #includes are of the form: # '#include ' # if not lenient and not re.search(r'\s<.*>', match.group(1)): err(output, "Bad include", hdr) ret = 1 elif not in_cplusplus and re.search(r'^#ifdef\s__cplusplus$', line): # # Start of C++ header guard. # Make sure it is of the form: # # #ifdef __cplusplus # extern "C" { # #endif # line = hdr.getline() if line == 'extern "C" {': line = hdr.getline() if line != '#endif': err(output, "Bad __cplusplus clause", hdr) ret = 1 else: in_cplusplus = True found_cplusplus = True else: continue elif in_cplusplus and re.search(r'^#ifdef\s__cplusplus$', line): # # End of C++ header guard. Make sure it is of the form: # # #ifdef __cplusplus # } # #endif # line = hdr.getline() if line == '}': line = hdr.getline() if line != '#endif': err(output, "Bad __cplusplus clause", hdr) ret = 1 else: in_cplusplus = False else: continue elif re.search(r'^#endif\s/\* [!]?%s \*/$' % guard, line): # # Ending header guard # found_endguard = True line = hdr.skipcomments() # # Check for missing end clauses # if (not lenient) and (not found_cplusplus) and found_code: err(output, "Missing __cplusplus guard", hdr) ret = 1 if in_cplusplus: err(output, "Missing closing #ifdef __cplusplus", hdr) ret = 1 if not found_endguard: err(output, "Missing or invalid ending header guard", hdr) ret = 1 return ret