blob: e8c955a0ee078a6ed2cc225e9f63e276252e2767 [file] [log] [blame]
#!/usr/bin/env python3
#
# Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
# for details. All rights reserved. Use of this source code is governed by a
# BSD-style license that can be found in the LICENSE file.
# Simple tool for verifying that sources from one layer do not reference
# sources from another layer.
#
# Currently it only checks that core runtime headers RUNTIME_LAYER_HEADERS
# are not included into any sources listed in SHOULD_NOT_DEPEND_ON_RUNTIME.
import os
import re
import sys
INCLUDE_DIRECTIVE_RE = re.compile(r'^#include "(.*)"')
RUNTIME_LAYER_HEADERS = [
'runtime/vm/isolate.h',
'runtime/vm/object.h',
'runtime/vm/raw_object.h',
'runtime/vm/thread.h',
]
SHOULD_NOT_DEPEND_ON_RUNTIME = [
'runtime/vm/allocation.h',
'runtime/vm/growable_array.h',
]
class LayeringChecker(object):
def __init__(self, root):
self.root = root
self.worklist = set()
# Mapping from header to a set of files it is included into.
self.included_into = dict()
# Set of files that were parsed to avoid double parsing.
self.loaded = set()
# Mapping from headers to their layer.
self.file_layers = {file: 'runtime' for file in RUNTIME_LAYER_HEADERS}
def Check(self):
self.AddAllSourcesToWorklist(os.path.join(self.root, 'runtime/vm'))
self.BuildIncludesGraph()
errors = self.PropagateLayers()
errors += self.CheckNotInRuntime(SHOULD_NOT_DEPEND_ON_RUNTIME)
return errors
def CheckNotInRuntime(self, files):
"""Check that given files do not depend on runtime layer."""
errors = []
for file in files:
if not os.path.exists(os.path.join(self.root, file)):
errors.append('File %s does not exist.' % (file))
if self.file_layers.get(file) is not None:
errors.append(
'LAYERING ERROR: %s includes object.h or raw_object.h' %
(file))
return errors
def BuildIncludesGraph(self):
while self.worklist:
file = self.worklist.pop()
deps = self.ExtractIncludes(file)
self.loaded.add(file)
for d in deps:
if d not in self.included_into:
self.included_into[d] = set()
self.included_into[d].add(file)
if d not in self.loaded:
self.worklist.add(d)
def PropagateLayers(self):
"""Propagate layering information through include graph.
If A is in layer L and A is included into B then B is in layer L.
"""
errors = []
self.worklist = set(self.file_layers.keys())
while self.worklist:
file = self.worklist.pop()
if file not in self.included_into:
continue
file_layer = self.file_layers[file]
for tgt in self.included_into[file]:
if tgt in self.file_layers:
if self.file_layers[tgt] != file_layer:
errors.add(
'Layer mismatch: %s (%s) is included into %s (%s)' %
(file, file_layer, tgt, self.file_layers[tgt]))
self.file_layers[tgt] = file_layer
self.worklist.add(tgt)
return errors
def AddAllSourcesToWorklist(self, dir):
"""Add all *.cc and *.h files from dir recursively into worklist."""
for file in os.listdir(dir):
path = os.path.join(dir, file)
if os.path.isdir(path):
self.AddAllSourcesToWorklist(path)
elif path.endswith('.cc') or path.endswith('.h'):
self.worklist.add(os.path.relpath(path, self.root))
def ExtractIncludes(self, file):
"""Extract the list of includes from the given file."""
deps = set()
with open(os.path.join(self.root, file), encoding='utf-8') as file:
for line in file:
if line.startswith('namespace dart {'):
break
m = INCLUDE_DIRECTIVE_RE.match(line)
if m is not None:
header = os.path.join('runtime', m.group(1))
if os.path.isfile(os.path.join(self.root, header)):
deps.add(header)
return deps
def DoCheck(sdk_root):
"""Run layering check at the given root folder."""
return LayeringChecker(sdk_root).Check()
if __name__ == '__main__':
errors = DoCheck('.')
print('\n'.join(errors))
if errors:
sys.exit(-1)