Repository navigation
feat(lint): add buildifier linting for Starlark files #1271
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -194,6 +194,10 @@ There are many linters for Python with no clear recommendation. | |
| We chose ruff because it is fast, and it has a good set of default checks. | ||
| Runs the [ruff checks](./.ruff.toml) for the host. | ||
|
|
||
| #### Linting for Bazel files | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this can be removed because it should already be covered by https://github.com/eclipse-score/communication/blob/main/CI.md#formatting-and-linting-of-bazel-files |
||
|
|
||
| Runs [buildifier](https://github.com/bazelbuild/buildtools/tree/main/buildifier) lint warnings on all Starlark files. Findings are uploaded as SARIF to Code Scanning and any finding fails the job. | ||
|
|
||
| #### API checks | ||
|
|
||
| Make sure that the publicly visible targets are part of the public API specification. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
| # Quality Tools | ||
|
|
||
| This document provides instructions for developers on how to execute the quality tools available in Score (Clang-Tidy, Ruff, CodeQL, Coverage, Sanitizers, Copyright Checker, and C++, Bazel Files Formatter) locally. | ||
| This document provides instructions for developers on how to execute the quality tools available in Score (Clang-Tidy, Ruff, Buildifier, CodeQL, Coverage, Sanitizers, Copyright Checker, and C++, Bazel Files Formatter) locally. | ||
|
|
||
| ## Clang-Tidy | ||
|
|
||
|
|
@@ -363,6 +363,33 @@ bazel run //:ruff.fix -- //docs/sphinx/utils/... | |
| > **Note:** Violations without an automatic fix are still reported in the terminal but leave no | ||
| > patch file, same as Clang-Tidy. | ||
|
|
||
| ### Buildifier | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should take a look how this plays along with |
||
|
|
||
| Buildifier lints the `BUILD`, `.bzl`, `.bazel` and `.BUILD` files and is used like the other linters | ||
| (`//:buildifier.check` / `//:buildifier.fix`, optionally with Bazel target patterns). Files are discovered | ||
| per package directory, so no package needs any buildifier-specific configuration. | ||
|
|
||
| ```bash | ||
| # Check all Starlark files (prints findings, fails if there are any) | ||
| bazel run //:buildifier.check | ||
|
|
||
| # Check / fix only some packages | ||
| bazel run //:buildifier.check -- //score/mw/com/... | ||
| bazel run //:buildifier.fix -- //score/mw/com/... | ||
| bazel run //:buildifier.check -- //... -//third_party/... | ||
|
|
||
| # Gating test (also part of `bazel test //...`) | ||
| bazel test //:buildifier_test | ||
|
|
||
| # Print the findings as SARIF (used by CI for Code Scanning) | ||
| bazel run //tools/lint:buildifier_sarif | ||
| ``` | ||
|
|
||
| > **Note:** Unlike the other linters, buildifier is not run via a `rules_lint` aspect (it has no good way to | ||
| > include BUILD files), so there is no `--config=buildifier` and no hold-the-line. Only | ||
| > `//pkg`, `//pkg:target` and `//pkg/...` patterns and their `-` exclusions are supported. | ||
| > Formatting is additionally checked by the formatter below. | ||
|
|
||
| ### C++ and Bazel Files Formatter | ||
|
|
||
| ```bash | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| # ******************************************************************************* | ||
| # Copyright (c) 2026 Contributors to the Eclipse Foundation | ||
| # | ||
| # See the NOTICE file(s) distributed with this work for additional | ||
| # information regarding copyright ownership. | ||
| # | ||
| # This program and the accompanying materials are made available under the | ||
| # terms of the Apache License Version 2.0 which is available at | ||
| # https://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| # ******************************************************************************* | ||
|
|
||
| """Developer entry point for buildifier: `bazel run //:buildifier.check|fix -- [patterns]`. | ||
|
|
||
| Resolves the Bazel-style package patterns (default `//...`) to files and runs | ||
| the buildifier binary on them. `check` prints the findings and fails if there | ||
| are any, `fix` applies all auto-fixable findings and formatting. | ||
| """ | ||
|
|
||
| import argparse | ||
| import os | ||
| import subprocess | ||
| import sys | ||
| from pathlib import Path | ||
|
|
||
| from tools.lint.buildifier_files import resolve_files | ||
|
|
||
| _MODES = { | ||
| "check": ["-mode=check", "-lint=warn"], | ||
| "fix": ["-mode=fix", "-lint=fix"], | ||
| } | ||
|
|
||
|
|
||
| def main() -> int: | ||
| parser = argparse.ArgumentParser(description=__doc__) | ||
| parser.add_argument("--mode", choices=sorted(_MODES), required=True) | ||
| parser.add_argument("--buildifier", required=True, help="Path of the buildifier binary.") | ||
| parser.add_argument("patterns", nargs="*") | ||
| # argparse would take `-//pkg/...` exclusions for options. | ||
| args, extra = parser.parse_known_args() | ||
| patterns = args.patterns + extra | ||
| if any(not p.lstrip("-").startswith("//") for p in extra): | ||
| parser.error(f"unrecognized arguments: {' '.join(extra)}") | ||
| if not patterns: | ||
| patterns = ["//..."] | ||
|
|
||
| workspace = Path(os.environ["BUILD_WORKSPACE_DIRECTORY"]) | ||
| print(f"=== buildifier {args.mode}: {' '.join(patterns)} ===", flush=True) | ||
| try: | ||
| files = resolve_files(workspace, patterns) | ||
| except ValueError as error: | ||
| print(error, file=sys.stderr) | ||
| return 1 | ||
|
|
||
| command = [os.path.abspath(args.buildifier), *_MODES[args.mode], *map(str, files)] | ||
| exit_code = subprocess.run(command, cwd=workspace).returncode | ||
| if args.mode == "fix" and exit_code == 0: | ||
| print("Review with: git diff") | ||
| return exit_code | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| sys.exit(main()) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| # ******************************************************************************* | ||
| # Copyright (c) 2026 Contributors to the Eclipse Foundation | ||
| # | ||
| # See the NOTICE file(s) distributed with this work for additional | ||
| # information regarding copyright ownership. | ||
| # | ||
| # This program and the accompanying materials are made available under the | ||
| # terms of the Apache License Version 2.0 which is available at | ||
| # https://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| # ******************************************************************************* | ||
|
|
||
| """Resolves Bazel-style package patterns to the Starlark files buildifier checks. | ||
|
|
||
| Supported patterns: `//pkg`, `//pkg:target` and `//pkg/...`, each optionally | ||
| prefixed with `-` to exclude the files it resolves to. | ||
| """ | ||
|
|
||
| from pathlib import Path | ||
|
|
||
| _SUFFIXES = (".bzl", ".bazel", ".sky", ".BUILD") | ||
|
|
||
|
|
||
| def _is_starlark_file(path: Path) -> bool: | ||
| return path.name == "BUILD" or path.name.endswith(_SUFFIXES) | ||
|
|
||
|
|
||
| def _is_skipped_dir(path: Path) -> bool: | ||
| # Hidden directories (.git, IDE state) hold no sources, and symlinked | ||
| # directories (the bazel-* output links) lead out of the source tree. | ||
| return path.name.startswith(".") or path.is_symlink() | ||
|
|
||
|
|
||
| def _resolve(workspace: Path, pattern: str) -> set[Path]: | ||
| if not pattern.startswith("//"): | ||
| raise ValueError(f"Unsupported pattern (expected [-]//pkg[:target] or [-]//pkg/...): {pattern}") | ||
| package = pattern[2:].split(":", 1)[0] | ||
| recursive = package == "..." or package.endswith("/...") | ||
| if recursive: | ||
| package = package[: -len("...")].rstrip("/") | ||
| root = workspace / package | ||
| if not root.is_dir(): | ||
| raise ValueError(f"No such package directory: {package or '.'}") | ||
|
|
||
| if not recursive: | ||
| return {p for p in root.iterdir() if p.is_file() and _is_starlark_file(p)} | ||
|
|
||
| files = set() | ||
| stack = [root] | ||
| while stack: | ||
| for entry in stack.pop().iterdir(): | ||
| if entry.is_dir(): | ||
| if not _is_skipped_dir(entry): | ||
| stack.append(entry) | ||
| elif _is_starlark_file(entry): | ||
| files.add(entry) | ||
| return files | ||
|
|
||
|
|
||
| def resolve_files(workspace: Path, patterns: list[str]) -> list[Path]: | ||
| """Returns the sorted Starlark files selected by `patterns`. | ||
|
|
||
| Raises ValueError for unsupported patterns, unknown directories or an | ||
| empty selection. | ||
| """ | ||
| included: set[Path] = set() | ||
| excluded: set[Path] = set() | ||
| for pattern in patterns: | ||
| if pattern.startswith("-"): | ||
| excluded |= _resolve(workspace, pattern[1:]) | ||
| else: | ||
| included |= _resolve(workspace, pattern) | ||
| selected = sorted(included - excluded) | ||
| if not selected: | ||
| raise ValueError(f"No Starlark files found for: {' '.join(patterns)}") | ||
| return selected |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Write to file, @LittleHuba know what that means :-)