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