diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/other_tools/root_maps/TARGETS | 23 | ||||
-rw-r--r-- | src/other_tools/root_maps/foreign_file_git_map.cpp | 242 | ||||
-rw-r--r-- | src/other_tools/root_maps/foreign_file_git_map.hpp | 39 |
3 files changed, 304 insertions, 0 deletions
diff --git a/src/other_tools/root_maps/TARGETS b/src/other_tools/root_maps/TARGETS index 5081b512..9452ca23 100644 --- a/src/other_tools/root_maps/TARGETS +++ b/src/other_tools/root_maps/TARGETS @@ -131,6 +131,29 @@ , ["src/utils/archive", "archive_ops"] ] } +, "foreign_file_git_map": + { "type": ["@", "rules", "CC", "library"] + , "name": ["foreign_file_git_map"] + , "hdrs": ["foreign_file_git_map.hpp"] + , "srcs": ["foreign_file_git_map.cpp"] + , "deps": + [ ["@", "gsl", "", "gsl"] + , ["@", "json", "", "json"] + , ["src/other_tools/ops_maps", "content_cas_map"] + , ["src/other_tools/ops_maps", "import_to_git_map"] + ] + , "private-deps": + [ ["@", "fmt", "", "fmt"] + , ["src/buildtool/file_system", "file_root"] + , ["src/buildtool/logging", "log_level"] + , ["src/buildtool/serve_api/remote", "serve_api"] + , ["src/buildtool/storage", "fs_utils"] + , ["src/buildtool/storage", "storage"] + , ["src/utils/cpp", "tmp_dir"] + , "root_utils" + ] + , "stage": ["src", "other_tools", "root_maps"] + } , "tree_id_git_map": { "type": ["@", "rules", "CC", "library"] , "name": ["tree_id_git_map"] diff --git a/src/other_tools/root_maps/foreign_file_git_map.cpp b/src/other_tools/root_maps/foreign_file_git_map.cpp new file mode 100644 index 00000000..9bc4127c --- /dev/null +++ b/src/other_tools/root_maps/foreign_file_git_map.cpp @@ -0,0 +1,242 @@ +// 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/other_tools/root_maps/foreign_file_git_map.hpp" + +#include "fmt/core.h" +#include "src/buildtool/file_system/file_root.hpp" +#include "src/buildtool/file_system/file_system_manager.hpp" +#include "src/buildtool/logging/log_level.hpp" +#include "src/buildtool/serve_api/remote/serve_api.hpp" +#include "src/buildtool/storage/fs_utils.hpp" +#include "src/buildtool/storage/storage.hpp" +#include "src/other_tools/root_maps/root_utils.hpp" +#include "src/utils/cpp/tmp_dir.hpp" + +namespace { + +void WithRootImportedToGit(ForeignFileInfo const& key, + std::pair<std::string, GitCASPtr> const& result, + ForeignFileGitMap::SetterPtr const& setter, + ForeignFileGitMap::LoggerPtr const& logger) { + if (not result.second) { + (*logger)("Importing to git failed", /*fatal=*/true); + return; + } + auto tree_id_file = StorageUtils::GetForeignFileTreeIDFile( + key.archive.content, key.name, key.executable); + auto cache_written = + StorageUtils::WriteTreeIDFile(tree_id_file, result.first); + if (not cache_written) { + (*logger)( + fmt::format("Failed to write cache file {}", tree_id_file.string()), + /*fatal=*/false); + } + (*setter)( + std::pair(nlohmann::json::array({FileRoot::kGitTreeMarker, + result.first, + StorageConfig::GitRoot().string()}), + /*is_cache_hit=*/false)); +} + +void WithFetchedFile(ForeignFileInfo const& key, + gsl::not_null<ImportToGitMap*> const& import_to_git_map, + gsl::not_null<TaskSystem*> const& ts, + ForeignFileGitMap::SetterPtr const& setter, + ForeignFileGitMap::LoggerPtr const& logger) { + auto tmp_dir = StorageUtils::CreateTypedTmpDir("foreign-file"); + auto const& cas = Storage::Instance().CAS(); + auto digest = ArtifactDigest(key.archive.content, 0, key.executable); + auto content_cas_path = cas.BlobPath(digest, key.executable); + if (not content_cas_path) { + (*logger)( + fmt::format("Failed to locally find {} after fetching for repo {}", + key.archive.content, + nlohmann::json(key.archive.origin).dump()), + true); + return; + } + auto did_create_hardlink = FileSystemManager::CreateFileHardlink( + *content_cas_path, tmp_dir->GetPath() / key.name, LogLevel::Warning); + if (not did_create_hardlink) { + (*logger)(fmt::format( + "Failed to hard link {} as {} in temporary directory {}", + content_cas_path->string(), + nlohmann::json(key.name).dump(), + tmp_dir->GetPath().string()), + true); + return; + } + CommitInfo c_info{ + tmp_dir->GetPath(), + fmt::format("foreign file at {}", nlohmann::json(key.name).dump()), + key.archive.content}; + import_to_git_map->ConsumeAfterKeysReady( + ts, + {std::move(c_info)}, + [tmp_dir, // keep tmp_dir alive + key, + setter, + logger](auto const& values) { + WithRootImportedToGit(key, *values[0], setter, logger); + }, + [logger, target_path = tmp_dir->GetPath()](auto const& msg, + bool fatal) { + (*logger)(fmt::format("While importing target {} to Git:\n{}", + target_path.string(), + msg), + fatal); + }); +} + +void UseCacheHit(const std::string& tree_id, + ForeignFileGitMap::SetterPtr const& setter) { + // We keep the invariant, that, whenever a cache entry is written, + // the root is in our git root; in particular, the latter is present, + // initialized, etc; so we can directly write the result. + (*setter)( + std::pair(nlohmann::json::array({FileRoot::kGitTreeMarker, + tree_id, + StorageConfig::GitRoot().string()}), + /*is_cache_hit=*/true)); +} + +void HandleAbsentForeignFile(ForeignFileInfo const& key, + bool serve_api_exists, + ForeignFileGitMap::SetterPtr const& setter, + ForeignFileGitMap::LoggerPtr const& logger) { + // Compute tree in memory + GitRepo::tree_entries_t entries{}; + auto raw_id = FromHexString(key.archive.content); + if (not raw_id) { + (*logger)(fmt::format("Failure converting {} to raw id.", + key.archive.content), + true); + return; + } + entries[*raw_id].emplace_back( + key.name, key.executable ? ObjectType::Executable : ObjectType::File); + auto tree = GitRepo::CreateShallowTree(entries); + if (not tree) { + (*logger)(fmt::format("Failure to construct in-memory tree with entry " + "{} at place {}", + key.archive.content, + nlohmann::json(key.name).dump()), + true); + return; + } + auto tree_id = ToHexString(tree->first); + if (serve_api_exists) { + auto has_tree = CheckServeHasAbsentRoot(tree_id, logger); + if (not has_tree) { + return; + } + if (*has_tree) { + (*setter)(std::pair( + nlohmann::json::array({FileRoot::kGitTreeMarker, tree_id}), + /*is_cache_hit=*/false)); + return; + } + auto serve_result = ServeApi::RetrieveTreeFromForeignFile( + key.archive.content, key.name, key.executable); + if (std::holds_alternative<std::string>(serve_result)) { + // if serve has set up the tree, it must match what we + // expect + auto const& served_tree_id = std::get<std::string>(serve_result); + if (tree_id != served_tree_id) { + (*logger)(fmt::format("Mismatch in served root tree " + "id: expected {}, but got {}", + tree_id, + served_tree_id), + /*fatal=*/true); + return; + } + // set workspace root as absent + (*setter)(std::pair( + nlohmann::json::array({FileRoot::kGitTreeMarker, tree_id}), + /*is_cache_hit=*/false)); + return; + } + auto const& is_fatal = std::get<bool>(serve_result); + if (is_fatal) { + (*logger)(fmt::format("Serve endpoint failed to set up root " + "from known foreign-file content {}", + key.archive.content), + /*fatal=*/true); + return; + } + (*logger)( + fmt::format("Failed to set up root via serve, continuing anyway"), + /*fatal=*/false); + } + else { + (*logger)(fmt::format("Workspace root {} marked absent but no " + "serve endpoint provided.", + tree_id), + /*fatal=*/false); + } + (*setter)( + std::pair(nlohmann::json::array({FileRoot::kGitTreeMarker, tree_id}), + false /*no cache hit*/)); +} +} // namespace + +[[nodiscard]] auto CreateForeignFileGitMap( + gsl::not_null<ContentCASMap*> const& content_cas_map, + gsl::not_null<ImportToGitMap*> const& import_to_git_map, + bool serve_api_exists, + bool fetch_absent, + std::size_t jobs) -> ForeignFileGitMap { + auto setup_foreign_file = + [content_cas_map, import_to_git_map, fetch_absent, serve_api_exists]( + auto ts, + auto setter, + auto logger, + auto /* unused */, + auto const& key) { + if (key.absent and not fetch_absent) { + HandleAbsentForeignFile(key, serve_api_exists, setter, logger); + } + auto tree_id_file = StorageUtils::GetForeignFileTreeIDFile( + key.archive.content, key.name, key.executable); + if (FileSystemManager::Exists(tree_id_file)) { + auto tree_id = FileSystemManager::ReadFile(tree_id_file); + if (not tree_id) { + (*logger)(fmt::format("Failed to read tree id from file {}", + tree_id_file.string()), + /*fatal=*/true); + return; + } + UseCacheHit(*tree_id, setter); + return; + } + content_cas_map->ConsumeAfterKeysReady( + ts, + {key.archive}, + [key, import_to_git_map, setter, logger, ts]( + [[maybe_unused]] auto const& values) { + WithFetchedFile(key, import_to_git_map, ts, setter, logger); + }, + [logger, content = key.archive.content](auto const& msg, + bool fatal) { + (*logger)(fmt::format("While ensuring content {} is in " + "CAS:\n{}", + content, + msg), + fatal); + }); + }; + return AsyncMapConsumer<ForeignFileInfo, std::pair<nlohmann::json, bool>>( + setup_foreign_file, jobs); +} diff --git a/src/other_tools/root_maps/foreign_file_git_map.hpp b/src/other_tools/root_maps/foreign_file_git_map.hpp new file mode 100644 index 00000000..c37d97f3 --- /dev/null +++ b/src/other_tools/root_maps/foreign_file_git_map.hpp @@ -0,0 +1,39 @@ +// 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_OTHER_TOOLS_ROOT_MAPS_FOREIGN_FILE_GIT_MAP_HPP +#define INCLUDED_SRC_OTHER_TOOLS_ROOT_MAPS_FOREIGN_FILE_GIT_MAP_HPP + +#include <optional> +#include <utility> + +#include "gsl/gsl" +#include "nlohmann/json.hpp" +#include "src/other_tools/ops_maps/content_cas_map.hpp" +#include "src/other_tools/ops_maps/import_to_git_map.hpp" + +/// \brief Maps a oreign file to the resulting Git tree WS root, +/// together with the information whether it was a cache hit. +using ForeignFileGitMap = + AsyncMapConsumer<ForeignFileInfo, + std::pair<nlohmann::json /*root*/, bool /*is_cache_hit*/>>; + +[[nodiscard]] auto CreateForeignFileGitMap( + gsl::not_null<ContentCASMap*> const& content_cas_map, + gsl::not_null<ImportToGitMap*> const& import_to_git_map, + bool serve_api_exists, + bool fetch_absent, + std::size_t jobs) -> ForeignFileGitMap; + +#endif |