blob: d0fc110a9559a4efbdf47d86b4bb843a98cb3a2c [file]
# Copyright 2026 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.
"""Defines SHAC checks for Bazel files."""
def _bazel_default_applicable_licenses(ctx):
"""Checks that non-third-party BUILD.bazel files have default_applicable_licenses set."""
allowlist_path = "build/bazel/shac/allowlists.json"
data = json.decode(str(ctx.io.read_file(allowlist_path)))
ignored_prefixes = data.get("ignored_prefixes", [])
ignored_files = data.get("ignored_files", [])
for f in ctx.scm.affected_files(glob = ["BUILD.bazel"]):
should_ignore = False
if f in ignored_files:
should_ignore = True
else:
for p in ignored_prefixes:
prefix = p if p.endswith("/") else p + "/"
if f.startswith(prefix):
should_ignore = True
break
if should_ignore:
continue
contents = str(ctx.io.read_file(f))
# Match package( ... default_applicable_licenses = ["//:license"] ... )
# Using [^)]* to match anything except closing parenthesis to stay within the package call.
if not ctx.re.allmatches(r"package\([^)#]*default_applicable_licenses\s*=\s*\[\"//:license\"\][^)]*\)", contents):
ctx.emit.finding(
level = "error",
message = "BUILD.bazel files must include `default_applicable_licenses = [\"//:license\"]` within a `package()` call to set the default file-level license.",
filepath = f,
)
def _enforce_bazel_build_file(ctx):
"""Checks that a BUILD.bazel file exists when a new FIDL library is created."""
# Check when :
# 1. The file is a new BUILD.gn file added under //sdk/fidl/.
# 2. The BUILD.gn file contains at least one fidl() template.
for path, meta in ctx.scm.affected_files(glob = "BUILD.gn").items():
if meta.action != "A":
continue
# TODO(https://fxbug.dev/487883318): Extend the check to FIDL libraries outside //sdk/fidl directory.
if not path.startswith("sdk/fidl/"):
continue
# Read the BUILD.gn content
content = str(ctx.io.read_file(ctx.scm.root + "/" + path))
# Check if it contains at least one fidl() template
has_fidl = False
for line in content.splitlines():
# Ignore comments
hash_idx = line.find("#")
if hash_idx != -1:
line_code = line[:hash_idx]
else:
line_code = line
if ctx.re.allmatches(r"\bfidl\s*\(\s*[\"\']", line_code):
has_fidl = True
break
# Skip if the BUILD.gn file doesn't contain any fidl() template
if not has_fidl:
continue
# Check if BUILD.bazel exists in the same directory
bazel_path = "/".join(path.split("/")[:-1] + ["BUILD.bazel"])
if not ctx.scm.all_files(glob = bazel_path):
ctx.emit.finding(
level = "error",
filepath = path,
message = "BUILD.bazel file is missing in the same directory as the added FIDL BUILD.gn file.",
)
def register_bazel_checks():
shac.register_check(shac.check(_bazel_default_applicable_licenses, formatter = False))
shac.register_check(shac.check(_enforce_bazel_build_file))