blob: 2a7a469c7183030ec5774069b2c739af5371d407 [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",
"arm64": "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())