Skip to content

Commit

Permalink
Add autoformatting for Bazel files
Browse files Browse the repository at this point in the history
  • Loading branch information
npaun committed Aug 21, 2024
1 parent 8640269 commit b3f78e3
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 30 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ jobs:
chmod +x llvm.sh
sudo ./llvm.sh 18
sudo apt-get install -y --no-install-recommends clang-format-18
# buildifier won't install properly if specifying a particular version
go install github.com/bazelbuild/buildtools/buildifier@latest
- name: Install pnpm
uses: pnpm/action-setup@v4
# The pnpm version will be determined by the `packageManager` field in `.npmrc`
Expand Down
87 changes: 57 additions & 30 deletions tools/cross/format.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@
import subprocess
from argparse import ArgumentParser, Namespace
from typing import List, Optional, Tuple, Callable
from pathlib import Path
from dataclasses import dataclass


CLANG_FORMAT = os.environ.get("CLANG_FORMAT", "clang-format")
PRETTIER = os.environ.get("PRETTIER", "node_modules/.bin/prettier")
BUILDIFIER = os.environ.get(
"BUILDIFIER", Path(os.environ.get("HOME", "/")) / "go/bin/buildifier"
)


def parse_args() -> Namespace:
Expand Down Expand Up @@ -58,11 +62,16 @@ def parse_args() -> Namespace:
return options


def check_clang_format() -> bool:
def check_clang_format() -> None:
try:
# Run clang-format with --version to check its version
output = subprocess.check_output([CLANG_FORMAT, "--version"], encoding="utf-8")
major, _, _ = re.search(r"version\s*(\d+)\.(\d+)\.(\d+)", output).groups()
match = re.search(r"version\s*(\d+)\.(\d+)\.(\d+)", output)
if not match:
logging.error("unable to read clang version")
exit(1)

major, _, _ = match.groups()
if int(major) != 18:
logging.error("clang-format version must be 18")
exit(1)
Expand All @@ -72,17 +81,30 @@ def check_clang_format() -> bool:
exit(1)


def filter_files_by_exts(
files: List[str], dir_path: str, exts: Tuple[str, ...]
) -> List[str]:
def filter_files_by_globs(
files: List[Path], dir_path: Path, globs: Tuple[str, ...]
) -> List[Path]:
return [
file
for file in files
if file.startswith(dir_path + "/") and file.endswith(exts)
if is_relative_to(file, dir_path) and matches_any_glob(globs, file)
]


def clang_format(files: List[str], check: bool = False) -> bool:
def matches_any_glob(globs: Tuple[str, ...], file: Path) -> bool:
return any(file.match(glob) for glob in globs)


def is_relative_to(path: Path, prefix: Path) -> bool:
# Polyfill for method introduced in Python 3.9
try:
path.relative_to(prefix)
return True
except ValueError:
return False


def clang_format(files: List[Path], check: bool = False) -> bool:
cmd = [CLANG_FORMAT, "--verbose"]
if check:
cmd += ["--dry-run", "--Werror"]
Expand All @@ -92,25 +114,27 @@ def clang_format(files: List[str], check: bool = False) -> bool:
return result.returncode == 0


def prettier(files: List[str], check: bool = False) -> bool:
cmd = [PRETTIER]
if check:
cmd.append("--check")
else:
cmd.append("--write")
def prettier(files: List[Path], check: bool = False) -> bool:
cmd = [PRETTIER, "--check" if check else "--write"]
result = subprocess.run(cmd + files)
return result.returncode == 0


def buildifier(files: List[Path], check: bool = False) -> bool:
cmd = [BUILDIFIER, "--mode=check" if check else "--mode=fix"]
result = subprocess.run(cmd + files)
return result.returncode == 0


def git_get_modified_files(
target: str, source: Optional[str], staged: bool
) -> List[str]:
) -> List[Path]:
if staged:
files_in_diff = subprocess.check_output(
["git", "diff", "--diff-filter=d", "--name-only", "--cached"],
encoding="utf-8",
).splitlines()
return files_in_diff
return [Path(file) for file in files_in_diff]
else:
merge_base = subprocess.check_output(
["git", "merge-base", target, source or "HEAD"], encoding="utf-8"
Expand All @@ -120,53 +144,56 @@ def git_get_modified_files(
+ ([source] if source else []),
encoding="utf-8",
).splitlines()
return files_in_diff
return [Path(file) for file in files_in_diff]


def git_get_all_files() -> List[str]:
return subprocess.check_output(
def git_get_all_files() -> List[Path]:
files = subprocess.check_output(
["git", "ls-files", "--cached", "--others", "--exclude-standard"],
encoding="utf-8",
).splitlines()
return [Path(file) for file in files]


@dataclass
class FormatConfig:
directory: str
extensions: Tuple[str, ...]
formatter: Callable[[List[str], bool], bool]
globs: Tuple[str, ...]
formatter: Callable[[List[Path], bool], bool]


FORMATTERS = [
FormatConfig(
directory="src/workerd", extensions=(".c++", ".h"), formatter=clang_format
directory="src/workerd", globs=("*.c++", "*.h"), formatter=clang_format
),
FormatConfig(
directory="src",
extensions=(".js", ".ts", ".cjs", ".ejs", ".mjs"),
globs=("*.js", "*.ts", "*.cjs", "*.ejs", "*.mjs"),
formatter=prettier,
),
FormatConfig(directory="src", extensions=(".json",), formatter=prettier),
# TODO: lint bazel files
FormatConfig(directory="src", globs=("*.json",), formatter=prettier),
FormatConfig(
directory=".",
globs=("*.bzl", "*.bazel", "WORKSPACE", "BUILD", "BUILD.*"),
formatter=buildifier,
),
]


def format(config: FormatConfig, files: List[str], check: bool) -> bool:
matching_files = filter_files_by_exts(files, config.directory, config.extensions)
def format(config: FormatConfig, files: List[Path], check: bool) -> bool:
matching_files = filter_files_by_globs(files, Path(config.directory), config.globs)

if not matching_files:
return True

return config.formatter(matching_files, check)


def main():
def main() -> None:
options = parse_args()
check_clang_format()
if options.subcommand == "git":
files = set(
git_get_modified_files(options.target, options.source, options.staged)
)
files = git_get_modified_files(options.target, options.source, options.staged)
else:
files = git_get_all_files()

Expand Down

0 comments on commit b3f78e3

Please sign in to comment.