Set up Rust linting and cargo-raze in CI

Change-Id: I4c7e603302a1033d5e5141466c4cc5a6131c6e35
Signed-off-by: Brian Silverman <bsilver16384@gmail.com>
diff --git a/Cargo.toml b/Cargo.toml
index 4d31a6d..fe957f9 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -85,6 +85,7 @@
 # which avoid build.rs in cargo-raze itself or elsewhere. rules_rust is another
 # place to look.
 # https://github.com/bazelbuild/rules_rust/blob/main/bindgen/raze/Cargo.toml
+# https://github.com/google/cargo-raze/blob/main/impl/Cargo.toml
 default_gen_buildrs = true
 
 [package.metadata.raze.crates.cxx.'*']
diff --git a/build_tests/hello_lib.rs b/build_tests/hello_lib.rs
index 6374ce6..4f1e16f 100644
--- a/build_tests/hello_lib.rs
+++ b/build_tests/hello_lib.rs
@@ -1,10 +1,12 @@
 pub struct Greeter {
-        greeting: String,
+    greeting: String,
 }
 
 impl Greeter {
     pub fn new(greeting: &str) -> Greeter {
-        Greeter { greeting: greeting.to_string(), }
+        Greeter {
+            greeting: greeting.to_string(),
+        }
     }
 
     pub fn greet(&self, thing: &str) -> String {
diff --git a/build_tests/rust_hello.rs b/build_tests/rust_hello.rs
index 6c6bf9f..110b96e 100644
--- a/build_tests/rust_hello.rs
+++ b/build_tests/rust_hello.rs
@@ -5,17 +5,17 @@
 }
 
 fn main() {
-  let hello = hello_lib::Greeter::new("Hello");
-  println!("{},\n{}", hello.greet("world"), hello.greet("bazel"));
+    let hello = hello_lib::Greeter::new("Hello");
+    println!("{},\n{}", hello.greet("world"), hello.greet("bazel"));
 
-  let mut numbers = Vec::new();
-  for i in 1..=10 {
-      numbers.push(i);
-  }
-  println!("{:?}", numbers);
+    let mut numbers = Vec::new();
+    for i in 1..=10 {
+        numbers.push(i);
+    }
+    println!("{:?}", numbers);
 
-  let words = vec!["foo", "bar", "baz"];
-  println!("{:?}", words);
+    let words = vec!["foo", "bar", "baz"];
+    println!("{:?}", words);
 
-  println!("sqrt(4) = {}", unsafe { sqrt(4.0) });
+    println!("sqrt(4) = {}", unsafe { sqrt(4.0) });
 }
diff --git a/third_party/cargo_raze/cargo_raze.patch b/third_party/cargo_raze/cargo_raze.patch
index 54cd9d5..306fcf3 100644
--- a/third_party/cargo_raze/cargo_raze.patch
+++ b/third_party/cargo_raze/cargo_raze.patch
@@ -39,12 +39,20 @@
          ],
      }),
      includes = ["zlib/include/"],
-@@ -28,6 +28,7 @@ genrule(
+@@ -28,6 +28,15 @@ genrule(
      srcs = _ZLIB_HEADERS,
      outs = _ZLIB_PREFIXED_HEADERS,
      cmd = "cp $(SRCS) $(@D)/zlib/include/",
 +    visibility = ["//visibility:public"],
  )
++
++genrule(
++    name = "dep_z_include",
++    visibility = ["//visibility:public"],
++    outs = ["dep_z_include.env"],
++    srcs = [":copy_public_headers"],
++    cmd = "echo 'DEP_Z_INCLUDE=$${pwd}/'\"$$(dirname \"$$(echo \"$(locations :copy_public_headers)\" | awk '{ print $$1 }')\")\" > $@",
++)
  
  cc_library(
 
@@ -130,19 +138,21 @@
  
  [package.metadata.raze.crates.openssl.'*']
  additional_deps = ["@cargo_raze__openssl//:openssl"]
-@@ -106,6 +106,7 @@ additional_deps = ["@cargo_raze__libgit2
+@@ -106,6 +106,8 @@ additional_deps = ["@cargo_raze__libgit2
  build_data_dependencies = [
      "@cargo_raze__libssh2//:libssh2",
      "@cargo_raze__openssl//:openssl",
 +    "@cargo_raze__zlib//:copy_public_headers",
  ]
  additional_deps = ["@cargo_raze__libssh2//:libssh2"]
++# TODO(Brian): Set buildrs_additional_environment_variables for upstreaming.
  
 
 --- third_party/cargo/remote/BUILD.libz-sys-1.1.2.bazel	2022-02-04 00:45:43.779201978 -0800
 +++ third_party/cargo/remote/BUILD.libz-sys-1.1.2.bazel	2022-02-04 00:45:57.151816346 -0800
-@@ -57,6 +57,7 @@ rust_library(
+@@ -57,6 +57,8 @@ rust_library(
      # buildifier: leave-alone
++    dep_env_files = ["@cargo_raze__zlib//:dep_z_include"],
      deps = [
          "@cargo_raze__libc__0_2_92//:libc",
 +        "@cargo_raze__zlib//:zlib",
@@ -152,15 +162,6 @@
 
 --- third_party/cargo/remote/BUILD.libssh2-sys-0.2.21.bazel	2022-02-04 00:54:43.031966734 -0800
 +++ third_party/cargo/remote/BUILD.libssh2-sys-0.2.21.bazel	2022-02-04 00:54:44.272023742 -0800
-@@ -41,6 +41,8 @@ cargo_build_script(
-     name = "libssh2_sys_build_script",
-     srcs = glob(["**/*.rs"]),
-     build_script_env = {
-+        # TODO: Custom rule that does the copy and exports a Make variable with the folder?
-+        "DEP_Z_INCLUDE": "${pwd}/bazel-out/k8-fastbuild/bin/external/cargo_raze__zlib/zlib/include",
-     },
-     crate_features = [
-     ],
 @@ -48,6 +48,7 @@ cargo_build_script(
      data = glob(["**"]) + [
          "@cargo_raze__libssh2//:libssh2",
@@ -182,3 +183,15 @@
    "i686-unknown-freebsd",
    "powerpc-unknown-linux-gnu",
 
+--- impl/BUILD.bazel	2022-05-30 01:37:24.111005507 -0700
++++ impl/BUILD.bazel	2022-05-30 01:37:22.778944267 -0700
+@@ -26,6 +26,9 @@ rust_binary(
+     edition = "2018",
+     proc_macro_deps = all_crate_deps(proc_macro = True),
+     deps = [":cargo_raze"] + all_crate_deps(),
++    # TODO: Make Rust play happy with pic vs nopic. Details at:
++    # https://github.com/bazelbuild/rules_rust/issues/118
++    rustc_flags = ["-Crelocation-model=static"],
+ )
+ 
+ _TEST_DATA = glob(["src/**/*.template"]) + [
diff --git a/third_party/rules_rust/rust/private/rust.bzl b/third_party/rules_rust/rust/private/rust.bzl
index 58cc9c4..ad2fba8 100644
--- a/third_party/rules_rust/rust/private/rust.bzl
+++ b/third_party/rules_rust/rust/private/rust.bzl
@@ -270,6 +270,7 @@
         ctx = ctx,
         attr = ctx.attr,
         toolchain = toolchain,
+        dep_env_files = ctx.files.dep_env_files,
         crate_info = rust_common.create_crate_info(
             name = crate_name,
             type = crate_type,
@@ -534,6 +535,14 @@
         """),
         allow_files = True,
     ),
+    "dep_env_files": attr.label_list(
+        doc = dedent("""\
+            Files containing additional environment variables to set for build scripts of direct dependencies.
+
+            See `rustc_env_files` for formatting details.
+        """),
+        allow_files = True,
+    ),
     "rustc_flags": attr.string_list(
         doc = dedent("""\
             List of compiler flags passed to `rustc`.
diff --git a/third_party/rules_rust/rust/private/rustc.bzl b/third_party/rules_rust/rust/private/rustc.bzl
index 596c10d..0c3df5e 100644
--- a/third_party/rules_rust/rust/private/rustc.bzl
+++ b/third_party/rules_rust/rust/private/rustc.bzl
@@ -124,6 +124,7 @@
     return False
 
 def collect_deps(
+        ctx,
         deps,
         proc_macro_deps,
         aliases,
@@ -200,6 +201,21 @@
 
     transitive_crates_depset = depset(transitive = transitive_crates)
 
+    dep_env = None
+    dep_env_files = list(ctx.files.dep_env_files)
+    if build_info:
+        if build_info.dep_env:
+            dep_env_files.append(build_info.dep_env)
+    if len(dep_env_files) > 1:
+        dep_env = ctx.actions.declare_file("_depenv/" + crate_info.output.basename)
+        ctx.actions.run_shell(
+            outputs = [dep_env],
+            inputs = dep_env_files,
+            cmd = ["cat"] + [f.path for f in dep_env_files] + [">", dep_env.path],
+        )
+    elif dep_env_files:
+        dep_env = dep_env_files[0]
+
     return (
         rust_common.dep_info(
             direct_crates = depset(direct_crates),
@@ -211,7 +227,7 @@
             transitive_crate_outputs = depset(transitive = transitive_crate_outputs),
             transitive_build_infos = depset(transitive = transitive_build_infos),
             link_search_path_files = depset(transitive = transitive_link_search_paths),
-            dep_env = build_info.dep_env if build_info else None,
+            dep_env = dep_env,
         ),
         build_info,
         depset(transitive = linkstamps),
@@ -819,6 +835,7 @@
         crate_info,
         output_hash = None,
         rust_flags = [],
+        dep_env_files = [],
         force_all_deps_direct = False):
     """Create and run a rustc compile action based on the current rule's attributes
 
@@ -841,6 +858,7 @@
     cc_toolchain, feature_configuration = find_cc_toolchain(ctx)
 
     dep_info, build_info, linkstamps = collect_deps(
+        ctx = ctx,
         deps = crate_info.deps,
         proc_macro_deps = crate_info.proc_macro_deps,
         aliases = crate_info.aliases,
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