Update the check-includes script to enforce some naming and no-circularity rules

This commit is contained in:
Nick Mathewson 2018-11-07 10:30:46 -05:00
parent 4ecd5da306
commit c3c8c926bf

View file

@ -33,6 +33,9 @@ else:
def open_file(fname): def open_file(fname):
return open(fname, 'r', encoding='utf-8') return open(fname, 'r', encoding='utf-8')
def warn(msg):
print(msg, file=sys.stderr)
def err(msg): def err(msg):
""" Declare that an error has happened, and remember that there has """ Declare that an error has happened, and remember that there has
been an error. """ been an error. """
@ -48,14 +51,34 @@ def fname_is_c(fname):
INCLUDE_PATTERN = re.compile(r'\s*#\s*include\s+"([^"]*)"') INCLUDE_PATTERN = re.compile(r'\s*#\s*include\s+"([^"]*)"')
RULES_FNAME = ".may_include" RULES_FNAME = ".may_include"
ALLOWED_PATTERNS = [
re.compile(r'^.*\*\.(h|inc)$'),
re.compile(r'^.*/.*\.h$'),
re.compile(r'^ext/.*\.c$'),
re.compile(r'^orconfig.h$'),
re.compile(r'^micro-revision.i$'),
]
def pattern_is_normal(s):
for p in ALLOWED_PATTERNS:
if p.match(s):
return True
return False
class Rules(object): class Rules(object):
""" A 'Rules' object is the parsed version of a .may_include file. """ """ A 'Rules' object is the parsed version of a .may_include file. """
def __init__(self, dirpath): def __init__(self, dirpath):
self.dirpath = dirpath self.dirpath = dirpath
if dirpath.startswith("src/"):
self.incpath = dirpath[4:]
else:
self.incpath = dirpath
self.patterns = [] self.patterns = []
self.usedPatterns = set() self.usedPatterns = set()
def addPattern(self, pattern): def addPattern(self, pattern):
if not pattern_is_normal(pattern):
warn("Unusual pattern {} in {}".format(pattern, self.dirpath))
self.patterns.append(pattern) self.patterns.append(pattern)
def includeOk(self, path): def includeOk(self, path):
@ -86,6 +109,20 @@ class Rules(object):
if p not in self.usedPatterns: if p not in self.usedPatterns:
print("Pattern {} in {} was never used.".format(p, self.dirpath)) print("Pattern {} in {} was never used.".format(p, self.dirpath))
def getAllowedDirectories(self):
allowed = []
for p in self.patterns:
m = re.match(r'^(.*)/\*\.(h|inc)$', p)
if m:
allowed.append(m.group(1))
continue
m = re.match(r'^(.*)/[^/]*$', p)
if m:
allowed.append(m.group(1))
continue
return allowed
def load_include_rules(fname): def load_include_rules(fname):
""" Read a rules file from 'fname', and return it as a Rules object. """ """ Read a rules file from 'fname', and return it as a Rules object. """
result = Rules(os.path.split(fname)[0]) result = Rules(os.path.split(fname)[0])
@ -99,6 +136,8 @@ def load_include_rules(fname):
list_unused = False list_unused = False
uses_dirs = { }
for dirpath, dirnames, fnames in os.walk("src"): for dirpath, dirnames, fnames in os.walk("src"):
if ".may_include" in fnames: if ".may_include" in fnames:
rules = load_include_rules(os.path.join(dirpath, RULES_FNAME)) rules = load_include_rules(os.path.join(dirpath, RULES_FNAME))
@ -108,8 +147,33 @@ for dirpath, dirnames, fnames in os.walk("src"):
if list_unused: if list_unused:
rules.noteUnusedRules() rules.noteUnusedRules()
uses_dirs[rules.incpath] = rules.getAllowedDirectories()
if trouble: if trouble:
err( err(
"""To change which includes are allowed in a C file, edit the {} """To change which includes are allowed in a C file, edit the {}
files in its enclosing directory.""".format(RULES_FNAME)) files in its enclosing directory.""".format(RULES_FNAME))
sys.exit(1) sys.exit(1)
all_levels = []
n = 0
while uses_dirs:
n += 0
cur_level = []
for k in list(uses_dirs):
uses_dirs[k] = [ d for d in uses_dirs[k]
if (d in uses_dirs and d != k)]
if uses_dirs[k] == []:
cur_level.append(k)
for k in cur_level:
del uses_dirs[k]
n += 1
if cur_level:
print(n, cur_level)
if n > 100:
break
if uses_dirs:
print("Circular dependencies in here somewhere:", uses_dirs)
sys.exit(1)