blob: 903ae20d5ce5a48da190ba3749198aae5e8ce3d6 [file] [log] [blame] [edit]
# Copyright 2023 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.
load("./common.star", "FORMATTER_MSG", "cipd_platform_name", "get_fuchsia_dir", "os_exec")
def _pyfmt(ctx):
"""Formats Python code using autoflake, isort and black on a Python code base.
Args:
ctx: A ctx instance.
"""
py_files = [
f
for f in ctx.scm.affected_files()
if f.endswith(".py") and "third_party" not in f.split("/")
]
if not py_files:
return
# Format tools make conflicting code style changes. To ensure consistent formatting:
# 1. Run Autoflake to remove unused imports and variables.
# 2. Run isort to sort imports on the autoflake formatted code.
# 3. Run black on the isort formatted code to enforce its code style guidelines.
fuchsia_dir = get_fuchsia_dir(ctx)
platform = cipd_platform_name(ctx)
autoflake_cmd = [
"%s/prebuilt/third_party/python3/%s/bin/python3" % (
fuchsia_dir,
platform,
),
"%s/third_party/pylibs/autoflake/main.py" % fuchsia_dir,
"--remove-unused-variables",
"--remove-all-unused-imports",
"--remove-duplicate-keys",
"--ignore-init-module-imports",
"--stdout",
]
isort_cmd = [
"%s/prebuilt/third_party/python3/%s/bin/python3" % (
fuchsia_dir,
platform,
),
"%s/third_party/pylibs/isort/main.py" % fuchsia_dir,
# The skip flag is necessary to override the default autoflake behavior,
# which includes the "build/" directory in its skip section.
"--skip",
".venvs",
"--stdout",
"--filename",
]
black_cmd = [
"%s/prebuilt/third_party/black/%s/black" % (
fuchsia_dir,
platform,
),
"--config",
"%s/pyproject.toml" % fuchsia_dir,
"-",
]
# Run autoflake on each file
autoflake_procs = [(f, os_exec(ctx, autoflake_cmd + [f])) for f in py_files]
# Run isort on the output of the autoflake.
isort_procs = []
for f, proc in autoflake_procs:
formatted = proc.wait().stdout
isort_procs.append(
(f, os_exec(ctx, isort_cmd + [f, "-"], stdin = formatted)),
)
# Run black on the output of the isort.
black_procs = []
for f, proc in isort_procs:
formatted = proc.wait().stdout
black_procs.append(
(f, os_exec(ctx, black_cmd, stdin = formatted)),
)
for filepath, proc in black_procs:
original = str(ctx.io.read_file(filepath))
formatted = proc.wait().stdout
if formatted != original:
ctx.emit.finding(
level = "error",
message = FORMATTER_MSG,
filepath = filepath,
replacements = [formatted],
)
def _py_shebangs(ctx):
"""Validates that all Python script shebangs specify the vendored Python interpeter.
Scripts can opt out of this by adding a comment with
"allow-non-vendored-python" in a line after the shebang.
"""
ignore_paths = (
"build/bazel/",
"build/bazel_sdk/",
"infra/",
"integration/",
"vendor/",
"third_party/",
)
for path in ctx.scm.affected_files():
if not path.endswith(".py"):
continue
if path.startswith(ignore_paths):
continue
lines = str(ctx.io.read_file(path, 4096)).splitlines()
if not lines:
continue
first_line = lines[0]
want_shebang = "#!/usr/bin/env fuchsia-vendored-python"
if first_line.startswith("#!") and first_line != want_shebang:
if len(lines) > 1 and lines[1].startswith("# allow-non-vendored-python"):
continue
ctx.emit.finding(
level = "warning",
message = "Use fuchsia-vendored-python in shebangs for Python scripts.",
filepath = path,
line = 1,
replacements = [want_shebang + "\n"],
)
def register_python_checks():
shac.register_check(shac.check(_pyfmt, formatter = True))
shac.register_check(_py_shebangs)