Set up Rust linting and cargo-raze in CI

Change-Id: I4c7e603302a1033d5e5141466c4cc5a6131c6e35
Signed-off-by: Brian Silverman <bsilver16384@gmail.com>
diff --git a/tools/lint/BUILD b/tools/lint/BUILD
index 743c852..57d4e08 100644
--- a/tools/lint/BUILD
+++ b/tools/lint/BUILD
@@ -12,6 +12,17 @@
 )
 
 sh_binary(
+    name = "rustfmt",
+    srcs = ["rustfmt.sh"],
+    data = [
+        "@rust//:rustfmt",
+    ],
+    deps = [
+        "@bazel_tools//tools/bash/runfiles",
+    ],
+)
+
+sh_binary(
     name = "buildifier",
     srcs = ["buildifier.sh"],
     data = [
@@ -44,9 +55,12 @@
         ":buildifier",
         ":gofmt",
         ":prettier",
+        ":rustfmt",
         "//:gazelle-runner",
         "//tools/go:mirror_go_repos",
         "//tools/go:tweak_gazelle_go_deps",
+        "//tools/rust:tweak_cargo_raze_output",
+        "@cargo_raze//:raze",
         "@go_sdk//:bin/go",
     ],
     env = {
diff --git a/tools/lint/run-ci.sh b/tools/lint/run-ci.sh
index 0576110..a036c06 100755
--- a/tools/lint/run-ci.sh
+++ b/tools/lint/run-ci.sh
@@ -33,7 +33,7 @@
     "${go}" mod tidy -e
 }
 
-update_repos() {
+update_go_repos() {
     # Clear out the go_deps.bzl file so that gazelle won't hesitate to update
     # it. Without this step gazelle would never try to remove a dependency.
     cat > "${BUILD_WORKSPACE_DIRECTORY}"/go_deps.bzl <<EOF
@@ -60,6 +60,27 @@
     ./tools/go/mirror_go_repos --prune
 }
 
+rustfmt() {
+    ./tools/lint/rustfmt
+}
+
+cargo_raze() {
+    local -r cargo_raze="$(readlink -f external/cargo_raze/impl/cargo_raze_bin)"
+    export CARGO="$(readlink -f external/rust/bin/cargo)"
+    export RUSTC="$(readlink -f external/rust/bin/rustc)"
+    cd "${BUILD_WORKSPACE_DIRECTORY}"
+    # Note we don't run with --generate-lockfile here. If there's a new
+    # dependency, we don't want to download it, just failing with an error
+    # is sufficient.
+    "${cargo_raze}" --manifest-path=Cargo.toml
+}
+
+tweak_cargo_raze() {
+    local -r tweaker="$(readlink -f tools/rust/tweak_cargo_raze_output)"
+    cd "${BUILD_WORKSPACE_DIRECTORY}"
+    "${tweaker}" .
+}
+
 buildifier() {
     ./tools/lint/buildifier
 }
@@ -80,10 +101,13 @@
 readonly -a LINTERS=(
     gofmt
     gomod
-    update_repos
+    update_go_repos
     gazelle
     tweak_gazelle_go_deps
     clean_up_go_mirrors
+    rustfmt
+    cargo_raze
+    tweak_cargo_raze
     buildifier
     prettier
     git_status_is_clean  # This must the last linter.
@@ -91,8 +115,12 @@
 
 failure=0
 for linter in "${LINTERS[@]}"; do
+    echo "Running ${linter}..." >&2
     if ! (eval "${linter}"); then
+        echo "LINTER FAILURE: ${linter}" >&2
         failure=1
+    else
+        echo "${linter} succeeded" >&2
     fi
 done
 
diff --git a/tools/lint/rustfmt.sh b/tools/lint/rustfmt.sh
new file mode 100755
index 0000000..57ce64f
--- /dev/null
+++ b/tools/lint/rustfmt.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+
+# --- begin runfiles.bash initialization v2 ---
+# Copy-pasted from the Bazel Bash runfiles library v2.
+set -uo pipefail; f=bazel_tools/tools/bash/runfiles/runfiles.bash
+source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \
+  source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \
+  source "$0.runfiles/$f" 2>/dev/null || \
+  source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \
+  source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \
+  { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e
+# --- end runfiles.bash initialization v2 ---
+
+readonly RUSTFMT="$(rlocation rust/bin/rustfmt)"
+
+# Run everything from the root of the tree.
+cd "${BUILD_WORKSPACE_DIRECTORY}"
+
+# Find all the Rust files in the repo.
+rust_files=($(git ls-tree --name-only --full-tree -r @ \
+    | grep -v '^third_party/' \
+    | (grep '\.rs$' || :)))
+
+# If we have any Rust files, format them.
+if ((${#rust_files[@]} > 0)); then
+    exec "${RUSTFMT}" "${rust_files[@]}"
+fi