summaryrefslogtreecommitdiff
path: root/src/buildtool/execution_api/remote
diff options
context:
space:
mode:
authorSascha Roloff <sascha.roloff@huawei.com>2023-11-17 10:22:25 +0100
committerSascha Roloff <sascha.roloff@huawei.com>2023-11-22 16:18:17 +0100
commit3b1095f7e3584f37c984a79ad7a2b94ebaa0700f (patch)
tree517f1113d4a19215b6403d38cc07a26a47c3090c /src/buildtool/execution_api/remote
parent44a7c680289ba6812583746013f350d63942c894 (diff)
downloadjustbuild-3b1095f7e3584f37c984a79ad7a2b94ebaa0700f.tar.gz
Implement blob splitting protocol on just client side
Diffstat (limited to 'src/buildtool/execution_api/remote')
-rw-r--r--src/buildtool/execution_api/remote/TARGETS4
-rw-r--r--src/buildtool/execution_api/remote/bazel/bazel_ac_client.hpp2
-rw-r--r--src/buildtool/execution_api/remote/bazel/bazel_api.cpp237
-rw-r--r--src/buildtool/execution_api/remote/bazel/bazel_api.hpp10
-rw-r--r--src/buildtool/execution_api/remote/bazel/bazel_cas_client.cpp95
-rw-r--r--src/buildtool/execution_api/remote/bazel/bazel_cas_client.hpp11
-rw-r--r--src/buildtool/execution_api/remote/bazel/bazel_execution_client.hpp2
-rw-r--r--src/buildtool/execution_api/remote/bazel/bazel_network.cpp5
-rw-r--r--src/buildtool/execution_api/remote/bazel/bazel_network.hpp8
9 files changed, 288 insertions, 86 deletions
diff --git a/src/buildtool/execution_api/remote/TARGETS b/src/buildtool/execution_api/remote/TARGETS
index 1ea38e40..381a8620 100644
--- a/src/buildtool/execution_api/remote/TARGETS
+++ b/src/buildtool/execution_api/remote/TARGETS
@@ -43,6 +43,8 @@
, ["src/buildtool/compatibility", "compatibility"]
, ["src/buildtool/execution_api/bazel_msg", "bazel_msg_factory"]
, ["src/buildtool/execution_api/utils", "outputscheck"]
+ , ["src/buildtool/compatibility", "compatibility"]
+ , ["src/buildtool/crypto", "hash_function"]
, ["@", "grpc", "", "grpc++"]
]
}
@@ -66,6 +68,8 @@
, ["@", "fmt", "", "fmt"]
, ["src/buildtool/compatibility", "compatibility"]
, ["src/buildtool/multithreading", "task_system"]
+ , ["src/buildtool/file_system", "file_system_manager"]
+ , ["src/buildtool/file_system", "object_type"]
]
}
, "config":
diff --git a/src/buildtool/execution_api/remote/bazel/bazel_ac_client.hpp b/src/buildtool/execution_api/remote/bazel/bazel_ac_client.hpp
index b3a62af6..864d1254 100644
--- a/src/buildtool/execution_api/remote/bazel/bazel_ac_client.hpp
+++ b/src/buildtool/execution_api/remote/bazel/bazel_ac_client.hpp
@@ -28,7 +28,7 @@
#include "src/buildtool/logging/logger.hpp"
/// Implements client side for service defined here:
-/// https://github.com/bazelbuild/bazel/blob/4b6ad34dbba15dacebfb6cbf76fa741649cdb007/third_party/remoteapis/build/bazel/remote/execution/v2/remote_execution.proto#L137
+/// https://github.com/bazelbuild/remote-apis/blob/e1fe21be4c9ae76269a5a63215bb3c72ed9ab3f0/build/bazel/remote/execution/v2/remote_execution.proto#L144
class BazelAcClient {
public:
BazelAcClient(std::string const& server, Port port) noexcept;
diff --git a/src/buildtool/execution_api/remote/bazel/bazel_api.cpp b/src/buildtool/execution_api/remote/bazel/bazel_api.cpp
index 74e8286f..19ff52f6 100644
--- a/src/buildtool/execution_api/remote/bazel/bazel_api.cpp
+++ b/src/buildtool/execution_api/remote/bazel/bazel_api.cpp
@@ -16,12 +16,9 @@
#include <algorithm>
#include <atomic>
-#include <map>
-#include <memory>
-#include <optional>
-#include <string>
+#include <cstdint>
#include <unordered_map>
-#include <vector>
+#include <unordered_set>
#include "fmt/core.h"
#include "src/buildtool/common/bazel_types.hpp"
@@ -37,9 +34,150 @@
#include "src/buildtool/execution_api/remote/bazel/bazel_network.hpp"
#include "src/buildtool/execution_api/remote/bazel/bazel_response.hpp"
#include "src/buildtool/file_system/file_system_manager.hpp"
+#include "src/buildtool/file_system/object_type.hpp"
#include "src/buildtool/logging/logger.hpp"
#include "src/buildtool/multithreading/task_system.hpp"
+namespace {
+
+[[nodiscard]] auto IsAvailable(
+ std::vector<bazel_re::Digest> const& digests,
+ gsl::not_null<IExecutionApi*> const& api) noexcept
+ -> std::vector<bazel_re::Digest> {
+ std::vector<ArtifactDigest> artifact_digests;
+ artifact_digests.reserve(digests.size());
+ for (auto const& digest : digests) {
+ artifact_digests.emplace_back(digest);
+ }
+ auto const& missing_artifact_digests = api->IsAvailable(artifact_digests);
+ std::vector<bazel_re::Digest> missing_digests;
+ missing_digests.reserve(missing_artifact_digests.size());
+ for (auto const& digest : missing_artifact_digests) {
+ missing_digests.emplace_back(static_cast<bazel_re::Digest>(digest));
+ }
+ return missing_digests;
+}
+
+[[nodiscard]] auto RetrieveToCas(
+ std::vector<bazel_re::Digest> const& digests,
+ gsl::not_null<IExecutionApi*> const& api,
+ std::shared_ptr<BazelNetwork> const& network,
+ std::unordered_map<ArtifactDigest, Artifact::ObjectInfo> const&
+ info_map) noexcept -> bool {
+
+ // Fetch blobs from this CAS.
+ auto size = digests.size();
+ auto reader = network->ReadBlobs(digests);
+ auto blobs = reader.Next();
+ std::size_t count{};
+ BlobContainer container{};
+ while (not blobs.empty()) {
+ if (count + blobs.size() > size) {
+ Logger::Log(LogLevel::Error, "received more blobs than requested.");
+ return false;
+ }
+ for (auto const& blob : blobs) {
+ try {
+ auto digest = ArtifactDigest{blob.digest};
+ auto exec = info_map.contains(digest)
+ ? IsExecutableObject(info_map.at(digest).type)
+ : false;
+ container.Emplace(BazelBlob{blob.digest, blob.data, exec});
+ } catch (std::exception const& ex) {
+ Logger::Log(
+ LogLevel::Error, "failed to emplace blob: ", ex.what());
+ return false;
+ }
+ }
+ count += blobs.size();
+ blobs = reader.Next();
+ }
+
+ if (count != size) {
+ Logger::Log(LogLevel::Error, "could not retrieve all requested blobs.");
+ return false;
+ }
+
+ // Upload blobs to other CAS.
+ return api->Upload(container, /*skip_find_missing=*/true);
+}
+
+[[nodiscard]] auto RetrieveToCasSplitted(
+ Artifact::ObjectInfo const& artifact_info,
+ gsl::not_null<IExecutionApi*> const& api,
+ std::shared_ptr<BazelNetwork> const& network,
+ std::unordered_map<ArtifactDigest, Artifact::ObjectInfo> const&
+ info_map) noexcept -> bool {
+
+ // Split blob into chunks at the remote side and retrieve chunk digests.
+ auto chunk_digests = network->SplitBlob(artifact_info.digest);
+ if (not chunk_digests) {
+ // If blob splitting failed, fall back to regular fetching.
+ return ::RetrieveToCas({artifact_info.digest}, api, network, info_map);
+ }
+
+ // Fetch unknown chunks.
+ auto digest_set = std::unordered_set<bazel_re::Digest>{
+ (*chunk_digests).begin(), (*chunk_digests).end()};
+ auto unique_digests =
+ std::vector<bazel_re::Digest>{digest_set.begin(), digest_set.end()};
+ auto missing_digests = ::IsAvailable(unique_digests, api);
+ if (not ::RetrieveToCas(missing_digests, api, network, info_map)) {
+ return false;
+ }
+
+ // Assemble blob from chunks.
+ std::string blob_data{};
+ for (auto const& chunk_digest : *chunk_digests) {
+ auto info = Artifact::ObjectInfo{.digest = ArtifactDigest{chunk_digest},
+ .type = ObjectType::File};
+ auto chunk_data = api->RetrieveToMemory(info);
+ if (not chunk_data) {
+ Logger::Log(LogLevel::Error,
+ "could not load blob chunk in memory: ",
+ chunk_digest.hash());
+ return false;
+ }
+ blob_data += *chunk_data;
+ }
+
+ Logger::Log(
+ LogLevel::Debug,
+ [&artifact_info, &unique_digests, &missing_digests, &blob_data]() {
+ auto missing_digest_set = std::unordered_set<bazel_re::Digest>{
+ missing_digests.begin(), missing_digests.end()};
+ std::uint64_t transmitted_bytes{0};
+ for (auto const& chunk_digest : unique_digests) {
+ if (missing_digest_set.contains(chunk_digest)) {
+ transmitted_bytes += chunk_digest.size_bytes();
+ }
+ }
+ double transmission_factor =
+ not blob_data.empty()
+ ? 100.0 * transmitted_bytes / blob_data.size()
+ : 100.0;
+ return fmt::format(
+ "Blob splitting saved {} bytes ({:.2f}%) of network traffic "
+ "when fetching {}.\n",
+ blob_data.size() - transmitted_bytes,
+ 100.0 - transmission_factor,
+ artifact_info.ToString());
+ });
+
+ // Upload blob to other CAS.
+ BlobContainer container{};
+ try {
+ auto exec = IsExecutableObject(artifact_info.type);
+ container.Emplace(BazelBlob{artifact_info.digest, blob_data, exec});
+ } catch (std::exception const& ex) {
+ Logger::Log(LogLevel::Error, "failed to emplace blob: ", ex.what());
+ return false;
+ }
+ return api->Upload(container, /*skip_find_missing=*/true);
+}
+
+} // namespace
+
BazelApi::BazelApi(std::string const& instance_name,
std::string const& host,
Port port,
@@ -218,46 +356,15 @@ auto BazelApi::CreateAction(
blob_digests.push_back(info.digest);
}
- // Fetch blobs from this CAS.
- auto size = blob_digests.size();
- auto reader = network_->ReadBlobs(std::move(blob_digests));
- auto blobs = reader.Next();
- std::size_t count{};
- BlobContainer container{};
- while (not blobs.empty()) {
- if (count + blobs.size() > size) {
- Logger::Log(LogLevel::Error, "received more blobs than requested.");
- return false;
- }
- for (auto& blob : blobs) {
- try {
- auto exec = IsExecutableObject(
- info_map[ArtifactDigest{blob.digest}].type);
- container.Emplace(BazelBlob{blob.digest, blob.data, exec});
- } catch (std::exception const& ex) {
- Logger::Log(
- LogLevel::Error, "failed to emplace blob: ", ex.what());
- return false;
- }
- }
- count += blobs.size();
- blobs = reader.Next();
- }
-
- if (count != size) {
- Logger::Log(LogLevel::Error, "could not retrieve all requested blobs.");
- return false;
- }
-
- // Upload blobs to other CAS.
- return api->Upload(container, /*skip_find_missing=*/true);
+ return ::RetrieveToCas(blob_digests, api, network_, info_map);
}
/// NOLINTNEXTLINE(misc-no-recursion)
[[nodiscard]] auto BazelApi::ParallelRetrieveToCas(
std::vector<Artifact::ObjectInfo> const& artifacts_info,
gsl::not_null<IExecutionApi*> const& api,
- std::size_t jobs) noexcept -> bool {
+ std::size_t jobs,
+ bool use_blob_splitting) noexcept -> bool {
// Return immediately if target CAS is this CAS
if (this == api) {
@@ -288,7 +395,8 @@ auto BazelApi::CreateAction(
auto const infos = network_->ReadDirectTreeEntries(
info.digest, std::filesystem::path{});
if (not infos or
- not ParallelRetrieveToCas(infos->second, api, jobs)) {
+ not ParallelRetrieveToCas(
+ infos->second, api, jobs, use_blob_splitting)) {
return false;
}
}
@@ -297,47 +405,15 @@ auto BazelApi::CreateAction(
// as size, but this is handled by the remote execution engine, so
// no need to regenerate the digest.
ts.QueueTask(
- [this, digest = info.digest, &api, &failure, &info_map]() {
- auto reader = network_->ReadBlobs({digest});
- auto blobs = reader.Next();
- std::size_t count{};
- BlobContainer container{};
- while (not blobs.empty()) {
- if (count + blobs.size() > 1) {
- Logger::Log(LogLevel::Error,
- "received more blobs than requested.");
- failure = true;
- return;
- }
- for (auto& blob : blobs) {
- try {
- auto exec = IsExecutableObject(
- info_map[ArtifactDigest{blob.digest}].type);
- container.Emplace(
- BazelBlob{blob.digest, blob.data, exec});
- } catch (std::exception const& ex) {
- Logger::Log(LogLevel::Error,
- "failed to emplace blob: {}",
- ex.what());
- failure = true;
- return;
- }
- }
- count += blobs.size();
- blobs = reader.Next();
- }
- if (count != 1) {
- Logger::Log(LogLevel::Error,
- "could not retrieve all requested blobs.");
- failure = true;
- return;
- }
- auto result =
- api->Upload(container, /*skip_find_missing=*/true);
- if (not result) {
- failure = true;
+ [this, &info, &api, &failure, &info_map, use_blob_splitting]() {
+ if (use_blob_splitting
+ ? ::RetrieveToCasSplitted(
+ info, api, network_, info_map)
+ : ::RetrieveToCas(
+ {info.digest}, api, network_, info_map)) {
return;
}
+ failure = true;
});
}
} catch (std::exception const& ex) {
@@ -350,7 +426,8 @@ auto BazelApi::CreateAction(
}
[[nodiscard]] auto BazelApi::RetrieveToMemory(
- Artifact::ObjectInfo const& artifact_info) -> std::optional<std::string> {
+ Artifact::ObjectInfo const& artifact_info) noexcept
+ -> std::optional<std::string> {
auto blobs = network_->ReadBlobs({artifact_info.digest}).Next();
if (blobs.size() == 1) {
return blobs.at(0).data;
diff --git a/src/buildtool/execution_api/remote/bazel/bazel_api.hpp b/src/buildtool/execution_api/remote/bazel/bazel_api.hpp
index 3b1b5193..d0b38d32 100644
--- a/src/buildtool/execution_api/remote/bazel/bazel_api.hpp
+++ b/src/buildtool/execution_api/remote/bazel/bazel_api.hpp
@@ -15,12 +15,15 @@
#ifndef INCLUDED_SRC_BUILDTOOL_EXECUTION_API_REMOTE_BAZEL_BAZEL_API_HPP
#define INCLUDED_SRC_BUILDTOOL_EXECUTION_API_REMOTE_BAZEL_BAZEL_API_HPP
+#include <filesystem>
+#include <map>
#include <memory>
+#include <optional>
#include <string>
-#include <utility>
#include <vector>
#include "gsl/gsl"
+#include "src/buildtool/common/artifact.hpp"
#include "src/buildtool/common/artifact_digest.hpp"
#include "src/buildtool/common/remote/port.hpp"
#include "src/buildtool/execution_api/bazel_msg/bazel_common.hpp"
@@ -68,7 +71,8 @@ class BazelApi final : public IExecutionApi {
[[nodiscard]] auto ParallelRetrieveToCas(
std::vector<Artifact::ObjectInfo> const& artifacts_info,
gsl::not_null<IExecutionApi*> const& api,
- std::size_t jobs) noexcept -> bool final;
+ std::size_t jobs,
+ bool use_blob_splitting) noexcept -> bool final;
[[nodiscard]] auto RetrieveToCas(
std::vector<Artifact::ObjectInfo> const& artifacts_info,
@@ -88,7 +92,7 @@ class BazelApi final : public IExecutionApi {
const noexcept -> std::vector<ArtifactDigest> final;
[[nodiscard]] auto RetrieveToMemory(
- Artifact::ObjectInfo const& artifact_info)
+ Artifact::ObjectInfo const& artifact_info) noexcept
-> std::optional<std::string> final;
private:
diff --git a/src/buildtool/execution_api/remote/bazel/bazel_cas_client.cpp b/src/buildtool/execution_api/remote/bazel/bazel_cas_client.cpp
index 66381fde..1ba7d7f1 100644
--- a/src/buildtool/execution_api/remote/bazel/bazel_cas_client.cpp
+++ b/src/buildtool/execution_api/remote/bazel/bazel_cas_client.cpp
@@ -14,10 +14,16 @@
#include "src/buildtool/execution_api/remote/bazel/bazel_cas_client.hpp"
+#include <mutex>
+#include <shared_mutex>
+#include <unordered_map>
+
#include "grpcpp/grpcpp.h"
#include "gsl/gsl"
#include "src/buildtool/common/bazel_types.hpp"
#include "src/buildtool/common/remote/client_common.hpp"
+#include "src/buildtool/compatibility/native_support.hpp"
+#include "src/buildtool/crypto/hash_function.hpp"
#include "src/buildtool/execution_api/common/execution_common.hpp"
namespace {
@@ -29,6 +35,67 @@ namespace {
"{}/blobs/{}/{}", instance_name, digest.hash(), digest.size_bytes());
}
+// In order to determine whether blob splitting is supported at the remote, a
+// trial request to the remote CAS service is issued. This is just a workaround
+// until the blob split API extension is accepted as part of the official remote
+// execution protocol. Then, the ordinary way to determine server capabilities
+// can be employed by using the capabilities service.
+[[nodiscard]] auto BlobSplitSupport(
+ std::string const& instance_name,
+ std::unique_ptr<bazel_re::ContentAddressableStorage::Stub> const&
+ stub) noexcept -> bool {
+ // Create empty blob.
+ std::string empty_str{};
+ std::string hash = HashFunction::ComputeBlobHash(empty_str).HexString();
+ bazel_re::Digest digest{};
+ digest.set_hash(NativeSupport::Prefix(hash, false));
+ digest.set_size_bytes(empty_str.size());
+
+ // Upload empty blob.
+ grpc::ClientContext update_context{};
+ bazel_re::BatchUpdateBlobsRequest update_request{};
+ bazel_re::BatchUpdateBlobsResponse update_response{};
+ update_request.set_instance_name(instance_name);
+ auto* request = update_request.add_requests();
+ request->mutable_digest()->CopyFrom(digest);
+ request->set_data(empty_str);
+ grpc::Status update_status = stub->BatchUpdateBlobs(
+ &update_context, update_request, &update_response);
+ if (not update_status.ok()) {
+ return false;
+ }
+
+ // Request splitting empty blob.
+ grpc::ClientContext split_context{};
+ bazel_re::SplitBlobRequest split_request{};
+ bazel_re::SplitBlobResponse split_response{};
+ split_request.set_instance_name(instance_name);
+ split_request.mutable_blob_digest()->CopyFrom(digest);
+ grpc::Status split_status =
+ stub->SplitBlob(&split_context, split_request, &split_response);
+ return split_status.ok();
+}
+
+// Cached version of blob-split support request.
+[[nodiscard]] auto BlobSplitSupportCached(
+ std::string const& instance_name,
+ std::unique_ptr<bazel_re::ContentAddressableStorage::Stub> const&
+ stub) noexcept -> bool {
+ static auto mutex = std::shared_mutex{};
+ static auto blob_split_support_map =
+ std::unordered_map<std::string, bool>{};
+ {
+ auto lock = std::shared_lock(mutex);
+ if (blob_split_support_map.contains(instance_name)) {
+ return blob_split_support_map[instance_name];
+ }
+ }
+ auto supported = BlobSplitSupport(instance_name, stub);
+ auto lock = std::unique_lock(mutex);
+ blob_split_support_map[instance_name] = supported;
+ return supported;
+}
+
} // namespace
BazelCasClient::BazelCasClient(std::string const& server, Port port) noexcept
@@ -195,6 +262,26 @@ auto BazelCasClient::ReadSingleBlob(std::string const& instance_name,
return std::nullopt;
}
+auto BazelCasClient::SplitBlob(std::string const& instance_name,
+ bazel_re::Digest const& digest) noexcept
+ -> std::optional<std::vector<bazel_re::Digest>> {
+ if (not BlobSplitSupportCached(instance_name, stub_)) {
+ return std::nullopt;
+ }
+ grpc::ClientContext context{};
+ bazel_re::SplitBlobRequest request{};
+ request.set_instance_name(instance_name);
+ request.mutable_blob_digest()->CopyFrom(digest);
+ bazel_re::SplitBlobResponse response{};
+ grpc::Status status = stub_->SplitBlob(&context, request, &response);
+ std::vector<bazel_re::Digest> result{};
+ if (not status.ok()) {
+ LogStatus(&logger_, LogLevel::Debug, status);
+ return std::nullopt;
+ }
+ return ProcessResponseContents<bazel_re::Digest>(response);
+}
+
template <class T_OutputIter>
auto BazelCasClient::FindMissingBlobs(std::string const& instance_name,
T_OutputIter const& start,
@@ -331,6 +418,14 @@ auto GetResponseContents<bazel_re::Directory, bazel_re::GetTreeResponse>(
return response.directories();
}
+// Specialization of GetResponseContents for 'SplitBlobResponse'
+template <>
+auto GetResponseContents<bazel_re::Digest, bazel_re::SplitBlobResponse>(
+ bazel_re::SplitBlobResponse const& response) noexcept
+ -> pb::RepeatedPtrField<bazel_re::Digest> const& {
+ return response.chunk_digests();
+}
+
} // namespace detail
template <class T_Request, class T_Content, class T_OutputIter>
diff --git a/src/buildtool/execution_api/remote/bazel/bazel_cas_client.hpp b/src/buildtool/execution_api/remote/bazel/bazel_cas_client.hpp
index 6750f054..99da5c3e 100644
--- a/src/buildtool/execution_api/remote/bazel/bazel_cas_client.hpp
+++ b/src/buildtool/execution_api/remote/bazel/bazel_cas_client.hpp
@@ -17,6 +17,7 @@
#include <functional>
#include <memory>
+#include <optional>
#include <string>
#include <utility>
#include <vector>
@@ -31,7 +32,7 @@
#include "src/buildtool/logging/logger.hpp"
/// Implements client side for serivce defined here:
-/// https://github.com/bazelbuild/bazel/blob/4b6ad34dbba15dacebfb6cbf76fa741649cdb007/third_party/remoteapis/build/bazel/remote/execution/v2/remote_execution.proto#L243
+/// https://github.com/bazelbuild/remote-apis/blob/e1fe21be4c9ae76269a5a63215bb3c72ed9ab3f0/build/bazel/remote/execution/v2/remote_execution.proto#L317
class BazelCasClient {
public:
BazelCasClient(std::string const& server, Port port) noexcept;
@@ -128,6 +129,14 @@ class BazelCasClient {
bazel_re::Digest const& digest) noexcept
-> std::optional<BazelBlob>;
+ /// @brief Split single blob into chunks
+ /// @param[in] instance_name Name of the CAS instance
+ /// @param[in] digest Blob digest to be splitted
+ /// @return The chunk digests of the splitted blob
+ [[nodiscard]] auto SplitBlob(std::string const& instance_name,
+ bazel_re::Digest const& digest) noexcept
+ -> std::optional<std::vector<bazel_re::Digest>>;
+
private:
std::unique_ptr<ByteStreamClient> stream_{};
std::unique_ptr<bazel_re::ContentAddressableStorage::Stub> stub_;
diff --git a/src/buildtool/execution_api/remote/bazel/bazel_execution_client.hpp b/src/buildtool/execution_api/remote/bazel/bazel_execution_client.hpp
index 227757a9..31ee1144 100644
--- a/src/buildtool/execution_api/remote/bazel/bazel_execution_client.hpp
+++ b/src/buildtool/execution_api/remote/bazel/bazel_execution_client.hpp
@@ -28,7 +28,7 @@
#include "src/buildtool/logging/logger.hpp"
/// Implements client side for service defined here:
-/// https://github.com/bazelbuild/bazel/blob/4b6ad34dbba15dacebfb6cbf76fa741649cdb007/third_party/remoteapis/build/bazel/remote/execution/v2/remote_execution.proto#L42
+/// https://github.com/bazelbuild/remote-apis/blob/e1fe21be4c9ae76269a5a63215bb3c72ed9ab3f0/build/bazel/remote/execution/v2/remote_execution.proto#L44
class BazelExecutionClient {
public:
struct ExecutionOutput {
diff --git a/src/buildtool/execution_api/remote/bazel/bazel_network.cpp b/src/buildtool/execution_api/remote/bazel/bazel_network.cpp
index 4f8020ef..ef3837d3 100644
--- a/src/buildtool/execution_api/remote/bazel/bazel_network.cpp
+++ b/src/buildtool/execution_api/remote/bazel/bazel_network.cpp
@@ -153,6 +153,11 @@ auto BazelNetwork::IsAvailable(std::vector<bazel_re::Digest> const& digests)
return cas_->FindMissingBlobs(instance_name_, digests);
}
+auto BazelNetwork::SplitBlob(bazel_re::Digest const& digest) const noexcept
+ -> std::optional<std::vector<bazel_re::Digest>> {
+ return cas_->SplitBlob(instance_name_, digest);
+}
+
template <class T_Iter>
auto BazelNetwork::DoUploadBlobs(T_Iter const& first,
T_Iter const& last) noexcept -> bool {
diff --git a/src/buildtool/execution_api/remote/bazel/bazel_network.hpp b/src/buildtool/execution_api/remote/bazel/bazel_network.hpp
index 9bd411da..ff944264 100644
--- a/src/buildtool/execution_api/remote/bazel/bazel_network.hpp
+++ b/src/buildtool/execution_api/remote/bazel/bazel_network.hpp
@@ -15,10 +15,15 @@
#ifndef INCLUDED_SRC_BUILDTOOL_EXECUTION_API_REMOTE_BAZEL_BAZEL_NETWORK_HPP
#define INCLUDED_SRC_BUILDTOOL_EXECUTION_API_REMOTE_BAZEL_BAZEL_NETWORK_HPP
+#include <filesystem>
#include <memory>
#include <optional>
+#include <string>
#include <unordered_map>
+#include <utility>
+#include <vector>
+#include "gsl/gsl"
#include "src/buildtool/common/bazel_types.hpp"
#include "src/buildtool/common/remote/port.hpp"
#include "src/buildtool/execution_api/bazel_msg/bazel_blob.hpp"
@@ -70,6 +75,9 @@ class BazelNetwork {
[[nodiscard]] auto IsAvailable(std::vector<bazel_re::Digest> const& digests)
const noexcept -> std::vector<bazel_re::Digest>;
+ [[nodiscard]] auto SplitBlob(bazel_re::Digest const& digest) const noexcept
+ -> std::optional<std::vector<bazel_re::Digest>>;
+
/// \brief Uploads blobs to CAS
/// \param blobs The blobs to upload
/// \param skip_find_missing Skip finding missing blobs, just upload all