// 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/execution_api/bazel_msg/bazel_msg_factory.hpp" #include #include #include #include #include #include #include #include #include #include #include // std::move #include #include "fmt/core.h" #include "google/protobuf/duration.pb.h" #include "google/protobuf/repeated_ptr_field.h" #include "src/buildtool/common/artifact_digest_factory.hpp" #include "src/buildtool/file_system/file_system_manager.hpp" #include "src/buildtool/file_system/git_repo.hpp" #include "src/buildtool/file_system/object_type.hpp" #include "src/utils/cpp/hex_string.hpp" #include "src/utils/cpp/path.hpp" namespace { /// \brief Serialize protobuf message to string. template [[nodiscard]] auto SerializeMessage(T const& message) noexcept -> std::optional { try { std::string content(message.ByteSizeLong(), '\0'); message.SerializeToArray(content.data(), gsl::narrow(content.size())); return content; } catch (...) { return std::nullopt; } } /// \brief Create protobuf message 'Platform'. [[nodiscard]] auto CreatePlatform( std::vector const& props) noexcept -> std::unique_ptr { auto platform = std::make_unique(); std::copy(props.cbegin(), props.cend(), pb::back_inserter(platform->mutable_properties())); return platform; } /// \brief Create protobuf message 'Directory'. [[nodiscard]] auto CreateDirectory( std::vector const& files, std::vector const& dirs, std::vector const& links) noexcept -> bazel_re::Directory { bazel_re::Directory dir{}; auto copy_nodes = [](auto* pb_container, auto const& nodes) { pb_container->Reserve(gsl::narrow(nodes.size())); std::copy(nodes.begin(), nodes.end(), pb::back_inserter(pb_container)); std::sort( pb_container->begin(), pb_container->end(), [](auto const& l, auto const& r) { return l.name() < r.name(); }); }; copy_nodes(dir.mutable_files(), files); copy_nodes(dir.mutable_directories(), dirs); copy_nodes(dir.mutable_symlinks(), links); return dir; } /// \brief Create protobuf message 'FileNode'. [[nodiscard]] auto CreateFileNode(std::string const& file_name, ObjectType type, ArtifactDigest const& digest) noexcept -> bazel_re::FileNode { bazel_re::FileNode node; node.set_name(file_name); node.set_is_executable(IsExecutableObject(type)); (*node.mutable_digest()) = ArtifactDigestFactory::ToBazel(digest); return node; } /// \brief Create protobuf message 'DirectoryNode'. [[nodiscard]] auto CreateDirectoryNode(std::string const& dir_name, ArtifactDigest const& digest) noexcept -> bazel_re::DirectoryNode { bazel_re::DirectoryNode node; node.set_name(dir_name); (*node.mutable_digest()) = ArtifactDigestFactory::ToBazel(digest); return node; } /// \brief Create protobuf message 'SymlinkNode'. [[nodiscard]] auto CreateSymlinkNode(std::string const& link_name, std::string const& target) noexcept -> bazel_re::SymlinkNode { bazel_re::SymlinkNode node; node.set_name(link_name); node.set_target(target); return node; } /// \brief Create protobuf message SymlinkNode from Digest for multiple /// instances at once [[nodiscard]] auto CreateSymlinkNodesFromDigests( std::vector const& symlink_names, std::vector const& symlink_digests, BazelMsgFactory::LinkDigestResolveFunc const& resolve_links) -> std::optional> { if (symlink_names.size() != symlink_digests.size()) { return std::nullopt; } std::vector symlink_targets; symlink_targets.reserve(symlink_digests.size()); resolve_links(symlink_digests, &symlink_targets); // Fail if the number of resolved symlinks does not match the requested // number. if (symlink_targets.size() != symlink_names.size()) { return std::nullopt; } std::vector symlink_nodes; symlink_nodes.reserve(symlink_targets.size()); for (std::size_t i = 0; i < symlink_targets.size(); ++i) { symlink_nodes.emplace_back( CreateSymlinkNode(symlink_names[i], symlink_targets[i])); } return symlink_nodes; } struct DirectoryNodeBundle final { bazel_re::DirectoryNode message; ArtifactBlob blob; }; /// \brief Create bundle for protobuf message DirectoryNode from Directory. [[nodiscard]] auto CreateDirectoryNodeBundle(std::string const& dir_name, bazel_re::Directory const& dir) -> std::optional { auto content = SerializeMessage(dir); if (not content) { return std::nullopt; } // SHA256 is used since bazel types are processed here. HashFunction const hash_function{HashFunction::Type::PlainSHA256}; auto blob = ArtifactBlob::FromMemory( hash_function, ObjectType::File, *std::move(content)); if (not blob.has_value()) { return std::nullopt; } auto const digest = blob->GetDigest(); return DirectoryNodeBundle{.message = CreateDirectoryNode(dir_name, digest), .blob = *std::move(blob)}; } /// \brief Create bundle for protobuf message Command from args strings. [[nodiscard]] auto CreateCommandBundle( BazelMsgFactory::ActionDigestRequest const& request) -> std::optional { bazel_re::Command msg; // DEPRECATED as of v2.2: platform properties are now specified // directly in the action. See documentation note in the // [Action][build.bazel.remote.execution.v2.Action] for migration. // (https://github.com/bazelbuild/remote-apis/blob/e1fe21be4c9ae76269a5a63215bb3c72ed9ab3f0/build/bazel/remote/execution/v2/remote_execution.proto#L646) msg.set_allocated_platform(CreatePlatform(*request.properties).release()); msg.set_working_directory(*request.cwd); std::copy(request.command_line->begin(), request.command_line->end(), pb::back_inserter(msg.mutable_arguments())); // DEPRECATED as of v2.1: use output_paths instead, combining // output_files and output_dirs. std::copy(request.output_files->begin(), request.output_files->end(), pb::back_inserter(msg.mutable_output_files())); std::copy(request.output_dirs->begin(), request.output_dirs->end(), pb::back_inserter(msg.mutable_output_directories())); // NEW in v2.2 std::copy(request.output_paths->begin(), request.output_paths->end(), pb::back_inserter(msg.mutable_output_paths())); std::copy(request.env_vars->begin(), request.env_vars->end(), pb::back_inserter(msg.mutable_environment_variables())); auto content = SerializeMessage(msg); if (not content) { return std::nullopt; } auto blob = ArtifactBlob::FromMemory( request.hash_function, ObjectType::File, *std::move(content)); if (not blob.has_value()) { return std::nullopt; } return *std::move(blob); } /// \brief Create bundle for protobuf message Action from Command. [[nodiscard]] auto CreateActionBundle( ArtifactDigest const& command, BazelMsgFactory::ActionDigestRequest const& request) -> std::optional { using seconds = std::chrono::seconds; using nanoseconds = std::chrono::nanoseconds; auto sec = std::chrono::duration_cast(request.timeout); auto nanos = std::chrono::duration_cast(request.timeout - sec); auto duration = std::make_unique(); duration->set_seconds(sec.count()); duration->set_nanos(static_cast(nanos.count())); bazel_re::Action msg; msg.set_do_not_cache(request.skip_action_cache); msg.set_allocated_timeout(duration.release()); *msg.mutable_command_digest() = ArtifactDigestFactory::ToBazel(command); *msg.mutable_input_root_digest() = ArtifactDigestFactory::ToBazel(*request.exec_dir); // New in version 2.2: clients SHOULD set these platform properties // as well as those in the // [Command][build.bazel.remote.execution.v2.Command]. Servers // SHOULD prefer those set here. // (https://github.com/bazelbuild/remote-apis/blob/e1fe21be4c9ae76269a5a63215bb3c72ed9ab3f0/build/bazel/remote/execution/v2/remote_execution.proto#L516) msg.set_allocated_platform(CreatePlatform(*request.properties).release()); auto content = SerializeMessage(msg); if (not content) { return std::nullopt; } auto blob = ArtifactBlob::FromMemory( request.hash_function, ObjectType::File, *std::move(content)); if (not blob.has_value()) { return std::nullopt; } return *std::move(blob); } /// \brief Convert `DirectoryTree` to `DirectoryNodeBundle`. [[nodiscard]] auto DirectoryTreeToBundle( std::string const& root_name, DirectoryTreePtr const& tree, BazelMsgFactory::LinkDigestResolveFunc const& resolve_links, BazelMsgFactory::BlobProcessFunc const& process_blob, std::filesystem::path const& parent = "") noexcept -> std::optional { std::vector file_nodes{}; std::vector dir_nodes{}; std::vector symlink_names{}; std::vector symlink_digests{}; try { for (auto const& [name, node] : *tree) { if (std::holds_alternative(node)) { auto const& dir = std::get(node); auto dir_bundle = DirectoryTreeToBundle( name, dir, resolve_links, process_blob, parent / name); if (not dir_bundle) { return std::nullopt; } dir_nodes.emplace_back(std::move(dir_bundle->message)); if (not process_blob(std::move(dir_bundle->blob))) { return std::nullopt; } } else { auto const& artifact = std::get(node); auto const& object_info = artifact->Info(); if (not object_info) { return std::nullopt; } if (IsTreeObject(object_info->type)) { dir_nodes.emplace_back( CreateDirectoryNode(name, object_info->digest)); } else if (IsSymlinkObject(object_info->type)) { // for symlinks we need to retrieve the data from the // digest, which we will handle in bulk symlink_names.emplace_back(name); symlink_digests.emplace_back(object_info->digest); } else { file_nodes.emplace_back(CreateFileNode( name, object_info->type, object_info->digest)); } } } auto symlink_nodes = CreateSymlinkNodesFromDigests( symlink_names, symlink_digests, resolve_links); if (not symlink_nodes.has_value()) { return std::nullopt; } return CreateDirectoryNodeBundle( root_name, CreateDirectory(file_nodes, dir_nodes, *std::move(symlink_nodes))); } catch (...) { return std::nullopt; } return std::nullopt; } [[nodiscard]] auto GetContentFromGitEntry( BazelMsgFactory::GitReadFunc const& read_git, ArtifactDigest const& digest, ObjectType entry_type) -> expected { auto read_git_res = read_git(digest, entry_type); if (not read_git_res) { return unexpected{ fmt::format("failed reading Git entry {}", digest.hash())}; } if (std::holds_alternative(read_git_res.value())) { return std::get(std::move(read_git_res).value()); } if (std::holds_alternative(read_git_res.value())) { auto content = FileSystemManager::ReadFile( std::get(std::move(read_git_res).value())); if (not content) { return unexpected{fmt::format("failed reading content of tree {}", digest.hash())}; } return *std::move(content); } return unexpected{ fmt::format("unexpected failure reading Git entry {}", digest.hash())}; } } // namespace auto BazelMsgFactory::CreateDirectoryDigestFromTree( DirectoryTreePtr const& tree, LinkDigestResolveFunc const& resolve_links, BlobProcessFunc const& process_blob) noexcept -> std::optional { auto bundle = DirectoryTreeToBundle("", tree, resolve_links, process_blob); if (not bundle) { return std::nullopt; } auto digest = bundle->blob.GetDigest(); try { if (not process_blob(std::move(bundle->blob))) { return std::nullopt; } } catch (...) { return std::nullopt; } return digest; } auto BazelMsgFactory::CreateDirectoryDigestFromGitTree( ArtifactDigest const& digest, GitReadFunc const& read_git, BlobStoreFunc const& store_file, TreeStoreFunc const& store_dir, SymlinkStoreFunc const& store_symlink, RehashedDigestReadFunc const& read_rehashed, RehashedDigestStoreFunc const& store_rehashed) noexcept -> expected { std::vector files{}; std::vector dirs{}; std::vector symlinks{}; try { // read tree object auto const tree_content = GetContentFromGitEntry(read_git, digest, ObjectType::Tree); if (not tree_content) { return unexpected{tree_content.error()}; } // Git-SHA1 hashing is used for reading from git HashFunction const hash_function{HashFunction::Type::GitSHA1}; // the tree digest is in native mode, so no need for rehashing content auto skip_symlinks = [](auto const& /*unused*/) { return true; }; auto const entries = GitRepo::ReadTreeData( *tree_content, digest.hash(), skip_symlinks, /*is_hex_id=*/true); if (not entries) { return unexpected{fmt::format("failed reading entries of tree {}", digest.hash())}; } // handle tree entries for (auto const& [raw_id, es] : *entries) { auto const hex_id = ToHexString(raw_id); for (auto const& entry : es) { // get native digest of entry auto const git_digest = ArtifactDigestFactory::Create(HashFunction::Type::GitSHA1, hex_id, /*size is unknown*/ 0, IsTreeObject(entry.type)); if (not git_digest) { return unexpected{git_digest.error()}; } // get any cached digest mapping, to avoid unnecessary work auto const cached_obj = read_rehashed(*git_digest); if (not cached_obj) { return unexpected{cached_obj.error()}; } // create and store the directory entry switch (entry.type) { case ObjectType::Tree: { if (cached_obj.value()) { // no work to be done if we already know the digest dirs.emplace_back(CreateDirectoryNode( entry.name, cached_obj.value()->digest)); } else { // create and store sub directory auto const dir_digest = CreateDirectoryDigestFromGitTree( *git_digest, read_git, store_file, store_dir, store_symlink, read_rehashed, store_rehashed); if (not dir_digest) { return unexpected{dir_digest.error()}; } dirs.emplace_back( CreateDirectoryNode(entry.name, *dir_digest)); // no need to cache the digest mapping, as this was // done in the recursive call } } break; case ObjectType::Symlink: { // create and store symlink; for this entry type the // cached digest is ignored because we always need the // target (i.e., the symlink content) auto const sym_target = GetContentFromGitEntry( read_git, *git_digest, ObjectType::Symlink); if (not sym_target) { return unexpected{sym_target.error()}; } auto const sym_digest = store_symlink(*sym_target); if (not sym_digest) { return unexpected{fmt::format( "failed storing symlink {}", hex_id)}; } symlinks.emplace_back( CreateSymlinkNode(entry.name, *sym_target)); // while useless for future symlinks, cache digest // mapping for file-type blobs with same content if (auto error_msg = store_rehashed(*git_digest, *sym_digest, ObjectType::Symlink)) { return unexpected{*std::move(error_msg)}; } } break; default: { if (cached_obj.value()) { // no work to be done if we already know the digest files.emplace_back( CreateFileNode(entry.name, entry.type, cached_obj.value()->digest)); } else { // create and store file; here we want to NOT read // the content if from CAS, where we can rehash via // streams! auto const read_git_file = read_git(*git_digest, entry.type); if (not read_git_file) { return unexpected{ fmt::format("failed reading Git entry ")}; } auto const file_digest = store_file( *read_git_file, IsExecutableObject(entry.type)); if (not file_digest) { return unexpected{fmt::format( "failed storing file {}", hex_id)}; } files.emplace_back(CreateFileNode( entry.name, entry.type, *file_digest)); // cache digest mapping if (auto error_msg = store_rehashed( *git_digest, *file_digest, entry.type)) { return unexpected{*std::move(error_msg)}; } } } } } } // create and store tree auto const bytes = SerializeMessage(CreateDirectory(files, dirs, symlinks)); if (not bytes) { return unexpected{ fmt::format("failed serializing bazel Directory for tree {}", digest.hash())}; } auto const tree_digest = store_dir(*bytes); if (not tree_digest) { return unexpected{fmt::format( "failed storing bazel Directory for tree {}", digest.hash())}; } // cache digest mapping if (auto error_msg = store_rehashed(digest, *tree_digest, ObjectType::Tree)) { return unexpected{*std::move(error_msg)}; } // return digest return *tree_digest; } catch (std::exception const& ex) { return unexpected{fmt::format( "creating bazel Directory digest unexpectedly failed with:\n{}", ex.what())}; } } // NOLINTNEXTLINE(misc-no-recursion) auto BazelMsgFactory::CreateGitTreeDigestFromDirectory( ArtifactDigest const& digest, PathReadFunc const& read_path, FileStoreFunc const& store_file, TreeStoreFunc const& store_tree, SymlinkStoreFunc const& store_symlink, RehashedDigestReadFunc const& read_rehashed, RehashedDigestStoreFunc const& store_rehashed) noexcept -> expected { GitRepo::tree_entries_t entries{}; try { // read directory object auto const tree_path = read_path(digest, ObjectType::Tree); if (not tree_path) { return unexpected{ fmt::format("failed reading CAS entry {}", digest.hash())}; } auto const tree_content = FileSystemManager::ReadFile(*tree_path); if (not tree_content) { return unexpected{fmt::format("failed reading content of tree {}", digest.hash())}; } auto dir = BazelMsgFactory::MessageFromString( *tree_content); // process subdirectories for (auto const& subdir : dir->directories()) { // get digest auto const subdir_digest = ArtifactDigestFactory::FromBazel( HashFunction::Type::PlainSHA256, subdir.digest()); if (not subdir_digest) { return unexpected{subdir_digest.error()}; } // get any cached digest mapping, to avoid unnecessary work auto const cached_obj = read_rehashed(*subdir_digest); if (not cached_obj) { return unexpected{cached_obj.error()}; } if (cached_obj.value()) { // no work to be done, just add to map if (auto raw_id = FromHexString(cached_obj.value()->digest.hash())) { entries[std::move(*raw_id)].emplace_back(subdir.name(), ObjectType::Tree); } else { return unexpected{fmt::format( "failed to get raw id for cached dir digest {}", cached_obj.value()->digest.hash())}; } } else { // recursively get the subdirectory digest auto const tree_digest = CreateGitTreeDigestFromDirectory(*subdir_digest, read_path, store_file, store_tree, store_symlink, read_rehashed, store_rehashed); if (not tree_digest) { return unexpected{tree_digest.error()}; } if (auto raw_id = FromHexString(tree_digest->hash())) { entries[std::move(*raw_id)].emplace_back(subdir.name(), ObjectType::Tree); } else { return unexpected{ fmt::format("failed to get raw id for tree digest {}", tree_digest->hash())}; } // no need to cache the digest mapping, as this was done in the // recursive call } } // process symlinks for (auto const& sym : dir->symlinks()) { // get digest auto const sym_digest = ArtifactDigestFactory::HashDataAs( HashFunction{HashFunction::Type::PlainSHA256}, sym.target()); // get any cached digest mapping, to avoid unnecessary work auto const cached_obj = read_rehashed(sym_digest); if (not cached_obj) { return unexpected{cached_obj.error()}; } if (cached_obj.value()) { // no work to be done, just add to map if (auto raw_id = FromHexString(cached_obj.value()->digest.hash())) { entries[std::move(*raw_id)].emplace_back( sym.name(), ObjectType::Symlink); } else { return unexpected{fmt::format( "failed to get raw id for cached symlink digest {}", cached_obj.value()->digest.hash())}; } } else { // rehash symlink auto const blob_digest = store_symlink(sym.target()); if (not blob_digest) { return unexpected{ fmt::format("failed rehashing as blob symlink {}", sym_digest.hash())}; } if (auto raw_id = FromHexString(blob_digest->hash())) { entries[std::move(*raw_id)].emplace_back( sym.name(), ObjectType::Symlink); } else { return unexpected{fmt::format( "failed to get raw id for symlink blob digest {}", blob_digest->hash())}; } // while useless for future symlinks, cache digest mapping for // file-type blobs with same content if (auto error_msg = store_rehashed( sym_digest, *blob_digest, ObjectType::Symlink)) { return unexpected{*std::move(error_msg)}; } } } // process files for (auto const& file : dir->files()) { // get digest auto const file_digest = ArtifactDigestFactory::FromBazel( HashFunction::Type::PlainSHA256, file.digest()); if (not file_digest) { return unexpected{file_digest.error()}; } auto const file_type = file.is_executable() ? ObjectType::Executable : ObjectType::File; // get any cached digest mapping, to avoid unnecessary work auto const cached_obj = read_rehashed(*file_digest); if (not cached_obj) { return unexpected{cached_obj.error()}; } if (cached_obj.value()) { // no work to be done, just add to map if (auto raw_id = FromHexString(cached_obj.value()->digest.hash())) { entries[std::move(*raw_id)].emplace_back(file.name(), file_type); } else { return unexpected{fmt::format( "failed to get raw id for cached file digest {}", cached_obj.value()->digest.hash())}; } } else { // rehash file auto const file_path = read_path(*file_digest, file_type); if (not file_path) { return unexpected{fmt::format("failed reading CAS entry {}", file_digest->hash())}; } auto const blob_digest = store_file(*file_path, file.is_executable()); if (not blob_digest) { return unexpected{ fmt::format("failed rehashing as blob file {}", file_digest->hash())}; } if (auto raw_id = FromHexString(blob_digest->hash())) { entries[std::move(*raw_id)].emplace_back(file.name(), file_type); } else { return unexpected{fmt::format( "failed to get raw id for file blob digest {}", blob_digest->hash())}; } // cache digest mapping if (auto error_msg = store_rehashed(*file_digest, *blob_digest, file_type)) { return unexpected{*std::move(error_msg)}; } } } // create and store Git tree auto const git_tree = GitRepo::CreateShallowTree(entries); if (not git_tree) { return unexpected{ fmt::format("failed creating Git tree for bazel Directory {}", digest.hash())}; } auto const tree_digest = store_tree(git_tree->second); // cache digest mapping if (auto error_msg = store_rehashed(digest, *tree_digest, ObjectType::Tree)) { return unexpected{*std::move(error_msg)}; } // return digest return *tree_digest; } catch (std::exception const& ex) { return unexpected{fmt::format( "creating Git tree digest unexpectedly failed with:\n{}", ex.what())}; } } auto BazelMsgFactory::CreateDirectoryDigestFromLocalTree( std::filesystem::path const& root, FileStoreFunc const& store_file, TreeStoreFunc const& store_dir, SymlinkStoreFunc const& store_symlink) noexcept -> std::optional { std::vector files{}; std::vector dirs{}; std::vector symlinks{}; auto dir_reader = [&files, &dirs, &symlinks, &root, &store_file, &store_dir, &store_symlink](std::filesystem::path const& name, ObjectType type) { const auto& full_name = root / name; if (IsTreeObject(type)) { // create and store sub directory auto digest = CreateDirectoryDigestFromLocalTree( root / name, store_file, store_dir, store_symlink); if (not digest) { Logger::Log(LogLevel::Error, "failed storing tree {}", full_name.string()); return false; } dirs.emplace_back(CreateDirectoryNode(name.string(), *digest)); return true; } try { if (IsSymlinkObject(type)) { // create and store symlink auto content = FileSystemManager::ReadSymlink(full_name); if (content and store_symlink(*content)) { symlinks.emplace_back( CreateSymlinkNode(name.string(), *content)); return true; } Logger::Log(LogLevel::Error, "failed storing symlink {}", full_name.string()); return false; } // create and store file if (auto digest = store_file(full_name, IsExecutableObject(type))) { auto file = CreateFileNode(name.string(), type, *digest); files.emplace_back(std::move(file)); return true; } Logger::Log( LogLevel::Error, "failed storing file {}", full_name.string()); } catch (std::exception const& ex) { Logger::Log( LogLevel::Error, "storing file failed with:\n{}", ex.what()); } return false; }; if (FileSystemManager::ReadDirectory( root, dir_reader, /*allow_upwards=*/true)) { auto dir = CreateDirectory(files, dirs, symlinks); if (auto bytes = SerializeMessage(dir)) { try { return store_dir(*bytes); } catch (std::exception const& ex) { Logger::Log(LogLevel::Error, "storing directory failed with:\n{}", ex.what()); } return std::nullopt; } } return std::nullopt; } auto BazelMsgFactory::CreateGitTreeDigestFromLocalTree( std::filesystem::path const& root, FileStoreFunc const& store_file, TreeStoreFunc const& store_tree, SymlinkStoreFunc const& store_symlink) noexcept -> std::optional { GitRepo::tree_entries_t entries{}; auto dir_reader = [&entries, &root, &store_file, &store_tree, &store_symlink](std::filesystem::path const& name, ObjectType type) { const auto full_name = root / name; if (IsTreeObject(type)) { // create and store sub directory if (auto digest = CreateGitTreeDigestFromLocalTree( full_name, store_file, store_tree, store_symlink)) { if (auto raw_id = FromHexString(digest->hash())) { entries[std::move(*raw_id)].emplace_back(name.string(), ObjectType::Tree); return true; } } Logger::Log( LogLevel::Error, "failed storing tree {}", full_name.string()); return false; } try { if (IsSymlinkObject(type)) { auto content = FileSystemManager::ReadSymlink(full_name); if (content and PathIsNonUpwards(*content)) { if (auto digest = store_symlink(*content)) { if (auto raw_id = FromHexString(digest->hash())) { entries[std::move(*raw_id)].emplace_back( name.string(), type); return true; } } Logger::Log(LogLevel::Error, "failed storing symlink {}", full_name.string()); } else { Logger::Log(LogLevel::Error, "failed storing symlink {} -- not non-upwards", full_name.string()); } return false; } // create and store file if (auto digest = store_file(full_name, IsExecutableObject(type))) { if (auto raw_id = FromHexString(digest->hash())) { entries[std::move(*raw_id)].emplace_back(name.string(), type); return true; } } Logger::Log( LogLevel::Error, "failed storing file {}", full_name.string()); } catch (std::exception const& ex) { Logger::Log( LogLevel::Error, "storing file failed with:\n{}", ex.what()); } return false; }; if (FileSystemManager::ReadDirectory( root, dir_reader, /*allow_upwards=*/true)) { if (auto tree = GitRepo::CreateShallowTree(entries)) { try { return store_tree(tree->second); } catch (std::exception const& ex) { Logger::Log(LogLevel::Error, "storing tree failed with:\n{}", ex.what()); } return std::nullopt; } } return std::nullopt; } auto BazelMsgFactory::CreateActionDigestFromCommandLine( ActionDigestRequest const& request) -> std::optional { auto cmd = CreateCommandBundle(request); if (not cmd) { return std::nullopt; } auto action = CreateActionBundle(cmd->GetDigest(), request); if (not action) { return std::nullopt; } auto result = action->GetDigest(); if (request.store_blob) { std::invoke(*request.store_blob, *std::move(cmd)); std::invoke(*request.store_blob, *std::move(action)); } return result; }