blob: f93237ba05a869e28bd1633319f0f36ac9744bf2 [file]
#!/usr/bin/env python3
# This file is licensed under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""Checks that files in selected source subtrees are Bazel target inputs."""
import subprocess
import sys
from fnmatch import fnmatch
from pathlib import Path, PurePosixPath
from typing import Set
BAZEL_REPOSITORY = "@llvm-project"
SUBTREES = [
# TODO: Add more directories as we support most of their files in bazel.
# "libc",
# "lldb",
"lld",
]
IGNORED_GLOBS = [
"*.md",
"*.txt",
"*.TXT", # Both cases are used
"*/.clang-format",
"*/.clang-tidy",
"*/.gitignore",
"*/cmake/*",
"*/CMakeLists.txt",
"*/docs/*",
"*/utils/*",
"clang/www/*",
"lld/test/Unit/lit.cfg.py", # cmake shims
"lld/test/Unit/lit.site.cfg.py.in", # cmake shims
]
def source_files(project_root: Path) -> Set[str]:
files = set()
for subtree in SUBTREES:
subtree_root = project_root / subtree
if not subtree_root.is_dir():
raise RuntimeError(f"source subtree does not exist: {subtree_root}")
for path in subtree_root.rglob("*"):
relative_path = path.relative_to(project_root)
source_path = relative_path.as_posix()
if path.is_file() and not any(
fnmatch(source_path, glob) for glob in IGNORED_GLOBS
):
files.add(source_path)
return files
def label_to_source_path(label: str) -> str:
repository, separator, remainder = label.partition("//")
if not separator or repository != BAZEL_REPOSITORY:
raise RuntimeError(f"unexpected Bazel label: {label}")
package, separator, target = remainder.partition(":")
if not separator:
raise RuntimeError(f"unexpected Bazel label: {label}")
return str(PurePosixPath(package, target))
def bazel_input_files(bazel_workspace: Path) -> Set[str]:
target_patterns = " union ".join(
f"{BAZEL_REPOSITORY}//{subtree}/..." for subtree in SUBTREES
)
query = (
f'kind("source file", {BAZEL_REPOSITORY}//...:* '
f"intersect deps({target_patterns}))"
)
result = subprocess.run(
["bazel", "query", "--output=label", query],
cwd=bazel_workspace,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
)
if result.returncode:
sys.stderr.write(result.stderr)
raise RuntimeError(f"bazel query failed with exit code {result.returncode}")
return {label_to_source_path(label) for label in result.stdout.splitlines()}
def main() -> int:
script = Path(__file__).resolve()
bazel_workspace = script.parents[1]
project_root = script.parents[3]
try:
files = source_files(project_root)
orphaned = files - bazel_input_files(bazel_workspace)
except (OSError, RuntimeError) as error:
print(f"error: {error}", file=sys.stderr)
return 2
if orphaned:
print("Files not used as inputs by any Bazel target:", file=sys.stderr)
for path in sorted(orphaned):
print(f" {path}", file=sys.stderr)
return 1
subtree_word = "subtree" if len(SUBTREES) == 1 else "subtrees"
print(f"All {len(files)} files in {len(SUBTREES)} {subtree_word} are Bazel inputs.")
return 0
if __name__ == "__main__":
sys.exit(main())