diff --git a/.bazelrc b/.bazelrc new file mode 100644 index 00000000..d18ae2f7 --- /dev/null +++ b/.bazelrc @@ -0,0 +1,2 @@ +build:clang-format-check --aspects //bazel/clang_format:clang_format_check.bzl%clang_format_check_aspect +build:clang-format-check --output_groups=report diff --git a/clang_format/BUILD b/clang_format/BUILD new file mode 100644 index 00000000..984e80de --- /dev/null +++ b/clang_format/BUILD @@ -0,0 +1,27 @@ +load(":choose_clang_format.bzl", "choose_clang_format") + +choose_clang_format( + name = "clang_format_bin", + visibility = ["//visibility:public"], +) + +filegroup( + name = "_clang_format_bin", + srcs = [":clang_format_bin"], +) + +sh_binary( + name = "clang_format", + srcs = [ + "run_clang_format.sh", + ], + args = [ + "format_all", + "$(location :_clang_format_bin)", + ], + data = [ + ":_clang_format_bin", + "//:clang_format_config", + ], + visibility = ["//visibility:public"], +) diff --git a/clang_format/choose_clang_format.bzl b/clang_format/choose_clang_format.bzl new file mode 100644 index 00000000..ddd26909 --- /dev/null +++ b/clang_format/choose_clang_format.bzl @@ -0,0 +1,26 @@ +def _choose_clang_format(ctx): + out = ctx.actions.declare_file("clang_format_bin.sh") + + ctx.actions.run_shell( + outputs = [out], + command = """ + if command -v clang-format-14 &> /dev/null + then + echo clang-format-14 \\"\\$@\\" > {0} + elif command -v clang-format &> /dev/null + then + echo clang-format \\"\\$@\\" > {0} + else + err_msg='clang-format-14 / clang-format: command not found' + echo $err_msg + echo "echo "$err_msg">&2" >> {0} + echo "exit 1" >> {0} + fi + """.format(out.path), + ) + + return [DefaultInfo(files = depset([out]))] + +choose_clang_format = rule( + implementation = _choose_clang_format, +) diff --git a/clang_format/clang_format_check.bzl b/clang_format/clang_format_check.bzl new file mode 100644 index 00000000..defef733 --- /dev/null +++ b/clang_format/clang_format_check.bzl @@ -0,0 +1,60 @@ +def _check_format(ctx, exe, config, infile, clang_format_bin): + output = ctx.actions.declare_file(infile.path + ".clang-format.txt") + + args = ctx.actions.args() + + args.add("check_file") + args.add(clang_format_bin.path) + args.add(infile.path) + args.add(output.path) + + ctx.actions.run( + inputs = [clang_format_bin, infile, config], + outputs = [output], + executable = exe, + arguments = [args], + mnemonic = "ClangFormat", + progress_message = "Check clang-format on {}".format(infile.short_path), + ) + return output + +def _extract_files(ctx): + files = [] + if hasattr(ctx.rule.attr, "srcs"): + for src in ctx.rule.attr.srcs: + files += [src for src in src.files.to_list() if src.is_source] + + if hasattr(ctx.rule.attr, "hdrs"): + for hdr in ctx.rule.attr.hdrs: + files += [hdr for hdr in hdr.files.to_list() if hdr.is_source] + + return files + +def _clang_format_check_aspect_impl(target, ctx): + # if not a C/C++ target, we are not interested + if not CcInfo in target: + return [] + + exe = ctx.attr._clang_format.files_to_run + config = ctx.attr._clang_format_config.files.to_list()[0] + clang_format_bin = ctx.attr._clang_format_bin.files.to_list()[0] + files = _extract_files(ctx) + + outputs = [] + for file in files: + if file.basename.endswith((".c", ".h", ".cpp", ".cc", ".hpp")): + outputs.append(_check_format(ctx, exe, config, file, clang_format_bin)) + + return [ + OutputGroupInfo(report = depset(direct = outputs)), + ] + +clang_format_check_aspect = aspect( + implementation = _clang_format_check_aspect_impl, + fragments = ["cpp"], + attrs = { + "_clang_format": attr.label(default = Label("//bazel/clang_format:clang_format")), + "_clang_format_config": attr.label(default = Label("//:clang_format_config")), + "_clang_format_bin": attr.label(default = Label("//bazel/clang_format:clang_format_bin")), + }, +) diff --git a/clang_format/run_clang_format.sh b/clang_format/run_clang_format.sh new file mode 100755 index 00000000..bf8f4ff7 --- /dev/null +++ b/clang_format/run_clang_format.sh @@ -0,0 +1,27 @@ +#! /bin/bash +# Usages: +# run_clang_format format_all +# run_clang_format check_file +set -ue + +format_all() { + cd $BUILD_WORKSPACE_DIRECTORY + git ls-files '*.[ch]' '*.cpp' '*.cc' '*.hpp' | xargs $CLANG_FORMAT_BIN -i +} + +check_file() { + INPUT=$1 + OUTPUT=$2 + + $CLANG_FORMAT_BIN $INPUT --dry-run -Werror > $OUTPUT +} + +ARG=$1 +CLANG_FORMAT_BIN=$(realpath $2) +shift 2 + +if [ "$ARG" == "format_all" ]; then + format_all +elif [ "$ARG" == "check_file" ]; then + check_file "$@" +fi