blob: 8dd183cb1e12d430e4ec4f02af634ab80e8659a2 [file] [log] [blame]
#!/usr/bin/env fuchsia-vendored-python
# Copyright 2020 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.
from __future__ import print_function
import argparse
import os.path
import sys
# Exempt targets with these prefixes.
EXEMPTION_PREFIXES = [
# TODO(https://fxbug.dev/42134670): cargo-gnaw should generate sources files for third party crates.
"//third_party/rust_crates:",
]
def parse_depfile(depfile_path):
with open(depfile_path) as f:
# Only the first line contains important information.
line = f.readline().strip()
# The depfile format looks like `target: dep1 dep2 dep3...`
# We assume the target is the one we care about and that dep1, dep2, dep3, etc. are
# source files.
#
# We use `os.path.relpath` to convert paths like '../../out/default/foo/bar' to the
# canonical 'foo/bar', which is how the expected inputs are expressed.
return set(
os.path.relpath(os.path.normpath(source))
for source in line[line.find(":") + 1 :].split(" ")
if source.strip()
)
def build_file_source_path(target, source_path):
"""Returns a source path suitable for listing in a BUILD.gn file.
The returned path is relative to the `target` GN label, or source absolute if the
`source_path` is not a descendent of the `target`.
Eg. for `target` of '//src/sys/component_manager:bin':
when source_path='../../src/sys/component_manager/src/main.rs'
return 'src/main.rs'
when source_path='../../prebuilts/assets/font.ttf'
return '//prebuilts/assets/font.ttf'
"""
while source_path.startswith("../"):
source_path = source_path[3:]
target_dir = target[2:].split(":")[0]
if source_path.startswith(target_dir):
return os.path.relpath(source_path, start=target_dir)
return "//{}".format(source_path)
def print_suggested_sources(varname, sources):
"""Prints a GN list variable assignment with the variable name `varname`.
Eg.
sources = [
"src/main.rs",
"src/foo.rs",
]
"""
print(" {} = [".format(varname), file=sys.stderr)
for source in sources:
print(' "{}",'.format(source), file=sys.stderr)
print(" ]", file=sys.stderr)
def main():
parser = argparse.ArgumentParser(
description="Verifies that the compiler-emitted depfile strictly contains the expected source files"
)
parser.add_argument(
"-t",
"--target_label",
required=True,
help="GN target label being checked",
)
parser.add_argument(
"-d",
"--depfile",
required=True,
help="path to compiler emitted depfile",
)
parser.add_argument(
"expected_sources",
nargs="*",
help="path to the expected list of source files",
)
args = parser.parse_args()
# Check for opt-out.
if args.expected_sources and args.expected_sources[0].endswith(
"/build/rust/__SKIP_ENFORCEMENT__.rs"
):
return 0
# Ignore specific target exemptions.
for prefix in EXEMPTION_PREFIXES:
if args.target_label.startswith(prefix):
return 0
expected_sources = set(args.expected_sources)
actual_sources = parse_depfile(args.depfile)
unlisted_sources = actual_sources.difference(expected_sources)
if unlisted_sources:
# There is a mismatch in expected sources and actual sources used by the compiler.
# We don't treat overly-specified sources as an error. Ninja will still complain
# if those source files don't exist.
for source in unlisted_sources:
print(
"error: source file `{}` was used during compilation but not listed in BUILD.gn".format(
source
),
file=sys.stderr,
)
print(
"note: the BUILD.gn file for {} should have the following:\n".format(
args.target_label
),
file=sys.stderr,
)
rust_sources = [
build_file_source_path(args.target_label, source)
for source in actual_sources
if source.endswith(".rs")
]
if rust_sources:
print_suggested_sources("sources", rust_sources)
non_rust_sources = [
build_file_source_path(args.target_label, source)
for source in actual_sources
if not source.endswith(".rs")
]
if non_rust_sources:
print_suggested_sources("inputs", non_rust_sources)
return 1
return 0
if __name__ == "__main__":
sys.exit(main())