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