summaryrefslogtreecommitdiff
path: root/src/buildtool/execution_api/common
diff options
context:
space:
mode:
Diffstat (limited to 'src/buildtool/execution_api/common')
-rw-r--r--src/buildtool/execution_api/common/TARGETS20
-rw-r--r--src/buildtool/execution_api/common/common_api.cpp178
-rw-r--r--src/buildtool/execution_api/common/common_api.hpp99
3 files changed, 297 insertions, 0 deletions
diff --git a/src/buildtool/execution_api/common/TARGETS b/src/buildtool/execution_api/common/TARGETS
index d2fcfc6d..210cbb3a 100644
--- a/src/buildtool/execution_api/common/TARGETS
+++ b/src/buildtool/execution_api/common/TARGETS
@@ -48,4 +48,24 @@
, "deps": [["@", "grpc", "", "grpc++"]]
, "stage": ["src", "buildtool", "execution_api", "common"]
}
+, "common_api":
+ { "type": ["@", "rules", "CC", "library"]
+ , "name": ["common_api"]
+ , "hdrs": ["common_api.hpp"]
+ , "srcs": ["common_api.cpp"]
+ , "deps":
+ [ "common"
+ , ["@", "gsl", "", "gsl"]
+ , ["src/buildtool/common", "common"]
+ , ["src/buildtool/execution_api/bazel_msg", "bazel_msg_factory"]
+ , ["src/buildtool/execution_api/bazel_msg", "blob_tree"]
+ , ["src/buildtool/execution_api/bazel_msg", "directory_tree"]
+ ]
+ , "stage": ["src", "buildtool", "execution_api", "common"]
+ , "private-deps":
+ [ ["@", "fmt", "", "fmt"]
+ , ["src/buildtool/logging", "logging"]
+ , ["src/buildtool/logging", "log_level"]
+ ]
+ }
}
diff --git a/src/buildtool/execution_api/common/common_api.cpp b/src/buildtool/execution_api/common/common_api.cpp
new file mode 100644
index 00000000..d1b3ad03
--- /dev/null
+++ b/src/buildtool/execution_api/common/common_api.cpp
@@ -0,0 +1,178 @@
+// Copyright 2024 Huawei Cloud Computing Technology Co., Ltd.
+//
+// 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.
+
+#include "src/buildtool/execution_api/common/common_api.hpp"
+
+#include <cstddef>
+#include <exception>
+
+#include "fmt/core.h"
+#include "src/buildtool/logging/log_level.hpp"
+#include "src/buildtool/logging/logger.hpp"
+
+auto CommonRetrieveToFds(
+ std::vector<Artifact::ObjectInfo> const& artifacts_info,
+ std::vector<int> const& fds,
+ std::function<bool(Artifact::ObjectInfo const&,
+ gsl::not_null<FILE*> const&)> const& dump_to_stream,
+ std::optional<std::function<bool(Artifact::ObjectInfo const&, int)>> const&
+ fallback) noexcept -> bool {
+ if (artifacts_info.size() != fds.size()) {
+ Logger::Log(LogLevel::Error,
+ "different number of digests and file descriptors.");
+ return false;
+ }
+
+ for (std::size_t i{}; i < artifacts_info.size(); ++i) {
+ auto fd = fds[i];
+ auto const& info = artifacts_info[i];
+
+ if (gsl::owner<FILE*> out = fdopen(dup(fd), "wb")) { // NOLINT
+ bool success{false};
+ try {
+ success = dump_to_stream(info, out);
+ } catch (std::exception const& ex) {
+ std::fclose(out); // close file
+ Logger::Log(LogLevel::Error,
+ "dumping {} to stream failed with:\n{}",
+ info.ToString(),
+ ex.what());
+ return false;
+ }
+ std::fclose(out);
+ if (not success) {
+ Logger::Log(
+ LogLevel::Debug,
+ "dumping {} {} from CAS to file descriptor {} failed.",
+ IsTreeObject(info.type) ? "tree" : "blob",
+ info.ToString(),
+ fd);
+ // locally we might be able to fallback to Git in native mode
+ try {
+ if (fallback and not(*fallback)(info, fd)) {
+ return false;
+ }
+ } catch (std::exception const& ex) {
+ Logger::Log(LogLevel::Error,
+ "fallback dumping {} to file descriptor {} "
+ "failed with:\n{}",
+ info.ToString(),
+ fd,
+ ex.what());
+ return false;
+ }
+ }
+ }
+ else {
+ Logger::Log(
+ LogLevel::Error, "dumping to file descriptor {} failed.", fd);
+ return false;
+ }
+ }
+ return true;
+}
+
+/// NOLINTNEXTLINE(misc-no-recursion)
+auto CommonUploadBlobTree(BlobTreePtr const& blob_tree,
+ gsl::not_null<IExecutionApi*> const& api) noexcept
+ -> bool {
+ // Create digest list from blobs for batch availability check.
+ auto missing_blobs_info = GetMissingArtifactsInfo<BlobTreePtr>(
+ api, blob_tree->begin(), blob_tree->end(), [](BlobTreePtr const& node) {
+ return ArtifactDigest{node->Blob().digest};
+ });
+ if (not missing_blobs_info) {
+ Logger::Log(LogLevel::Error,
+ "Failed to retrieve the missing tree blobs for upload");
+ return false;
+ }
+
+ // Process missing blobs.
+ BlobContainer container;
+ for (auto const& digest : missing_blobs_info->digests) {
+ if (auto it = missing_blobs_info->back_map.find(digest);
+ it != missing_blobs_info->back_map.end()) {
+ auto const& node = it->second;
+ // Process trees.
+ if (node->IsTree()) {
+ if (not CommonUploadBlobTree(node, api)) {
+ return false;
+ }
+ }
+ // Store blob.
+ try {
+ container.Emplace(node->Blob());
+ } catch (...) {
+ return false;
+ }
+ }
+ }
+
+ return api->Upload(container, /*skip_find_missing=*/true);
+}
+
+auto CommonUploadTreeCompatible(
+ gsl::not_null<IExecutionApi*> const& api,
+ DirectoryTreePtr const& build_root,
+ BazelMsgFactory::LinkDigestResolveFunc const& resolve_links) noexcept
+ -> std::optional<ArtifactDigest> {
+ BlobContainer blobs{};
+ auto digest = BazelMsgFactory::CreateDirectoryDigestFromTree(
+ build_root, resolve_links, [&blobs](BazelBlob&& blob) {
+ blobs.Emplace(std::move(blob));
+ });
+ if (not digest) {
+ Logger::Log(LogLevel::Debug, "failed to create digest for build root.");
+ return std::nullopt;
+ }
+ Logger::Log(LogLevel::Trace, [&digest]() {
+ std::ostringstream oss{};
+ oss << "upload root directory" << std::endl;
+ oss << fmt::format(" - root digest: {}", digest->hash()) << std::endl;
+ return oss.str();
+ });
+ if (not api->Upload(blobs, /*skip_find_missing=*/false)) {
+ Logger::Log(LogLevel::Debug, "failed to upload blobs for build root.");
+ return std::nullopt;
+ }
+ return ArtifactDigest{*digest};
+}
+
+auto CommonUploadTreeNative(gsl::not_null<IExecutionApi*> const& api,
+ DirectoryTreePtr const& build_root) noexcept
+ -> std::optional<ArtifactDigest> {
+ auto blob_tree = BlobTree::FromDirectoryTree(build_root);
+ if (not blob_tree) {
+ Logger::Log(LogLevel::Debug,
+ "failed to create blob tree for build root.");
+ return std::nullopt;
+ }
+ auto tree_blob = (*blob_tree)->Blob();
+ // Upload blob tree if tree is not available at the remote side (content
+ // first).
+ if (not api->IsAvailable(ArtifactDigest{tree_blob.digest})) {
+ if (not CommonUploadBlobTree(*blob_tree, api)) {
+ Logger::Log(LogLevel::Debug,
+ "failed to upload blob tree for build root.");
+ return std::nullopt;
+ }
+ if (not api->Upload(BlobContainer{{tree_blob}},
+ /*skip_find_missing=*/true)) {
+ Logger::Log(LogLevel::Debug,
+ "failed to upload tree blob for build root.");
+ return std::nullopt;
+ }
+ }
+ return ArtifactDigest{tree_blob.digest};
+}
diff --git a/src/buildtool/execution_api/common/common_api.hpp b/src/buildtool/execution_api/common/common_api.hpp
new file mode 100644
index 00000000..312f97db
--- /dev/null
+++ b/src/buildtool/execution_api/common/common_api.hpp
@@ -0,0 +1,99 @@
+// Copyright 2024 Huawei Cloud Computing Technology Co., Ltd.
+//
+// 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.
+
+#ifndef INCLUDED_SRC_BUILDTOOL_EXECUTION_API_COMMON_COMMON_API_HPP
+#define INCLUDED_SRC_BUILDTOOL_EXECUTION_API_COMMON_COMMON_API_HPP
+
+#include <cstdio>
+#include <exception>
+#include <functional>
+#include <optional>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include "gsl/gsl"
+#include "src/buildtool/common/artifact.hpp"
+#include "src/buildtool/common/artifact_digest.hpp"
+#include "src/buildtool/execution_api/bazel_msg/bazel_msg_factory.hpp"
+#include "src/buildtool/execution_api/bazel_msg/blob_tree.hpp"
+#include "src/buildtool/execution_api/bazel_msg/directory_tree.hpp"
+#include "src/buildtool/execution_api/common/execution_api.hpp"
+
+/// \brief Stores a list of missing artifact digests, as well as a back-mapping
+/// to some given original type.
+template <typename T>
+struct MissingArtifactsInfo {
+ std::vector<ArtifactDigest> digests;
+ std::unordered_map<ArtifactDigest, T> back_map;
+};
+
+/// \brief Common logic for RetrieveToFds.
+/// \param dump_to_stream Dumps the artifact to the respective open stream.
+/// \param fallback Processes the respective file descriptor further in case the
+/// regular dump fails.
+[[nodiscard]] auto CommonRetrieveToFds(
+ std::vector<Artifact::ObjectInfo> const& artifacts_info,
+ std::vector<int> const& fds,
+ std::function<bool(Artifact::ObjectInfo const&,
+ gsl::not_null<FILE*> const&)> const& dump_to_stream,
+ std::optional<std::function<bool(Artifact::ObjectInfo const&, int)>> const&
+ fallback) noexcept -> bool;
+
+/// \brief Get the missing artifacts from a given input list, needed, e.g., to
+/// be uploaded.
+/// \returns A struct storing the missing artifacts and a back-mapping to the
+/// original given type, or nullopt in case of exceptions.
+template <typename T>
+[[nodiscard]] auto GetMissingArtifactsInfo(
+ gsl::not_null<IExecutionApi*> const& api,
+ typename std::vector<T>::const_iterator const& begin,
+ typename std::vector<T>::const_iterator const& end,
+ typename std::function<ArtifactDigest(T const&)> const& converter) noexcept
+ -> std::optional<MissingArtifactsInfo<T>> {
+ std::vector<ArtifactDigest> digests;
+ digests.reserve(end - begin);
+ MissingArtifactsInfo<T> res{};
+ for (auto it = begin; it != end; ++it) {
+ try {
+ auto dgst = converter(*it); // can't enforce it to be noexcept
+ digests.emplace_back(dgst);
+ res.back_map.emplace(std::move(dgst), *it);
+ } catch (...) {
+ return std::nullopt;
+ }
+ }
+ res.digests = api->IsAvailable(digests);
+ return res;
+}
+
+/// \brief Upload missing blobs from a given BlobTree.
+[[nodiscard]] auto CommonUploadBlobTree(
+ BlobTreePtr const& blob_tree,
+ gsl::not_null<IExecutionApi*> const& api) noexcept -> bool;
+
+/// \brief Runs the compatible branch of local/bazel UploadTree API.
+[[nodiscard]] auto CommonUploadTreeCompatible(
+ gsl::not_null<IExecutionApi*> const& api,
+ DirectoryTreePtr const& build_root,
+ BazelMsgFactory::LinkDigestResolveFunc const& resolve_links) noexcept
+ -> std::optional<ArtifactDigest>;
+
+/// \brief Runs the native branch of local/bazel UploadTree API.
+[[nodiscard]] auto CommonUploadTreeNative(
+ gsl::not_null<IExecutionApi*> const& api,
+ DirectoryTreePtr const& build_root) noexcept
+ -> std::optional<ArtifactDigest>;
+
+#endif // INCLUDED_SRC_BUILDTOOL_EXECUTION_API_COMMON_COMMON_API_HPP