process_artifacts: Import Bazel's spawn.proto

I chose prost as the protocol buffer implementation because it's the
most popular one (c.f.  https://crates.io/keywords/protobuf) and
maintained by a trusted community (tokio). rules_rust also has prost
support (rust_prost_library) but I use cargo_build_script + build.rs for
now for rust-analyzer compatibility.

BUG=b:331890379
TEST=bazel test //bazel/portage/tools/process_artifacts:all

Change-Id: Ibffd40fc14bf198642a9671156b83ed2b715ca53
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/bazel/+/5528443
Tested-by: Shuhei Takahashi <nya@chromium.org>
Commit-Queue: Shuhei Takahashi <nya@chromium.org>
Reviewed-by: Raul Rangel <rrangel@chromium.org>
diff --git a/Cargo.lock b/Cargo.lock
index 8632e8b..4278675 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -161,7 +161,7 @@
 version = "0.1.0"
 dependencies = [
  "anyhow",
- "bytes",
+ "bytes 0.4.12",
  "cliutil",
  "fileutil",
  "processes",
@@ -291,6 +291,12 @@
 ]
 
 [[package]]
+name = "bytes"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9"
+
+[[package]]
 name = "bzip2"
 version = "0.4.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -702,7 +708,7 @@
 dependencies = [
  "anyhow",
  "binarypackage",
- "bytes",
+ "bytes 0.4.12",
  "bzip2",
  "clap",
  "cliutil",
@@ -754,6 +760,12 @@
 ]
 
 [[package]]
+name = "fixedbitset"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
+
+[[package]]
 name = "flate2"
 version = "1.0.27"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1127,6 +1139,12 @@
 ]
 
 [[package]]
+name = "multimap"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03"
+
+[[package]]
 name = "nix"
 version = "0.26.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1301,6 +1319,16 @@
 ]
 
 [[package]]
+name = "petgraph"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db"
+dependencies = [
+ "fixedbitset",
+ "indexmap",
+]
+
+[[package]]
 name = "pin-project-lite"
 version = "0.2.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1335,6 +1363,16 @@
 ]
 
 [[package]]
+name = "prettyplease"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d3928fb5db768cb86f891ff014f0144589297e3c6a1aba6ed7cecfdace270c7"
+dependencies = [
+ "proc-macro2",
+ "syn 2.0.55",
+]
+
+[[package]]
 name = "proc-macro-error"
 version = "1.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1374,6 +1412,9 @@
  "anyhow",
  "clap",
  "itertools",
+ "prost",
+ "prost-build",
+ "prost-types",
  "runfiles",
  "serde",
  "serde_json",
@@ -1413,6 +1454,59 @@
 ]
 
 [[package]]
+name = "prost"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0f5d036824e4761737860779c906171497f6d55681139d8312388f8fe398922"
+dependencies = [
+ "bytes 1.6.0",
+ "prost-derive",
+]
+
+[[package]]
+name = "prost-build"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80b776a1b2dc779f5ee0641f8ade0125bc1298dd41a9a0c16d8bd57b42d222b1"
+dependencies = [
+ "bytes 1.6.0",
+ "heck",
+ "itertools",
+ "log",
+ "multimap",
+ "once_cell",
+ "petgraph",
+ "prettyplease",
+ "prost",
+ "prost-types",
+ "regex",
+ "syn 2.0.55",
+ "tempfile",
+]
+
+[[package]]
+name = "prost-derive"
+version = "0.12.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9554e3ab233f0a932403704f1a1d08c30d5ccd931adfdfa1e8b5a19b52c1d55a"
+dependencies = [
+ "anyhow",
+ "itertools",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.55",
+]
+
+[[package]]
+name = "prost-types"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3235c33eb02c1f1e212abdbe34c78b264b038fb58ca612664343271e36e55ffe"
+dependencies = [
+ "prost",
+]
+
+[[package]]
 name = "quick-error"
 version = "1.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index cfd186b..274c9ce 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -81,6 +81,9 @@
 pathdiff = "0.2.1"
 pretty_assertions = "1.1.0"
 proptest = "1.0.0"
+prost = "0.12.4"
+prost-build = "0.12.4"
+prost-types = "0.12.4"
 protobuf = { version = "2.8.2", features = ["with-bytes"] }
 rand = "0.8.5"
 rayon = "1.6.0"
diff --git a/portage/tools/process_artifacts/BUILD.bazel b/portage/tools/process_artifacts/BUILD.bazel
index c1bf694..68b200e 100644
--- a/portage/tools/process_artifacts/BUILD.bazel
+++ b/portage/tools/process_artifacts/BUILD.bazel
@@ -2,19 +2,46 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+load("@rules_rust//cargo:defs.bzl", "cargo_build_script")
 load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_test")
 load("//bazel/build_defs:generate_cargo_toml.bzl", "generate_cargo_toml")
 load("//bazel/portage/build_defs:common.bzl", "RUSTC_DEBUG_FLAGS")
 
+# TODO: Use rust_prost_library instead. We use build.rs for now for
+# rust-analyzer compatibility.
+cargo_build_script(
+    name = "build_script",
+    srcs = ["build.rs"],
+    build_script_env = {
+        "PROTOC": "$(execpath @protobuf//:protoc)",
+        # This is an ugly hack to tell build.rs the include path for well known types.
+        "WELL_KNOWN_TYPES_MARKER": "$(execpath @protobuf//:LICENSE)",
+    },
+    data = [
+        "proto/third_party/spawn.proto",
+        "@protobuf//:LICENSE",
+        "@protobuf//:well_known_type_protos",
+    ],
+    tools = [
+        "@protobuf//:protoc",
+    ],
+    deps = [
+        "@alchemy_crates//:prost-build",
+    ],
+)
+
 rust_binary(
     name = "process_artifacts",
     srcs = glob(["src/**/*.rs"]),
     crate_name = "process_artifacts",
     rustc_flags = RUSTC_DEBUG_FLAGS,
     deps = [
+        ":build_script",
         "@alchemy_crates//:anyhow",
         "@alchemy_crates//:clap",
         "@alchemy_crates//:itertools",
+        "@alchemy_crates//:prost",
+        "@alchemy_crates//:prost-types",
         "@alchemy_crates//:serde",
         "@alchemy_crates//:serde_json",
         "@alchemy_crates//:tempfile",
diff --git a/portage/tools/process_artifacts/Cargo.toml b/portage/tools/process_artifacts/Cargo.toml
index 6bfbf35..20fa518 100644
--- a/portage/tools/process_artifacts/Cargo.toml
+++ b/portage/tools/process_artifacts/Cargo.toml
@@ -9,6 +9,8 @@
 anyhow.workspace = true
 clap.workspace = true
 itertools.workspace = true
+prost.workspace = true
+prost-types.workspace = true
 serde.workspace = true
 serde_json.workspace = true
 tempfile.workspace = true
@@ -16,3 +18,6 @@
 [dev-dependencies]
 runfiles.workspace = true
 walkdir.workspace = true
+
+[build-dependencies]
+prost-build.workspace = true
diff --git a/portage/tools/process_artifacts/build.rs b/portage/tools/process_artifacts/build.rs
new file mode 100644
index 0000000..b0706d5
--- /dev/null
+++ b/portage/tools/process_artifacts/build.rs
@@ -0,0 +1,19 @@
+// Copyright 2024 The ChromiumOS Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{
+    io::Result,
+    path::{Path, PathBuf},
+};
+
+fn main() -> Result<()> {
+    let mut includes: Vec<PathBuf> = vec!["proto/third_party".into()];
+    // Under Bazel, $WELL_KNOWN_TYPES_MARKER contains a path to the LICENSE file in the protobuf
+    // repository. Use this path to locate well know type protos.
+    if let Some(marker_path) = std::env::var_os("WELL_KNOWN_TYPES_MARKER") {
+        includes.push(Path::new(&marker_path).parent().unwrap().join("src"));
+    }
+    prost_build::compile_protos(&["proto/third_party/spawn.proto"], &includes)?;
+    Ok(())
+}
diff --git a/portage/tools/process_artifacts/proto/third_party/spawn.proto b/portage/tools/process_artifacts/proto/third_party/spawn.proto
new file mode 100644
index 0000000..e6a2e38
--- /dev/null
+++ b/portage/tools/process_artifacts/proto/third_party/spawn.proto
@@ -0,0 +1,352 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+syntax = "proto3";
+
+package tools.protos;
+
+import "google/protobuf/duration.proto";
+
+option java_package = "com.google.devtools.build.lib.exec";
+option java_outer_classname = "Protos";
+
+// This file defines the output formats emitted by the --execution_log_* flags.
+// The purpose of the execution log is to enable comparisons of multiple builds
+// to diagnose output differences or more subtle problems such as remote cache
+// misses.
+
+// Digest of a file or action cache entry.
+message Digest {
+  // The content hash as a lowercase hex string including any leading zeroes.
+  string hash = 1;
+
+  // The original content size in bytes.
+  int64 size_bytes = 2;
+
+  // The name of the digest function used to compute the hash.
+  string hash_function_name = 3;
+}
+
+message File {
+  // Path to the file relative to the execution root.
+  string path = 1;
+
+  // Symlink target path.
+  // Only set for unresolved symlinks.
+  string symlink_target_path = 4;
+
+  // File digest.
+  // Always omitted for unresolved symlinks. May be omitted for empty files.
+  Digest digest = 2;
+
+  // Whether the file is a tool.
+  // Only set for inputs, never for outputs.
+  bool is_tool = 3;
+}
+
+// Contents of command environment.
+message EnvironmentVariable {
+  string name = 1;
+  string value = 2;
+}
+
+// Command execution platform. This message needs to be kept in sync
+// with [Platform][google.devtools.remoteexecution.v1test.Platform].
+message Platform {
+  message Property {
+    string name = 1;
+    string value = 2;
+  }
+  repeated Property properties = 1;
+}
+
+// Timing, size, and memory statistics for a SpawnExec.
+message SpawnMetrics {
+  // Total wall time spent running a spawn, measured locally.
+  google.protobuf.Duration total_time = 1;
+  // Time taken to convert the spawn into a network request.
+  google.protobuf.Duration parse_time = 2;
+  // Time spent communicating over the network.
+  google.protobuf.Duration network_time = 3;
+  // Time spent fetching remote outputs.
+  google.protobuf.Duration fetch_time = 4;
+  // Time spent waiting in queues.
+  google.protobuf.Duration queue_time = 5;
+  // Time spent setting up the environment in which the spawn is run.
+  google.protobuf.Duration setup_time = 6;
+  // Time spent uploading outputs to a remote store.
+  google.protobuf.Duration upload_time = 7;
+  // Time spent running the subprocess.
+  google.protobuf.Duration execution_wall_time = 8;
+  // Time spent by the execution framework processing outputs.
+  google.protobuf.Duration process_outputs_time = 9;
+  // Time spent in previous failed attempts, not including queue time.
+  google.protobuf.Duration retry_time = 10;
+  // Total size in bytes of inputs or 0 if unavailable.
+  int64 input_bytes = 11;
+  // Total number of input files or 0 if unavailable.
+  int64 input_files = 12;
+  // Estimated memory usage or 0 if unavailable.
+  int64 memory_estimate_bytes = 13;
+  // Limit of total size of inputs or 0 if unavailable.
+  int64 input_bytes_limit = 14;
+  // Limit of total number of input files or 0 if unavailable.
+  int64 input_files_limit = 15;
+  // Limit of total size of outputs or 0 if unavailable.
+  int64 output_bytes_limit = 16;
+  // Limit of total number of output files or 0 if unavailable.
+  int64 output_files_limit = 17;
+  // Memory limit or 0 if unavailable.
+  int64 memory_bytes_limit = 18;
+  // Time limit or 0 if unavailable.
+  google.protobuf.Duration time_limit = 19;
+}
+
+// Details of an executed spawn.
+// This is the format generated by --execution_log_{binary,json}_file.
+
+// Each message contains an executed command, its full inputs and outputs, and
+// other information. This format is relatively costly to produce and results
+// in very large files, due to the amount of repeated information. The
+// --experimental_execution_log_compact_file format provides a better
+// alternative.
+message SpawnExec {
+  // The command that was run.
+  repeated string command_args = 1;
+
+  // The command environment.
+  repeated EnvironmentVariable environment_variables = 2;
+
+  // The command execution platform.
+  Platform platform = 3;
+
+  // The inputs at the time of the execution.
+  repeated File inputs = 4;
+
+  // All the listed outputs paths. The paths are relative to the execution root.
+  // Actual outputs are a subset of the listed outputs. These paths are sorted.
+  repeated string listed_outputs = 5;
+
+  // Whether the spawn was allowed to run remotely.
+  bool remotable = 6;
+
+  // Whether the spawn was allowed to be cached.
+  bool cacheable = 7;
+
+  // The spawn timeout.
+  int64 timeout_millis = 8;
+
+  // The mnemonic of the action this spawn belongs to.
+  string mnemonic = 10;
+
+  // The outputs generated by the execution.
+  // In order for one of the listed_outputs to appear here, it must have been
+  // produced and have the expected type (file, directory or symlink).
+  repeated File actual_outputs = 11;
+
+  // If the spawn did not hit a disk or remote cache, this will be the name of
+  // the runner, e.g. "remote", "linux-sandbox" or "worker".
+  //
+  // If the spawn hit a disk or remote cache, this will be "disk cache hit" or
+  // "remote cache hit", respectively. This includes the case where a remote
+  // cache was hit while executing the spawn remotely.
+  //
+  // Note that spawns whose owning action hits the persistent action cache
+  // are never reported at all.
+  //
+  // This won't always match the spawn strategy. For the dynamic strategy, it
+  // will be the runner for the first branch to complete. For the remote
+  // strategy, it might be a local runner in case of a fallback.
+  string runner = 12;
+
+  // Whether the spawn hit a disk or remote cache.
+  bool cache_hit = 13;
+
+  // A text status describing an execution error. Empty in case of success.
+  string status = 14;
+
+  // This field contains the contents of SpawnResult.exitCode.
+  // Its semantics varies greatly depending on the status field.
+  // Dependable: if status is empty, exit_code is guaranteed to be zero.
+  int32 exit_code = 15;
+
+  // Whether the spawn was allowed to be cached remotely.
+  bool remote_cacheable = 16;
+
+  // The canonical label of the target this spawn belongs to.
+  string target_label = 18;
+
+  // The action cache digest.
+  // Only available when remote execution, remote cache or disk cache was
+  // enabled for this spawn.
+  Digest digest = 19;
+
+  // Timing, size and memory statistics.
+  SpawnMetrics metrics = 20;
+
+  reserved 9, 17;
+}
+
+// An entry in the compact log format.
+// This is the format generated by --experimental_execution_log_compact_file.
+//
+// Each entry describes either an executed spawn or a piece of data referenced
+// by other entries. This considerably reduces the runtime overhead and the size
+// of the log when compared to the --execution_log_{binary,json}_file formats.
+//
+// To ensure that the log can be parsed in a single pass, every entry must be
+// serialized after all other entries it references by ID. However, entries
+// aren't guaranteed to be serialized in increasing ID order.
+//
+// Entries other than spawns may not be assumed to be canonical. For performance
+// reasons, the same file, directory or input set may be serialized multiple
+// times with a different ID.
+message ExecLogEntry {
+  // Information pertaining to the entire invocation.
+  // May appear at most once in the initial position.
+  message Invocation {
+    // The hash function used to compute digests.
+    string hash_function_name = 1;
+  }
+
+  // An input or output file.
+  message File {
+    // The file path.
+    string path = 1;
+    // A digest of the file contents.
+    // The hash function name is omitted. It can be obtained from Invocation.
+    // May be omitted for empty files.
+    Digest digest = 2;
+  }
+
+  // An input or output directory.
+  // May be a source directory, a runfiles or fileset tree, or a tree artifact.
+  message Directory {
+    // The directory path.
+    string path = 1;
+    // The contained files, whose paths are relative to the directory.
+    repeated File files = 2;
+  }
+
+  // An unresolved symlink.
+  message UnresolvedSymlink {
+    // The symlink path.
+    string path = 1;
+    // The path the symlink points to.
+    string target_path = 2;
+  }
+
+  // A set of spawn inputs.
+  // The contents of the set are the directly referenced files, directories and
+  // symlinks in addition to the contents of all transitively referenced sets.
+  // Sets are not canonical: two sets with different structure may yield the
+  // same contents.
+  message InputSet {
+    // Entry IDs of files belonging to this set.
+    repeated int32 file_ids = 1;
+    // Entry IDs of directories belonging to this set.
+    repeated int32 directory_ids = 2;
+    // Entry IDs of unresolved symlinks belonging to this set.
+    repeated int32 unresolved_symlink_ids = 3;
+    // Entry IDs of other sets contained in this set.
+    repeated int32 transitive_set_ids = 4;
+  }
+
+  // A spawn output.
+  message Output {
+    oneof type {
+      // An output file, i.e., ctx.actions.declare_file.
+      int32 file_id = 1;
+      // An output directory, i.e., ctx.actions.declare_directory.
+      int32 directory_id = 2;
+      // An output unresolved symlink, i.e., ctx.actions.declare_symlink.
+      int32 unresolved_symlink_id = 3;
+      // A declared output that is either missing or has the wrong type
+      // (e.g., a file where a directory was expected).
+      string invalid_output_path = 4;
+    }
+  }
+
+  // An executed spawn.
+  message Spawn {
+    // The command line arguments.
+    repeated string args = 1;
+
+    // The environment variables.
+    repeated EnvironmentVariable env_vars = 2;
+
+    // The execution platform.
+    Platform platform = 3;
+
+    // Entry ID of the set of inputs. Unset means empty.
+    int32 input_set_id = 4;
+
+    // Entry ID of the set of tool inputs. Unset means empty.
+    int32 tool_set_id = 5;
+
+    // The set of outputs.
+    repeated Output outputs = 6;
+
+    // See SpawnExec.label.
+    string target_label = 7;
+
+    // See SpawnExec.mnemonic.
+    string mnemonic = 8;
+
+    // See SpawnExec.exit_code.
+    int32 exit_code = 9;
+
+    // See SpawnExec.status.
+    string status = 10;
+
+    // See SpawnExec.runner.
+    string runner = 11;
+
+    // See SpawnExec.cache_hit.
+    bool cache_hit = 12;
+
+    // See SpawnExec.remotable.
+    bool remotable = 13;
+
+    // See SpawnExec.cacheable.
+    bool cacheable = 14;
+
+    // See SpawnExec.remote_cacheable.
+    bool remote_cacheable = 15;
+
+    // See SpawnExec.digest.
+    // The hash function name is omitted. It can be obtained from Invocation.
+    // Unset if the file is empty.
+    Digest digest = 16;
+
+    // See SpawnExec.timeout_millis.
+    int64 timeout_millis = 17;
+
+    // See SpawnExec.metrics.
+    SpawnMetrics metrics = 18;
+  }
+
+  // The entry ID. Must be nonzero.
+  int32 id = 1;
+
+  // The entry payload.
+  oneof type {
+    Invocation invocation = 2;
+    File file = 3;
+    Directory directory = 4;
+    UnresolvedSymlink unresolved_symlink = 5;
+    InputSet input_set = 6;
+    Spawn spawn = 7;
+  }
+}
diff --git a/portage/tools/process_artifacts/src/proto/mod.rs b/portage/tools/process_artifacts/src/proto/mod.rs
index 9ed7b7c..7b1ced6 100644
--- a/portage/tools/process_artifacts/src/proto/mod.rs
+++ b/portage/tools/process_artifacts/src/proto/mod.rs
@@ -4,3 +4,4 @@
 
 pub mod build_event_stream;
 pub mod ebuild_metadata;
+pub mod spawn;
diff --git a/portage/tools/process_artifacts/src/proto/spawn.rs b/portage/tools/process_artifacts/src/proto/spawn.rs
new file mode 100644
index 0000000..88fac39
--- /dev/null
+++ b/portage/tools/process_artifacts/src/proto/spawn.rs
@@ -0,0 +1,5 @@
+// Copyright 2024 The ChromiumOS Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+include!(concat!(env!("OUT_DIR"), "/tools.protos.rs"));
diff --git a/rust/alchemy_crates/Cargo.lock b/rust/alchemy_crates/Cargo.lock
index 63dac9a..3805cf0 100644
--- a/rust/alchemy_crates/Cargo.lock
+++ b/rust/alchemy_crates/Cargo.lock
@@ -182,6 +182,12 @@
 ]
 
 [[package]]
+name = "bytes"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9"
+
+[[package]]
 name = "bzip2"
 version = "0.4.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -432,7 +438,7 @@
  "anyhow",
  "base64 0.20.0",
  "by_address",
- "bytes",
+ "bytes 0.4.12",
  "bzip2",
  "chrono",
  "clap",
@@ -455,6 +461,9 @@
  "pathdiff",
  "pretty_assertions",
  "proptest",
+ "prost",
+ "prost-build",
+ "prost-types",
  "protobuf",
  "rand",
  "rayon",
@@ -554,6 +563,12 @@
 ]
 
 [[package]]
+name = "fixedbitset"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
+
+[[package]]
 name = "flate2"
 version = "1.0.27"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -668,7 +683,7 @@
 checksum = "2aaf1d741fe6f3413f1f9f71b99f5e4e26776d563475a8a53ce53a73a8534c1d"
 dependencies = [
  "base64 0.9.3",
- "bytes",
+ "bytes 0.4.12",
  "futures",
  "futures-cpupool",
  "httpbis",
@@ -720,7 +735,7 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7689cfa896b2a71da4f16206af167542b75d242b6906313e53857972a92d5614"
 dependencies = [
- "bytes",
+ "bytes 0.4.12",
  "futures",
  "futures-cpupool",
  "log 0.4.17",
@@ -1061,6 +1076,12 @@
 ]
 
 [[package]]
+name = "multimap"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03"
+
+[[package]]
 name = "net2"
 version = "0.2.39"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1275,6 +1296,16 @@
 ]
 
 [[package]]
+name = "petgraph"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db"
+dependencies = [
+ "fixedbitset",
+ "indexmap",
+]
+
+[[package]]
 name = "pin-project-lite"
 version = "0.2.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1309,6 +1340,16 @@
 ]
 
 [[package]]
+name = "prettyplease"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d3928fb5db768cb86f891ff014f0144589297e3c6a1aba6ed7cecfdace270c7"
+dependencies = [
+ "proc-macro2",
+ "syn 2.0.55",
+]
+
+[[package]]
 name = "proc-macro-error"
 version = "1.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1363,12 +1404,65 @@
 ]
 
 [[package]]
+name = "prost"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0f5d036824e4761737860779c906171497f6d55681139d8312388f8fe398922"
+dependencies = [
+ "bytes 1.6.0",
+ "prost-derive",
+]
+
+[[package]]
+name = "prost-build"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80b776a1b2dc779f5ee0641f8ade0125bc1298dd41a9a0c16d8bd57b42d222b1"
+dependencies = [
+ "bytes 1.6.0",
+ "heck",
+ "itertools",
+ "log 0.4.17",
+ "multimap",
+ "once_cell",
+ "petgraph",
+ "prettyplease",
+ "prost",
+ "prost-types",
+ "regex",
+ "syn 2.0.55",
+ "tempfile",
+]
+
+[[package]]
+name = "prost-derive"
+version = "0.12.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9554e3ab233f0a932403704f1a1d08c30d5ccd931adfdfa1e8b5a19b52c1d55a"
+dependencies = [
+ "anyhow",
+ "itertools",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.55",
+]
+
+[[package]]
+name = "prost-types"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3235c33eb02c1f1e212abdbe34c78b264b038fb58ca612664343271e36e55ffe"
+dependencies = [
+ "prost",
+]
+
+[[package]]
 name = "protobuf"
 version = "2.8.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "70731852eec72c56d11226c8a5f96ad5058a3dab73647ca5f7ee351e464f2571"
 dependencies = [
- "bytes",
+ "bytes 0.4.12",
 ]
 
 [[package]]
@@ -1910,7 +2004,7 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5a09c0b5bb588872ab2f09afa13ee6e9dac11e10a0ec9e8e3ba39a5a5d530af6"
 dependencies = [
- "bytes",
+ "bytes 0.4.12",
  "futures",
  "mio",
  "num_cpus",
@@ -1934,7 +2028,7 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "25b2998660ba0e70d18684de5d06b70b70a3a747469af9dea7618cc59e75976b"
 dependencies = [
- "bytes",
+ "bytes 0.4.12",
  "futures",
  "tokio-io",
 ]
@@ -1945,7 +2039,7 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "87b1395334443abca552f63d4f61d0486f12377c2ba8b368e523f89e828cffd4"
 dependencies = [
- "bytes",
+ "bytes 0.4.12",
  "futures",
  "iovec",
  "log 0.4.17",
@@ -1995,7 +2089,7 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "57fc868aae093479e3131e3d165c93b1c7474109d13c90ec0dda2a1bbfff0674"
 dependencies = [
- "bytes",
+ "bytes 0.4.12",
  "futures",
  "log 0.4.17",
 ]
@@ -2035,7 +2129,7 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "98df18ed66e3b72e742f185882a9e201892407957e45fbff8da17ae7a7c51f72"
 dependencies = [
- "bytes",
+ "bytes 0.4.12",
  "futures",
  "iovec",
  "mio",
@@ -2099,7 +2193,7 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e2a0b10e610b39c38b031a2fcab08e4b82f16ece36504988dcbd81dbba650d82"
 dependencies = [
- "bytes",
+ "bytes 0.4.12",
  "futures",
  "log 0.4.17",
  "mio",
@@ -2114,7 +2208,7 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "65ae5d255ce739e8537221ed2942e0445f4b3b813daebac1c0050ddaaa3587f9"
 dependencies = [
- "bytes",
+ "bytes 0.4.12",
  "futures",
  "iovec",
  "libc",
@@ -2131,7 +2225,7 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ab57a4ac4111c8c9dbcf70779f6fc8bc35ae4b2454809febac840ad19bd7e4e0"
 dependencies = [
- "bytes",
+ "bytes 0.4.12",
  "futures",
  "iovec",
  "libc",
diff --git a/rust/alchemy_crates/Cargo.toml b/rust/alchemy_crates/Cargo.toml
index a9eb588..2aadb56 100644
--- a/rust/alchemy_crates/Cargo.toml
+++ b/rust/alchemy_crates/Cargo.toml
@@ -36,6 +36,9 @@
 pathdiff = "0.2.1"
 pretty_assertions = "1.1.0"
 proptest = "1.0.0"
+prost = "0.12.4"
+prost-build = "0.12.4"
+prost-types = "0.12.4"
 protobuf = { version = "2.8.2", features = ["with-bytes"] }
 rand = "0.8.5"
 rayon = "1.6.0"