load_arbitrary_tool uses tool_suburl to look up sha256 (#1695)

* load_arbitrary_tool uses tool_suburl to look up sha256

In load_arbitrary_tool, use the suburl (which contains the a
subdirectory containing the date, if the version is beta or nightly) to
look up the sha256 to to download a specific archive. This allows Bazel
to cache the downloaded archive by hash, and supports the correct
behavior for dated nightly builds, as provided in the FILE_KEY_TO_SHA
dict in //rust:known_shas.bzl.

The most recent nightly entry is given in FILE_KEY_TO_SHA as:

```
    "2022-11-02/cargo-nightly-x86_64-unknown-linux-gnu.tar.gz": "d32e0a9f78ece567627b9b572912b000c53099c0dd9c9f5cea54848de02c6486",
```

This change fixes downloaded archive caching when using the nightly
toolchains, including the default nightly toolchain.

* Refactor load_arbitrary_tool()

Move the sha256 code out of load_arbitrary_tool() into a new function,
lookup_tool_sha256().

Implement a new unit test to confirm that the sha256 lookup code behaves
as expected, and returns the expected sha256 hash from the static list
of known tool hashes.
diff --git a/rust/private/repository_utils.bzl b/rust/private/repository_utils.bzl
index 5262e9c..50968b5 100644
--- a/rust/private/repository_utils.bzl
+++ b/rust/private/repository_utils.bzl
@@ -630,6 +630,30 @@
         fail("No tool version was provided")
     return "-".join([e for e in [tool_name, version, target_triple] if e])
 
+def lookup_tool_sha256(ctx, tool_name, target_triple, version, iso_date, sha256):
+    """Looks up the sha256 hash of a specific tool archive.
+
+    The lookup order is:
+
+    1. The sha256s dict in the context attributes;
+    2. The list of sha256 hashes populated in //rust:known_shas.bzl;
+    3. The sha256 argument to the function
+
+    Args:
+        ctx (repository_ctx): A repository_ctx (no attrs required).
+        tool_name (str): The name of the given tool per the archive naming.
+        target_triple (str): The rust-style target triple of the tool
+        version (str): The version of the tool among "nightly", "beta', or an exact version.
+        iso_date (str): The date of the tool (ignored if the version is a specific version).
+        sha256 (str): The expected hash of hash of the Rust tool.
+
+    Returns:
+        str: The sha256 of the tool archive, or an empty string if the hash could not be found.
+    """
+    tool_suburl = produce_tool_suburl(tool_name, target_triple, version, iso_date)
+    archive_path = tool_suburl + _get_tool_extension(ctx)
+    return getattr(ctx.attr, "sha256s", dict()).get(archive_path) or FILE_KEY_TO_SHA.get(archive_path) or sha256
+
 def load_arbitrary_tool(ctx, tool_name, tool_subdirectories, version, iso_date, target_triple, sha256 = ""):
     """Loads a Rust tool, downloads, and extracts into the common workspace.
 
@@ -671,8 +695,8 @@
             urls.append(new_url)
 
     tool_path = produce_tool_path(tool_name, target_triple, version)
-    archive_path = tool_path + _get_tool_extension(ctx)
-    sha256 = getattr(ctx.attr, "sha256s", dict()).get(archive_path) or FILE_KEY_TO_SHA.get(archive_path) or sha256
+
+    sha256 = lookup_tool_sha256(ctx, tool_name, target_triple, version, iso_date, sha256)
 
     for subdirectory in tool_subdirectories:
         # As long as the sha256 value is consistent accross calls here the
diff --git a/test/unit/repository_utils/repository_utils_test.bzl b/test/unit/repository_utils/repository_utils_test.bzl
index 10b0f52..76143d2 100644
--- a/test/unit/repository_utils/repository_utils_test.bzl
+++ b/test/unit/repository_utils/repository_utils_test.bzl
@@ -3,7 +3,7 @@
 load("@bazel_skylib//lib:unittest.bzl", "asserts", "unittest")
 
 # buildifier: disable=bzl-visibility
-load("//rust/private:repository_utils.bzl", "produce_tool_path", "produce_tool_suburl")
+load("//rust/private:repository_utils.bzl", "lookup_tool_sha256", "produce_tool_path", "produce_tool_suburl")
 
 def _produce_tool_suburl_test_impl(ctx):
     env = unittest.begin(ctx)
@@ -79,12 +79,88 @@
     )
     return unittest.end(env)
 
+def _lookup_tool_sha256_test_impl(ctx):
+    env = unittest.begin(ctx)
+
+    # Release version included in //rust:known_shas.bzl
+    asserts.equals(
+        env,
+        "6a30ffca17a244ad6bfb1d257572155f4e2b08d3ca2d852c2fc7420e264c6baa",
+        lookup_tool_sha256(
+            ctx,
+            tool_name = "rustc",
+            target_triple = "x86_64-unknown-linux-gnu",
+            version = "1.65.0",
+            iso_date = "2022-11-02",
+            sha256 = "",
+        ),
+    )
+
+    # Values in //rust:known_shas.bzl override sha256 arg
+    asserts.equals(
+        env,
+        "6a30ffca17a244ad6bfb1d257572155f4e2b08d3ca2d852c2fc7420e264c6baa",
+        lookup_tool_sha256(
+            ctx,
+            tool_name = "rustc",
+            target_triple = "x86_64-unknown-linux-gnu",
+            version = "1.65.0",
+            iso_date = "2022-11-02",
+            sha256 = "FAKE_SHA256_FROM_ARG",
+        ),
+    )
+
+    # Nightly version included in //rust:known_shas.bzl
+    asserts.equals(
+        env,
+        "187f7248dea1c0328e3fb26bb35ad7ba4df901cc34c1c6255260276de8fe3360",
+        lookup_tool_sha256(
+            ctx,
+            tool_name = "rust-std",
+            target_triple = "x86_64-unknown-linux-gnu",
+            version = "nightly",
+            iso_date = "2022-11-02",
+            sha256 = "",
+        ),
+    )
+
+    # Lookup failure (returns "") for a nightly version not included in //rust:known_shas.bzl
+    asserts.equals(
+        env,
+        "",
+        lookup_tool_sha256(
+            ctx,
+            tool_name = "rust-std",
+            target_triple = "x86_64-unknown-linux-gnu",
+            version = "nightly",
+            iso_date = "2022-11-01",
+            sha256 = "",
+        ),
+    )
+
+    # A nightly version not included in //rust:known_shas.bzl falls back to sha256 arg
+    asserts.equals(
+        env,
+        "FAKE_SHA256_FROM_ARG",
+        lookup_tool_sha256(
+            ctx,
+            tool_name = "rust-std",
+            target_triple = "x86_64-unknown-linux-gnu",
+            version = "nightly",
+            iso_date = "2022-11-01",
+            sha256 = "FAKE_SHA256_FROM_ARG",
+        ),
+    )
+    return unittest.end(env)
+
 produce_tool_suburl_test = unittest.make(_produce_tool_suburl_test_impl)
 produce_tool_path_test = unittest.make(_produce_tool_path_test_impl)
+lookup_tool_sha256_test = unittest.make(_lookup_tool_sha256_test_impl)
 
 def repository_utils_test_suite(name):
     unittest.suite(
         name,
         produce_tool_suburl_test,
         produce_tool_path_test,
+        lookup_tool_sha256_test,
     )