blob: 6130c0fbc79099fa0651973cc9a9c4b7ae5e5b3b [file] [log] [blame]
#!/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())