summaryrefslogtreecommitdiff
path: root/src/buildtool/graph_traverser/graph_traverser.cpp
diff options
context:
space:
mode:
authorKlaus Aehlig <klaus.aehlig@huawei.com>2025-03-24 15:45:33 +0100
committerKlaus Aehlig <klaus.aehlig@huawei.com>2025-03-24 16:58:54 +0100
commita1e9efb1cea77bacf83fa7b576bc765d0155f2c6 (patch)
tree276ee2834ff7fe4af75ce4a2dfff15aa82491cd8 /src/buildtool/graph_traverser/graph_traverser.cpp
parentd4ae1e4ca11876caf49d7fca2ae1ea880d194363 (diff)
downloadjustbuild-a1e9efb1cea77bacf83fa7b576bc765d0155f2c6.tar.gz
GraphTraverser: move implementation to cpp file
The public interface of our graph traverser is typically called once per program invocation, so there is no benefit in optimizing across the boundaries of that call. Many calls do happen within the internal functions of the graph traverser; but for that, the benefit of being in the same compilation unit also happens if everything is moved to the same cpp file. Therefore, make the graph traverser a standard library and in this way, clean up our code basis.
Diffstat (limited to 'src/buildtool/graph_traverser/graph_traverser.cpp')
-rw-r--r--src/buildtool/graph_traverser/graph_traverser.cpp703
1 files changed, 703 insertions, 0 deletions
diff --git a/src/buildtool/graph_traverser/graph_traverser.cpp b/src/buildtool/graph_traverser/graph_traverser.cpp
new file mode 100644
index 00000000..4add7a17
--- /dev/null
+++ b/src/buildtool/graph_traverser/graph_traverser.cpp
@@ -0,0 +1,703 @@
+// Copyright 2022 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/graph_traverser/graph_traverser.hpp"
+
+#ifndef BOOTSTRAP_BUILD_TOOL
+#ifdef __unix__
+#include <unistd.h>
+#else
+#error "Non-unix is not supported yet"
+#endif
+
+#include <algorithm>
+#include <atomic>
+#include <condition_variable>
+#include <cstdint>
+#include <cstdio>
+#include <fstream>
+#include <iomanip>
+#include <iostream>
+#include <iterator>
+#include <memory>
+#include <sstream>
+#include <thread>
+#include <unordered_set>
+
+#include "fmt/core.h"
+#include "src/buildtool/common/artifact_blob.hpp"
+#include "src/buildtool/common/artifact_digest.hpp"
+#include "src/buildtool/common/statistics.hpp"
+#include "src/buildtool/crypto/hash_function.hpp"
+#include "src/buildtool/execution_api/common/api_bundle.hpp"
+#include "src/buildtool/execution_api/common/common_api.hpp"
+#include "src/buildtool/execution_api/common/execution_api.hpp"
+#include "src/buildtool/execution_api/utils/subobject.hpp"
+#include "src/buildtool/execution_engine/executor/executor.hpp"
+#include "src/buildtool/execution_engine/traverser/traverser.hpp"
+#include "src/buildtool/file_system/file_system_manager.hpp"
+#include "src/buildtool/file_system/jsonfs.hpp"
+#include "src/buildtool/file_system/object_type.hpp"
+#include "src/buildtool/logging/log_level.hpp"
+#include "src/utils/cpp/expected.hpp"
+#include "src/utils/cpp/json.hpp"
+#include "src/utils/cpp/path.hpp"
+
+auto GraphTraverser::BuildAndStage(
+ std::map<std::string, ArtifactDescription> const& artifact_descriptions,
+ std::map<std::string, ArtifactDescription> const& runfile_descriptions,
+ std::vector<ActionDescription::Ptr>&& action_descriptions,
+ std::vector<std::string>&& blobs,
+ std::vector<Tree::Ptr>&& trees,
+ std::vector<ArtifactDescription>&& extra_artifacts) const
+ -> std::optional<BuildResult> {
+ DependencyGraph graph; // must outlive artifact_nodes
+ auto artifacts = BuildArtifacts(&graph,
+ artifact_descriptions,
+ runfile_descriptions,
+ std::move(action_descriptions),
+ std::move(trees),
+ std::move(blobs),
+ extra_artifacts);
+ if (not artifacts) {
+ return std::nullopt;
+ }
+ auto [rel_paths, artifact_nodes, extra_nodes] = *artifacts;
+
+ auto const object_infos = CollectObjectInfos(artifact_nodes, logger_);
+ auto extra_infos = CollectObjectInfos(extra_nodes, logger_);
+ if (not object_infos or not extra_infos) {
+ return std::nullopt;
+ }
+
+ Expects(extra_artifacts.size() == extra_infos->size());
+ std::unordered_map<ArtifactDescription, Artifact::ObjectInfo> infos;
+ infos.reserve(extra_infos->size());
+ std::transform(
+ std::make_move_iterator(extra_artifacts.begin()),
+ std::make_move_iterator(extra_artifacts.end()),
+ std::make_move_iterator(extra_infos->begin()),
+ std::inserter(infos, infos.end()),
+ std::make_pair<ArtifactDescription&&, Artifact::ObjectInfo&&>);
+
+ bool failed_artifacts = std::any_of(
+ object_infos->begin(), object_infos->end(), [](auto const& info) {
+ return info.failed;
+ });
+
+ if (not clargs_.stage) {
+ PrintOutputs("Artifacts built, logical paths are:",
+ rel_paths,
+ artifact_nodes,
+ runfile_descriptions);
+ MaybePrintToStdout(
+ rel_paths,
+ artifact_nodes,
+ artifact_descriptions.size() == 1
+ ? std::optional<std::string>{artifact_descriptions.begin()
+ ->first}
+ : std::nullopt);
+ return BuildResult{.output_paths = std::move(rel_paths),
+ .extra_infos = std::move(infos),
+ .failed_artifacts = failed_artifacts};
+ }
+
+ if (clargs_.stage->remember) {
+ if (not context_.apis->remote->ParallelRetrieveToCas(
+ *object_infos, *context_.apis->local, clargs_.jobs, true)) {
+ Logger::Log(
+ logger_, LogLevel::Warning, "Failed to copy objects to CAS");
+ }
+ }
+
+ auto output_paths = RetrieveOutputs(rel_paths, *object_infos);
+ if (not output_paths) {
+ return std::nullopt;
+ }
+ PrintOutputs("Artifacts can be found in:",
+ *output_paths,
+ artifact_nodes,
+ runfile_descriptions);
+
+ MaybePrintToStdout(
+ rel_paths,
+ artifact_nodes,
+ artifact_descriptions.size() == 1
+ ? std::optional<std::string>{artifact_descriptions.begin()->first}
+ : std::nullopt);
+
+ return BuildResult{.output_paths = *output_paths,
+ .extra_infos = std::move(infos),
+ .failed_artifacts = failed_artifacts};
+}
+
+auto GraphTraverser::BuildAndStage(
+ std::filesystem::path const& graph_description,
+ nlohmann::json const& artifacts) const -> std::optional<BuildResult> {
+ // Read blobs to upload and actions from graph description file
+ auto desc = ReadGraphDescription(graph_description, logger_);
+ if (not desc) {
+ return std::nullopt;
+ }
+ auto [blobs, tree_descs, actions] = *std::move(desc);
+
+ HashFunction::Type const hash_type = context_.apis->local->GetHashType();
+ std::vector<ActionDescription::Ptr> action_descriptions{};
+ action_descriptions.reserve(actions.size());
+ for (auto const& [id, description] : actions.items()) {
+ auto action = ActionDescription::FromJson(hash_type, id, description);
+ if (not action) {
+ return std::nullopt; // Error already logged
+ }
+ action_descriptions.emplace_back(std::move(*action));
+ }
+
+ std::vector<Tree::Ptr> trees{};
+ for (auto const& [id, description] : tree_descs.items()) {
+ auto tree = Tree::FromJson(hash_type, id, description);
+ if (not tree) {
+ return std::nullopt;
+ }
+ trees.emplace_back(std::move(*tree));
+ }
+
+ std::map<std::string, ArtifactDescription> artifact_descriptions{};
+ for (auto const& [rel_path, description] : artifacts.items()) {
+ auto artifact = ArtifactDescription::FromJson(hash_type, description);
+ if (not artifact) {
+ return std::nullopt; // Error already logged
+ }
+ artifact_descriptions.emplace(rel_path, std::move(*artifact));
+ }
+
+ return BuildAndStage(artifact_descriptions,
+ {},
+ std::move(action_descriptions),
+ std::move(blobs),
+ std::move(trees));
+}
+
+auto GraphTraverser::ReadGraphDescription(
+ std::filesystem::path const& graph_description,
+ Logger const* logger)
+ -> std::optional<
+ std::tuple<nlohmann::json, nlohmann::json, nlohmann::json>> {
+ auto const graph_description_opt = Json::ReadFile(graph_description);
+ if (not graph_description_opt.has_value()) {
+ Logger::Log(logger,
+ LogLevel::Error,
+ "parsing graph from {}",
+ graph_description.string());
+ return std::nullopt;
+ }
+ auto blobs_opt = ExtractValueAs<std::vector<std::string>>(
+ *graph_description_opt, "blobs", [logger](std::string const& s) {
+ Logger::Log(logger,
+ LogLevel::Error,
+ "{}\ncan not retrieve value for \"blobs\" from "
+ "graph description.",
+ s);
+ });
+ auto trees_opt = ExtractValueAs<nlohmann::json>(
+ *graph_description_opt, "trees", [logger](std::string const& s) {
+ Logger::Log(logger,
+ LogLevel::Error,
+ "{}\ncan not retrieve value for \"trees\" from "
+ "graph description.",
+ s);
+ });
+ auto actions_opt = ExtractValueAs<nlohmann::json>(
+ *graph_description_opt, "actions", [logger](std::string const& s) {
+ Logger::Log(logger,
+ LogLevel::Error,
+ "{}\ncan not retrieve value for \"actions\" from "
+ "graph description.",
+ s);
+ });
+ if (not blobs_opt or not trees_opt or not actions_opt) {
+ return std::nullopt;
+ }
+ return std::make_tuple(
+ std::move(*blobs_opt), std::move(*trees_opt), std::move(*actions_opt));
+}
+
+auto GraphTraverser::UploadBlobs(
+ std::vector<std::string>&& blobs) const noexcept -> bool {
+ std::unordered_set<ArtifactBlob> container;
+ HashFunction const hash_function{context_.apis->remote->GetHashType()};
+ for (auto& content : blobs) {
+ auto blob = ArtifactBlob::FromMemory(
+ hash_function, ObjectType::File, std::move(content));
+ if (not blob.has_value()) {
+ logger_->Emit(LogLevel::Trace, "Failed to create ArtifactBlob");
+ return false;
+ }
+ Logger::Log(logger_, LogLevel::Trace, [&]() {
+ return fmt::format(
+ "Will upload blob, its digest has id {} and size {}.",
+ blob->GetDigest().hash(),
+ blob->GetDigest().size());
+ });
+ // Store and/or upload blob, taking into account the maximum
+ // transfer size.
+ if (not UpdateContainerAndUpload(
+ &container,
+ *std::move(blob),
+ /*exception_is_fatal=*/true,
+ [&api = context_.apis->remote](
+ std::unordered_set<ArtifactBlob>&& blobs) {
+ return api->Upload(std::move(blobs));
+ },
+ logger_)) {
+ return false;
+ }
+ }
+ // Upload remaining blobs.
+ auto result = context_.apis->remote->Upload(std::move(container));
+ Logger::Log(logger_, LogLevel::Trace, [&]() {
+ std::stringstream msg{};
+ msg << (result ? "Finished" : "Failed") << " upload of\n";
+ for (auto const& blob : blobs) {
+ msg << " - " << nlohmann::json(blob).dump() << "\n";
+ }
+ return msg.str();
+ });
+ return result;
+}
+
+auto GraphTraverser::AddArtifactsToRetrieve(
+ gsl::not_null<DependencyGraph*> const& g,
+ std::map<std::string, ArtifactDescription> const& artifacts,
+ std::map<std::string, ArtifactDescription> const& runfiles)
+ -> std::optional<std::pair<std::vector<std::filesystem::path>,
+ std::vector<ArtifactIdentifier>>> {
+ std::vector<std::filesystem::path> rel_paths;
+ std::vector<ArtifactIdentifier> ids;
+ auto total_size = artifacts.size() + runfiles.size();
+ rel_paths.reserve(total_size);
+ ids.reserve(total_size);
+ auto add_and_get_info =
+ [&g, &rel_paths, &ids](
+ std::map<std::string, ArtifactDescription> const& descriptions)
+ -> bool {
+ for (auto const& [rel_path, artifact] : descriptions) {
+ rel_paths.emplace_back(rel_path);
+ ids.emplace_back(g->AddArtifact(artifact));
+ }
+ return true;
+ };
+ if (add_and_get_info(artifacts) and add_and_get_info(runfiles)) {
+ return std::make_pair(std::move(rel_paths), std::move(ids));
+ }
+ return std::nullopt;
+}
+
+auto GraphTraverser::Traverse(
+ DependencyGraph const& g,
+ std::vector<ArtifactIdentifier> const& artifact_ids) const -> bool {
+ Executor executor{&context_, logger_, clargs_.build.timeout};
+ bool traversing{};
+ std::atomic<bool> done = false;
+ std::atomic<bool> failed = false;
+ std::condition_variable cv{};
+ auto observer =
+ std::thread([this, &done, &cv]() { reporter_(&done, &cv); });
+ {
+ Traverser t{executor, g, clargs_.jobs, &failed};
+ traversing =
+ t.Traverse({std::begin(artifact_ids), std::end(artifact_ids)});
+ }
+ done = true;
+ cv.notify_all();
+ observer.join();
+ return traversing and not failed;
+}
+
+auto GraphTraverser::TraverseRebuild(
+ DependencyGraph const& g,
+ std::vector<ArtifactIdentifier> const& artifact_ids) const -> bool {
+ Rebuilder executor{&context_, clargs_.build.timeout};
+ bool traversing{false};
+ std::atomic<bool> done = false;
+ std::atomic<bool> failed = false;
+ std::condition_variable cv{};
+ auto observer =
+ std::thread([this, &done, &cv]() { reporter_(&done, &cv); });
+ {
+ Traverser t{executor, g, clargs_.jobs, &failed};
+ traversing =
+ t.Traverse({std::begin(artifact_ids), std::end(artifact_ids)});
+ }
+ done = true;
+ cv.notify_all();
+ observer.join();
+
+ if (traversing and not failed and clargs_.rebuild->dump_flaky) {
+ std::ofstream file{*clargs_.rebuild->dump_flaky};
+ file << executor.DumpFlakyActions().dump(2);
+ }
+ return traversing and not failed;
+}
+
+auto GraphTraverser::GetArtifactNodes(
+ DependencyGraph const& g,
+ std::vector<ArtifactIdentifier> const& artifact_ids,
+ Logger const* logger) noexcept
+ -> std::optional<std::vector<DependencyGraph::ArtifactNode const*>> {
+ std::vector<DependencyGraph::ArtifactNode const*> nodes{};
+
+ for (auto const& art_id : artifact_ids) {
+ auto const* node = g.ArtifactNodeWithId(art_id);
+ if (node == nullptr) {
+ Logger::Log(logger,
+ LogLevel::Error,
+ "Artifact {} not found in graph.",
+ art_id);
+ return std::nullopt;
+ }
+ nodes.push_back(node);
+ }
+ return nodes;
+}
+
+void GraphTraverser::LogStatistics() const noexcept {
+ auto& stats = *context_.statistics;
+ if (clargs_.rebuild) {
+ std::stringstream ss{};
+ ss << stats.RebuiltActionComparedCounter()
+ << " actions compared with cache";
+ if (stats.ActionsFlakyCounter() > 0) {
+ ss << ", " << stats.ActionsFlakyCounter() << " flaky actions found";
+ ss << " (" << stats.ActionsFlakyTaintedCounter()
+ << " of which tainted)";
+ }
+ if (stats.RebuiltActionMissingCounter() > 0) {
+ ss << ", no cache entry found for "
+ << stats.RebuiltActionMissingCounter() << " actions";
+ }
+ ss << ".";
+ Logger::Log(logger_, LogLevel::Info, ss.str());
+ }
+ else {
+ Logger::Log(logger_,
+ LogLevel::Info,
+ "Processed {} actions, {} cache hits.",
+ stats.ActionsQueuedCounter(),
+ stats.ActionsCachedCounter());
+ }
+}
+
+auto GraphTraverser::BuildArtifacts(
+ gsl::not_null<DependencyGraph*> const& graph,
+ std::map<std::string, ArtifactDescription> const& artifacts,
+ std::map<std::string, ArtifactDescription> const& runfiles,
+ std::vector<ActionDescription::Ptr>&& actions,
+ std::vector<Tree::Ptr>&& trees,
+ std::vector<std::string>&& blobs,
+ std::vector<ArtifactDescription> const& extra_artifacts) const
+ -> std::optional<
+ std::tuple<std::vector<std::filesystem::path>,
+ std::vector<DependencyGraph::ArtifactNode const*>,
+ std::vector<DependencyGraph::ArtifactNode const*>>> {
+ if (not UploadBlobs(std::move(blobs))) {
+ return std::nullopt;
+ }
+
+ auto artifact_infos = AddArtifactsToRetrieve(graph, artifacts, runfiles);
+ if (not artifact_infos) {
+ return std::nullopt;
+ }
+ auto& [output_paths, artifact_ids] = *artifact_infos;
+
+ // Add extra artifacts to ids to build
+ artifact_ids.reserve(artifact_ids.size() + extra_artifacts.size());
+ for (auto const& artifact : extra_artifacts) {
+ artifact_ids.emplace_back(graph->AddArtifact(artifact));
+ }
+
+ std::vector<ActionDescription> tree_actions{};
+ tree_actions.reserve(trees.size());
+ for (auto const& tree : trees) {
+ tree_actions.emplace_back(tree->Action());
+ }
+
+ if (not graph->Add(actions) or not graph->Add(tree_actions)) {
+ Logger::Log(logger_, LogLevel::Error, [&actions]() {
+ auto json = nlohmann::json::array();
+ for (auto const& desc : actions) {
+ json.push_back(desc->ToJson());
+ }
+ return fmt::format(
+ "could not build the dependency graph from the actions "
+ "described in {}.",
+ json.dump());
+ });
+ return std::nullopt;
+ }
+
+ if (clargs_.rebuild ? not TraverseRebuild(*graph, artifact_ids)
+ : not Traverse(*graph, artifact_ids)) {
+ Logger::Log(logger_, LogLevel::Error, "Build failed.");
+ return std::nullopt;
+ }
+
+ LogStatistics();
+
+ auto artifact_nodes = GetArtifactNodes(*graph, artifact_ids, logger_);
+ if (not artifact_nodes) {
+ return std::nullopt;
+ }
+
+ // split extra artifacts' nodes from artifact nodes
+ auto const it_extra =
+ std::next(artifact_nodes->begin(),
+ static_cast<std::int64_t>(output_paths.size()));
+ auto extra_nodes = std::vector<DependencyGraph::ArtifactNode const*>{
+ std::make_move_iterator(it_extra),
+ std::make_move_iterator(artifact_nodes->end())};
+ artifact_nodes->erase(it_extra, artifact_nodes->end());
+
+ return std::make_tuple(std::move(output_paths),
+ std::move(*artifact_nodes),
+ std::move(extra_nodes));
+}
+
+auto GraphTraverser::PrepareOutputPaths(
+ std::vector<std::filesystem::path> const& rel_paths) const
+ -> std::optional<std::vector<std::filesystem::path>> {
+ std::vector<std::filesystem::path> output_paths{};
+ output_paths.reserve(rel_paths.size());
+ for (auto const& rel_path : rel_paths) {
+ output_paths.emplace_back(clargs_.stage->output_dir / rel_path);
+ }
+ return output_paths;
+}
+
+auto GraphTraverser::CollectObjectInfos(
+ std::vector<DependencyGraph::ArtifactNode const*> const& artifact_nodes,
+ Logger const* logger) -> std::optional<std::vector<Artifact::ObjectInfo>> {
+ std::vector<Artifact::ObjectInfo> object_infos;
+ object_infos.reserve(artifact_nodes.size());
+ for (auto const* art_ptr : artifact_nodes) {
+ auto const& info = art_ptr->Content().Info();
+ if (info) {
+ object_infos.push_back(*info);
+ }
+ else {
+ Logger::Log(logger,
+ LogLevel::Error,
+ "artifact {} could not be retrieved, it can not be "
+ "found in CAS.",
+ art_ptr->Content().Id());
+ return std::nullopt;
+ }
+ }
+ return object_infos;
+}
+
+auto GraphTraverser::RetrieveOutputs(
+ std::vector<std::filesystem::path> const& rel_paths,
+ std::vector<Artifact::ObjectInfo> const& object_infos) const
+ -> std::optional<std::vector<std::filesystem::path>> {
+ // Create output directory
+ if (not FileSystemManager::CreateDirectory(clargs_.stage->output_dir)) {
+ return std::nullopt; // Message logged in the file system manager
+ }
+
+ auto output_paths = PrepareOutputPaths(rel_paths);
+
+ if (not output_paths or
+ not context_.apis->remote->RetrieveToPaths(
+ object_infos, *output_paths, &*context_.apis->local)) {
+ Logger::Log(logger_, LogLevel::Error, "Could not retrieve outputs.");
+ return std::nullopt;
+ }
+
+ return output_paths;
+}
+
+void GraphTraverser::PrintOutputs(
+ std::string message,
+ std::vector<std::filesystem::path> const& paths,
+ std::vector<DependencyGraph::ArtifactNode const*> const& artifact_nodes,
+ std::map<std::string, ArtifactDescription> const& runfiles) const {
+ std::string msg_dbg{"Artifact ids:"};
+ std::string msg_failed{"Failed artifacts:"};
+ bool failed{false};
+ nlohmann::json json{};
+ for (std::size_t pos = 0; pos < paths.size(); ++pos) {
+ auto path = paths[pos].string();
+ auto id = IdentifierToString(artifact_nodes[pos]->Content().Id());
+ if (clargs_.build.show_runfiles or
+ not runfiles.contains(clargs_.stage
+ ? std::filesystem::proximate(
+ path, clargs_.stage->output_dir)
+ .string()
+ : path)) {
+ auto info = artifact_nodes[pos]->Content().Info();
+ if (info) {
+ message += fmt::format("\n {} {}", path, info->ToString());
+ if (info->failed) {
+ msg_failed +=
+ fmt::format("\n {} {}", path, info->ToString());
+ failed = true;
+ }
+ if (clargs_.build.dump_artifacts) {
+ json[path] = info->ToJson();
+ }
+ }
+ else {
+ Logger::Log(logger_,
+ LogLevel::Error,
+ "Missing info for artifact {}.",
+ id);
+ }
+ }
+ msg_dbg += fmt::format("\n {}: {}", path, id);
+ }
+
+ if (not clargs_.build.show_runfiles and not runfiles.empty()) {
+ message += fmt::format("\n({} runfiles omitted.)", runfiles.size());
+ }
+
+ Logger::Log(logger_, LogLevel::Info, "{}", message);
+ Logger::Log(logger_, LogLevel::Debug, "{}", msg_dbg);
+ if (failed) {
+ Logger::Log(logger_, LogLevel::Info, "{}", msg_failed);
+ }
+
+ if (clargs_.build.dump_artifacts) {
+ if (*clargs_.build.dump_artifacts == "-") {
+ std::cout << std::setw(2) << json << std::endl;
+ }
+ else {
+ std::ofstream os(*clargs_.build.dump_artifacts);
+ os << std::setw(2) << json << std::endl;
+ }
+ }
+}
+
+void GraphTraverser::MaybePrintToStdout(
+ std::vector<std::filesystem::path> const& paths,
+ std::vector<DependencyGraph::ArtifactNode const*> const& artifacts,
+ std::optional<std::string> const& unique_artifact) const {
+ if (clargs_.build.print_to_stdout) {
+ auto const& remote = *context_.apis->remote;
+ for (std::size_t i = 0; i < paths.size(); i++) {
+ if (paths[i] == *(clargs_.build.print_to_stdout)) {
+ auto info = artifacts[i]->Content().Info();
+ if (info) {
+ if (not remote.RetrieveToFds({*info},
+ {dup(fileno(stdout))},
+ /*raw_tree=*/false,
+ &*context_.apis->local)) {
+ Logger::Log(logger_,
+ LogLevel::Error,
+ "Failed to retrieve {}",
+ *(clargs_.build.print_to_stdout));
+ }
+ }
+ else {
+ Logger::Log(logger_,
+ LogLevel::Error,
+ "Failed to obtain object information for {}",
+ *(clargs_.build.print_to_stdout));
+ }
+ return;
+ }
+ }
+ // Not directly an artifact, hence check if the path is contained in
+ // some artifact
+ auto target_path =
+ ToNormalPath(std::filesystem::path{*clargs_.build.print_to_stdout})
+ .relative_path();
+ for (std::size_t i = 0; i < paths.size(); i++) {
+ auto const& path = paths[i];
+ auto relpath = target_path.lexically_relative(path);
+ if ((not relpath.empty()) and *relpath.begin() != "..") {
+ Logger::Log(logger_,
+ LogLevel::Info,
+ "'{}' not a direct logical path of the specified "
+ "target; will take subobject '{}' of '{}'",
+ *(clargs_.build.print_to_stdout),
+ relpath.string(),
+ path.string());
+ auto info = artifacts[i]->Content().Info();
+ if (info) {
+ auto new_info =
+ RetrieveSubPathId(*info, *context_.apis, relpath);
+ if (new_info) {
+ if (not remote.RetrieveToFds({*new_info},
+ {dup(fileno(stdout))},
+ /*raw_tree=*/false,
+ &*context_.apis->local)) {
+ Logger::Log(logger_,
+ LogLevel::Error,
+ "Failed to retrieve artifact {} at "
+ "path '{}' of '{}'",
+ new_info->ToString(),
+ relpath.string(),
+ path.string());
+ }
+ }
+ }
+ else {
+ Logger::Log(logger_,
+ LogLevel::Error,
+ "Failed to obtain object information for {}",
+ *(clargs_.build.print_to_stdout));
+ }
+ return;
+ }
+ }
+ Logger::Log(logger_,
+ LogLevel::Warning,
+ "{} not a logical path of the specified target",
+ *(clargs_.build.print_to_stdout));
+ }
+ else if (clargs_.build.print_unique) {
+ if (unique_artifact) {
+ auto const& remote = *context_.apis->remote;
+ std::optional<Artifact::ObjectInfo> info = std::nullopt;
+ for (std::size_t i = 0; i < paths.size(); i++) {
+ if (paths[i] == unique_artifact) {
+ info = artifacts[i]->Content().Info();
+ }
+ }
+ if (info) {
+ if (not remote.RetrieveToFds({*info},
+ {dup(fileno(stdout))},
+ /*raw_tree=*/false,
+ &*context_.apis->local)) {
+ Logger::Log(logger_,
+ LogLevel::Error,
+ "Failed to retrieve {}",
+ *unique_artifact);
+ }
+ }
+ else {
+ Logger::Log(logger_,
+ LogLevel::Error,
+ "Failed to obtain object information for {}",
+ *unique_artifact);
+ }
+ return;
+ }
+ Logger::Log(logger_,
+ LogLevel::Info,
+ "Target does not have precisely one artifact.");
+ }
+}
+
+#endif