| #!/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) |