Skip to content

Instantly share code, notes, and snippets.

@alexeagle
Created December 21, 2025 16:09
Show Gist options
  • Select an option

  • Save alexeagle/44fd5444b43707766f59a3be2422dc63 to your computer and use it in GitHub Desktop.

Select an option

Save alexeagle/44fd5444b43707766f59a3be2422dc63 to your computer and use it in GitHub Desktop.
Wrap 'bazel build' to run linting aspects, without using Aspect CLI
#!/usr/bin/env bash
#
# Shows an end-to-end workflow for linting without failing the build.
# This is meant to mimic the behavior of the `aspect lint` command that you'd have
# by using the Aspect CLI [lint task](https://github.com/aspect-build/rules_lint/blob/main/lint/lint.axl).
#
# To make the build fail when a linter warning is present, run with `--fail-on-violation`.
# To auto-fix violations, run with `--fix` (or `--fix --dry-run` to just print the patches)
set -o errexit -o pipefail -o nounset
if [ "$#" -eq 0 ]; then
echo "usage: lint.sh [target pattern...]"
exit 1
fi
fix=""
buildevents=$(mktemp)
filter='.namedSetOfFiles | values | .files[] | select(.name | endswith($ext)) | ((.pathPrefix | join("/")) + "/" + .name)'
# FIXME: Register your linters here
args=(
"--aspects=//tools/lint:linters.bzl%eslint"
"--aspects=//tools/lint:linters.bzl%keep-sorted"
# ... more linters
)
# NB: perhaps --remote_download_toplevel is needed as well with remote execution?
args+=(
"--build_event_json_file=$buildevents"
# Required for the buf allow_comment_ignores option to work properly
# See https://github.com/bufbuild/rules_buf/issues/64#issuecomment-2125324929
"--experimental_proto_descriptor_sets_include_source_info"
"--remote_download_regex='.*AspectRulesLint.*'"
)
# This is a rudimentary flag parser.
if [ $1 == "--fail-on-violation" ]; then
args+=(
"--@aspect_rules_lint//lint:fail_on_violation"
"--keep_going"
)
shift
else
args+=(
# Allow lints of code that fails some validation action
# See https://github.com/aspect-build/rules_ts/pull/574#issuecomment-2073632879
"--norun_validations"
# Without validation actions, the linters won't run unless we request their output
"--output_groups=rules_lint_human"
)
fi
# Allow a `--fix` option on the command-line.
# This happens to make output of the linter such as ruff's
# [*] 1 fixable with the `--fix` option.
# so that the naive thing of pasting that flag to lint.sh will do what the user expects.
if [ $1 == "--fix" ]; then
fix="patch"
args+=(
"--@aspect_rules_lint//lint:fix"
# Trigger the fixer actions to run by requesting the patch outputs
"--output_groups=rules_lint_patch"
)
shift
fi
# NB: the --dry-run flag must immediately follow the --fix flag
if [ $1 == "--dry-run" ]; then
fix="print"
shift
fi
# Run linters
bazel build ${args[@]} $@
# TODO: Maybe this could be hermetic with bazel run @bazel_lib//tools:jq or sth
# jq on windows outputs CRLF which breaks this script. https://github.com/jqlang/jq/issues/92
valid_reports=$(jq --arg ext .out --raw-output "$filter" "$buildevents" | tr -d '\r')
# Show the results.
while IFS= read -r report; do
# Exclude coverage reports, and check if the output is empty.
if [[ "$report" == *coverage.dat ]] || [[ ! -s "$report" ]]; then
# Report is empty. No linting errors.
continue
fi
echo "From ${report}:"
cat "${report}"
echo
done <<<"$valid_reports"
if [ -n "$fix" ]; then
valid_patches=$(jq --arg ext .patch --raw-output "$filter" "$buildevents" | tr -d '\r')
while IFS= read -r patch; do
# Exclude coverage, and check if the patch is empty.
if [[ "$patch" == *coverage.dat ]] || [[ ! -s "$patch" ]]; then
# Patch is empty. No linting errors.
continue
fi
case "$fix" in
"print")
echo "From ${patch}:"
cat "${patch}"
echo
;;
"patch")
patch -p1 <${patch}
;;
*)
echo >2 "ERROR: unknown fix type $fix"
exit 1
;;
esac
done <<<"$valid_patches"
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment