| #!/usr/bin/env python |
| |
| # Copyright 2019 The Fuchsia Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import argparse |
| import json |
| import os.path |
| import subprocess |
| import sys |
| |
| fuchsia_root = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) |
| sys.path.append(os.path.join(fuchsia_root, 'src')) |
| |
| from area_dependency_exceptions import exceptions |
| |
| allowed_deps = [ |
| # These dependencies are always allowed: |
| # https://fuchsia.googlesource.com/fuchsia/+/refs/heads/master/docs/development/source_code/layout.md#dependency-structure |
| '//build', |
| '//buildtools', |
| '//sdk', |
| '//third_party', |
| |
| # The follow entries are temporarily allowed universally, but should be |
| # moved to //sdk or //src/lib: |
| # Code libraries |
| '//garnet/public/lib', |
| '//garnet/public/rust', |
| '//zircon/public/lib', |
| '//zircon/public/fidl', |
| '//zircon/public/banjo', |
| '//zircon/system/public', |
| |
| # Tools |
| '//garnet/go/src/pm:pm_bin(//build/toolchain:host_x64)', |
| '//garnet/bin/cmc:cmc(//build/toolchain:host_x64)', |
| '//zircon/public/tool', |
| '//garnet/go/src/fidl:fidlgen(//build/toolchain:host_x64)', |
| '//garnet/go/src/fidl:fidlgen_llcpp(//build/toolchain:host_x64)', |
| '//garnet/go/src/fidlmerge:fidlmerge(//build/toolchain:host_x64)', |
| |
| # This is currently implicitly generated as a dependency on any C++ |
| # generation of a FIDL target. |
| # TODO(ctiller): File an issue for cleaning this up. |
| '//src/connectivity/overnet/lib/protocol:fidl_stream', |
| ] |
| |
| target_types_to_check = [ |
| 'action', |
| 'executable', |
| 'loadable_module', |
| 'shared_library', |
| 'source_set', |
| 'static_library', |
| ] |
| |
| |
| class DisallowedDepsRecord: |
| def __init__(self): |
| self.count = 0 |
| self.labels = {} |
| |
| def AddBadDep(self, label, dep): |
| self.count = self.count + 1 |
| if label not in self.labels: |
| self.labels[label] = [] |
| self.labels[label].append(dep) |
| |
| |
| # An area is defined as a directory within //src/ that contains an OWNERS file. |
| # See docs/development/source_code/layout.md |
| def area_for_label(source_dir, label): |
| src_prefix = '//src/' |
| if not label.startswith(src_prefix): |
| return '' # Not in an area |
| if ':' in label: |
| label = label[0:label.find(':')] |
| while label != '//': |
| expected_owners_path = os.path.join(source_dir, label[2:], 'OWNERS') |
| if os.path.exists(expected_owners_path): |
| return label |
| label = os.path.dirname(label) |
| return '' |
| |
| |
| # Checks dependency rules as described in |
| # docs/development/source_code/layout.md#dependency-structure |
| def dep_allowed(label, label_area, dep, dep_area, ignore_exceptions): |
| # Targets can depend on globally allowed targets |
| for a in allowed_deps: |
| if dep.startswith(a): |
| return True |
| # Targets within an area can depend on other targets in the same area |
| if label_area == dep_area: |
| return True |
| # Targets can depend on '//(../*)lib' |
| while label != '//': |
| if dep.startswith(label + '/lib/'): |
| return True |
| label = os.path.dirname(label) |
| if ignore_exceptions: |
| return False |
| # Some areas are temporarily allowed additional dependencies |
| if label_area in exceptions: |
| prefixes = exceptions[label_area] |
| for prefix in prefixes: |
| if dep.startswith(prefix): |
| return True |
| return False |
| |
| |
| def record_bad_dep(bad_deps, area, label, bad_dep): |
| if area not in bad_deps: |
| bad_deps[area] = DisallowedDepsRecord() |
| bad_deps[area].AddBadDep(label, bad_dep) |
| |
| |
| def extract_build_graph(gn_binary, out_dir): |
| args = [gn_binary, 'desc', out_dir, '//src/*', '--format=json', |
| '--all-toolchains'] |
| json_build_graph = subprocess.check_output(args) |
| return json.loads(json_build_graph) |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser( |
| description='Check dependency graph in areas') |
| parser.add_argument('--out', default='out/default', |
| help='Build output directory') |
| parser.add_argument('--ignore-exceptions', action='store_true', |
| help='Ignore registered exceptions. ' + |
| 'Set to see all dependency issues') |
| args = parser.parse_args() |
| |
| gn_binary = os.path.join(fuchsia_root, 'buildtools', 'gn') |
| targets = extract_build_graph(gn_binary, os.path.join(fuchsia_root, |
| args.out)) |
| |
| disallowed_dependencies = {} |
| for label, target in targets.iteritems(): |
| if target['type'] not in target_types_to_check: |
| continue |
| label_area = area_for_label(fuchsia_root, label) |
| for dep in target['deps']: |
| dep_area = area_for_label(fuchsia_root, dep) |
| if not dep_allowed(label, label_area, dep, |
| dep_area, args.ignore_exceptions): |
| record_bad_dep(disallowed_dependencies, label_area, label, dep) |
| |
| total_count = 0 |
| for area in sorted(disallowed_dependencies.keys()): |
| disallowed_deps = disallowed_dependencies[area] |
| print 'Area %s has %d disallowed dependencies:' % ( |
| area, disallowed_deps.count) |
| total_count = total_count + disallowed_deps.count |
| |
| for label in sorted(disallowed_deps.labels.keys()): |
| bad_deps = disallowed_deps.labels[label] |
| print ' Target %s has %d disallowed dependencies:' % ( |
| label, len(bad_deps)) |
| for dep in sorted(bad_deps): |
| print ' %s' % dep |
| print |
| print |
| |
| print 'Found %d dependency errors' % total_count |
| if total_count != 0: |
| return 1 |
| return 0 |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main()) |