blob: 3bbdadddab312b3ebcaa87a1ca97f940100c9247 [file] [log] [blame]
# 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.
def black(ctx):
tools_dir = _install_tools(ctx)
py_files = [
f
for f in ctx.scm.affected_files()
if f.endswith(".py") and
# recipes.py is vendored from the recipe engine and should be
# ignored.
f != "recipes.py"
]
if not py_files:
return
original_contents = {}
procs = {}
for filepath in py_files:
original = str(ctx.io.read_file(filepath))
original_contents[filepath] = original
procs[filepath] = ctx.os.exec([tools_dir + "/black", "-"], stdin = original)
for filepath, proc in procs.items():
res = proc.wait()
original = original_contents[filepath]
if res.stdout != original:
ctx.emit.finding(
filepath = filepath,
level = "error",
replacements = [res.stdout],
)
def proto_format(ctx):
tools_dir = _install_tools(ctx)
procs = []
for p in ctx.scm.affected_files():
if not p.endswith(".proto"):
continue
# TODO(olivernewman): Use a single `buf format` invocation and the
# --diff option once shac knows how to parse diffs.
cmd = [
tools_dir + "/buf",
"format",
"--exit-code",
p,
]
procs.append((p, ctx.os.exec(cmd, ok_retcodes = [0, 100])))
for p, proc in procs:
res = proc.wait()
if res.retcode == 100:
ctx.emit.finding(
filepath = p,
level = "error",
replacements = [res.stdout],
)
def proto_field_numbering(ctx):
"""Checks that recipe property protobuf files have clean field numbers.
Recipe property protos need not subscribe to normal protobuf maintenance
conventions such as never deleting fields or changing field numbers, because
they are only used to de/serialize JSON-encoded protos, never binary-encoded
protos.
So it's safe (and required, for code cleanliness) to keep proto fields
monotonically increasing with no jumps.
Args:
ctx: A ctx instance.
"""
for f in ctx.scm.affected_files():
# The recipe_proto directory contains protos that may be used in other
# places than just recipe properties, so we cannot safely renumber their
# fields.
if not f.endswith(".proto") or f.startswith("recipe_proto/"):
continue
res = ctx.os.exec(
[
"python3",
"scripts/renumber_proto_fields.py",
"--dry-run",
f,
],
).wait()
if res.stdout != str(ctx.io.read_file(f)):
ctx.emit.finding(
filepath = f,
level = "error",
replacements = [res.stdout],
)
def _install_tools(ctx):
install_dir = ctx.scm.root + "/.tools"
ctx.os.exec(
["scripts/install-shac-tools.sh", install_dir],
allow_network = True,
).wait()
return install_dir
def check_deps(ctx):
res = ctx.os.exec(
[
"python3",
"scripts/cleanup_deps.py",
"--check",
"--json-output",
"-",
],
ok_retcodes = [0, 65],
).wait()
if res.retcode == 65:
for file in json.decode(res.stdout):
# TODO(olivernewman): Parse the diff so fixes can be applied with
# `shac fix`.
ctx.emit.finding(
filepath = file,
message = "DEPS are malformatted. Run ./scripts/cleanup_deps.py to fix.",
level = "error",
)
def recipe_style_guide(ctx):
"""Enforces http://go/fuchsia-recipe-docs#style-guide."""
procs = []
for f in ctx.scm.affected_files():
if f.endswith(".json") and ".expected" in f:
test_name = f.split("/")[-1].rsplit(".", 1)[0]
if " " in test_name:
# It would be nicer to parse the recipe file to find test case
# names because then we could emit the finding at the place
# where the test name is defined. But because tests are
# generated by arbitrary Python code, it's practically
# impossible to parse out their names in a foolproof way.
ctx.emit.finding(
filepath = f,
message = "Test name %r should not contain spaces" % test_name,
level = "error",
)
if not f.endswith(".py"):
continue
procs.append(ctx.os.exec(["python3", "scripts/enforce_style_guide.py", f]))
for proc in procs:
res = proc.wait()
for finding in json.decode(res.stdout):
ctx.emit.finding(**finding)
shac.register_check(shac.check(black, formatter = True))
shac.register_check(shac.check(proto_format, formatter = True))
shac.register_check(shac.check(proto_field_numbering, formatter = True))
shac.register_check(check_deps)
shac.register_check(recipe_style_guide)