| #!/usr/bin/env fuchsia-vendored-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 platform |
| import os.path |
| import subprocess |
| import sys |
| |
| HOST_PLATFORM = "%s-%s" % ( |
| platform.system().lower().replace("darwin", "mac"), |
| { |
| "x86_64": "x64", |
| "aarch64": "arm64", |
| }[platform.machine()], |
| ) |
| |
| 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/+/HEAD/docs/development/source_code/layout.md#dependency-structure |
| '//build', |
| '//prebuilt', |
| '//sdk', |
| '//third_party', |
| |
| # The follow entries are temporarily allowed universally, but should be |
| # moved to //sdk or //src/lib: |
| # Code libraries |
| '//garnet/lib/rust', |
| '//garnet/public/lib', |
| '//garnet/public/rust', |
| '//zircon/public/lib', |
| '//zircon/system/public', |
| |
| # Tools |
| '//tools', |
| # Will move to //tools or //sdk: |
| '//garnet/go/src/pm:pm_bin(//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', |
| '//src/connectivity/overnet/lib/embedded:runtime', |
| ] |
| |
| 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, testonly, 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/' |
| # Targets marked testonly can depend on '//(../*)testing/' |
| while label != '//': |
| if dep.startswith(label + '/lib/'): |
| return True |
| if testonly and dep.startswith(label + '/testing'): |
| 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, 'prebuilt', 'third_party', 'gn', HOST_PLATFORM, 'gn') |
| targets = extract_build_graph( |
| gn_binary, os.path.join(fuchsia_root, args.out)) |
| |
| disallowed_dependencies = {} |
| for label, target in targets.items(): |
| if target['type'] not in target_types_to_check: |
| continue |
| label_area = area_for_label(fuchsia_root, label) |
| testonly = target['testonly'] |
| for dep in target['deps']: |
| dep_area = area_for_label(fuchsia_root, dep) |
| if not dep_allowed(label, label_area, dep, dep_area, testonly, |
| 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()) |