| #!/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()) |