diff options
author | Paul Cristian Sarbu <paul.cristian.sarbu@huawei.com> | 2023-08-23 15:07:11 +0200 |
---|---|---|
committer | Paul Cristian Sarbu <paul.cristian.sarbu@huawei.com> | 2023-08-23 15:23:20 +0200 |
commit | 9fab20246c23193b1a68387cf0b5f63236639a58 (patch) | |
tree | bcf2fc7275fb395e44c3bb10b27c84c128549aa8 | |
parent | 796d9a27daba4dd16f31a7b0237fff00218d754b (diff) | |
download | justbuild-9fab20246c23193b1a68387cf0b5f63236639a58.tar.gz |
just-mr: Split main code
...by moving subcommands code into separate libraries.
This maintains a cleaner code structure and lowers the build time
by improving target caching.
-rw-r--r-- | src/other_tools/just_mr/TARGETS | 99 | ||||
-rw-r--r-- | src/other_tools/just_mr/fetch.cpp | 300 | ||||
-rw-r--r-- | src/other_tools/just_mr/fetch.hpp | 28 | ||||
-rw-r--r-- | src/other_tools/just_mr/launch.cpp | 168 | ||||
-rw-r--r-- | src/other_tools/just_mr/launch.hpp | 31 | ||||
-rw-r--r-- | src/other_tools/just_mr/main.cpp | 941 | ||||
-rw-r--r-- | src/other_tools/just_mr/setup.cpp | 206 | ||||
-rw-r--r-- | src/other_tools/just_mr/setup.hpp | 31 | ||||
-rw-r--r-- | src/other_tools/just_mr/update.cpp | 226 | ||||
-rw-r--r-- | src/other_tools/just_mr/update.hpp | 27 | ||||
-rw-r--r-- | src/other_tools/just_mr/utils.cpp | 111 | ||||
-rw-r--r-- | src/other_tools/just_mr/utils.hpp | 21 |
12 files changed, 1261 insertions, 928 deletions
diff --git a/src/other_tools/just_mr/TARGETS b/src/other_tools/just_mr/TARGETS index aac66487..b3ddcfb0 100644 --- a/src/other_tools/just_mr/TARGETS +++ b/src/other_tools/just_mr/TARGETS @@ -10,17 +10,15 @@ , ["src/buildtool/main", "version"] , "cli" , "exit_codes" - , ["src/other_tools/ops_maps", "repo_fetch_map"] - , ["src/other_tools/ops_maps", "git_update_map"] - , ["src/other_tools/repo_map", "repos_to_setup_map"] - , ["src/buildtool/progress_reporting", "base_progress_reporter"] - , ["src/buildtool/progress_reporting", "progress"] + , ["@", "cli11", "", "cli11"] + , ["@", "gsl", "", "gsl"] , ["@", "json", "", "json"] - , ["src/other_tools/just_mr/progress_reporting", "progress"] - , ["src/other_tools/just_mr/progress_reporting", "statistics"] - , ["src/other_tools/just_mr/progress_reporting", "progress_reporter"] , ["src/buildtool/storage", "storage"] - , ["src/other_tools/symlinks_map", "resolve_symlinks_map"] + , ["src/buildtool/file_system", "git_context"] + , "fetch" + , "update" + , "setup" + , "launch" ] , "stage": ["src", "other_tools", "just_mr"] , "private-ldflags": @@ -51,6 +49,7 @@ [ ["src/utils/cpp", "path"] , ["src/buildtool/execution_api/local", "local"] , ["src/buildtool/file_system", "file_storage"] + , "exit_codes" ] } , "exit_codes": @@ -76,4 +75,86 @@ , "stage": ["src", "other_tools", "just_mr"] , "private-deps": [["src/buildtool/logging", "logging"]] } +, "fetch": + { "type": ["@", "rules", "CC", "library"] + , "name": ["fetch"] + , "hdrs": ["fetch.hpp"] + , "srcs": ["fetch.cpp"] + , "deps": [["src/buildtool/build_engine/expression", "expression"], "cli"] + , "stage": ["src", "other_tools", "just_mr"] + , "private-deps": + [ ["@", "json", "", "json"] + , ["src/buildtool/logging", "logging"] + , ["src/buildtool/multithreading", "task_system"] + , "exit_codes" + , ["src/other_tools/just_mr/progress_reporting", "progress"] + , ["src/other_tools/just_mr/progress_reporting", "progress_reporter"] + , "utils" + , ["src/other_tools/ops_maps", "content_cas_map"] + , ["src/other_tools/ops_maps", "repo_fetch_map"] + ] + } +, "update": + { "type": ["@", "rules", "CC", "library"] + , "name": ["update"] + , "hdrs": ["update.hpp"] + , "srcs": ["update.cpp"] + , "deps": [["src/buildtool/build_engine/expression", "expression"], "cli"] + , "stage": ["src", "other_tools", "just_mr"] + , "private-deps": + [ ["@", "json", "", "json"] + , ["src/buildtool/logging", "logging"] + , ["src/buildtool/multithreading", "task_system"] + , ["src/other_tools/git_operations", "git_repo_remote"] + , "exit_codes" + , ["src/other_tools/just_mr/progress_reporting", "progress"] + , ["src/other_tools/just_mr/progress_reporting", "progress_reporter"] + , "utils" + , ["src/other_tools/ops_maps", "git_update_map"] + ] + } +, "setup": + { "type": ["@", "rules", "CC", "library"] + , "name": ["setup"] + , "hdrs": ["setup.hpp"] + , "srcs": ["setup.cpp"] + , "deps": [["src/buildtool/build_engine/expression", "expression"], "cli"] + , "stage": ["src", "other_tools", "just_mr"] + , "private-deps": + [ ["@", "json", "", "json"] + , ["src/buildtool/logging", "logging"] + , ["src/buildtool/multithreading", "task_system"] + , "exit_codes" + , ["src/other_tools/just_mr/progress_reporting", "progress"] + , ["src/other_tools/just_mr/progress_reporting", "progress_reporter"] + , "utils" + , ["src/other_tools/ops_maps", "critical_git_op_map"] + , ["src/other_tools/repo_map", "repos_to_setup_map"] + , ["src/other_tools/root_maps", "commit_git_map"] + , ["src/other_tools/root_maps", "content_git_map"] + , ["src/other_tools/root_maps", "distdir_git_map"] + , ["src/other_tools/root_maps", "fpath_git_map"] + , ["src/other_tools/root_maps", "tree_id_git_map"] + , ["src/other_tools/symlinks_map", "resolve_symlinks_map"] + ] + } +, "launch": + { "type": ["@", "rules", "CC", "library"] + , "name": ["launch"] + , "hdrs": ["launch.hpp"] + , "srcs": ["launch.cpp"] + , "deps": ["cli"] + , "stage": ["src", "other_tools", "just_mr"] + , "private-deps": + [ ["@", "json", "", "json"] + , ["src/buildtool/build_engine/expression", "expression"] + , ["src/buildtool/logging", "logging"] + , ["src/buildtool/multithreading", "task_system"] + , ["src/buildtool/storage", "storage"] + , "exit_codes" + , "setup" + , "utils" + , ["src/utils/cpp", "file_locking"] + ] + } } diff --git a/src/other_tools/just_mr/fetch.cpp b/src/other_tools/just_mr/fetch.cpp new file mode 100644 index 00000000..c6a4bc17 --- /dev/null +++ b/src/other_tools/just_mr/fetch.cpp @@ -0,0 +1,300 @@ +// Copyright 2023 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/just_mr/fetch.hpp" + +#include <filesystem> + +#include "nlohmann/json.hpp" +#include "src/buildtool/logging/log_level.hpp" +#include "src/buildtool/logging/logger.hpp" +#include "src/buildtool/multithreading/task_system.hpp" +#include "src/other_tools/just_mr/exit_codes.hpp" +#include "src/other_tools/just_mr/progress_reporting/progress.hpp" +#include "src/other_tools/just_mr/progress_reporting/progress_reporter.hpp" +#include "src/other_tools/just_mr/utils.hpp" +#include "src/other_tools/ops_maps/content_cas_map.hpp" +#include "src/other_tools/ops_maps/repo_fetch_map.hpp" + +auto MultiRepoFetch(std::shared_ptr<Configuration> const& config, + MultiRepoCommonArguments const& common_args, + MultiRepoSetupArguments const& setup_args, + MultiRepoFetchArguments const& fetch_args) -> int { + // provide report + Logger::Log(LogLevel::Info, "Performing repositories fetch"); + + // find fetch dir + auto fetch_dir = fetch_args.fetch_dir; + if (not fetch_dir) { + for (auto const& d : common_args.just_mr_paths->distdirs) { + if (FileSystemManager::IsDirectory(d)) { + fetch_dir = std::filesystem::weakly_canonical( + std::filesystem::absolute(d)); + break; + } + } + } + if (not fetch_dir) { + auto considered = nlohmann::json(common_args.just_mr_paths->distdirs); + Logger::Log(LogLevel::Error, + "No directory found to fetch to, considered {}", + considered.dump()); + return kExitFetchError; + } + + auto repos = (*config)["repositories"]; + if (not repos.IsNotNull()) { + Logger::Log(LogLevel::Error, + "Config: Mandatory key \"repositories\" missing"); + return kExitFetchError; + } + auto fetch_repos = + std::make_shared<JustMR::SetupRepos>(); // repos to setup and include + JustMR::Utils::DefaultReachableRepositories(repos, fetch_repos); + + if (not setup_args.sub_all) { + auto main = common_args.main; + if (not main and not fetch_repos->to_include.empty()) { + main = *std::min_element(fetch_repos->to_include.begin(), + fetch_repos->to_include.end()); + } + if (main) { + JustMR::Utils::ReachableRepositories(repos, *main, fetch_repos); + } + + std::function<bool(std::filesystem::path const&, + std::filesystem::path const&)> + is_subpath = [](std::filesystem::path const& path, + std::filesystem::path const& base) { + return (std::filesystem::proximate(path, base) == base); + }; + + // warn if fetch_dir is in invocation workspace + if (common_args.just_mr_paths->workspace_root and + is_subpath(*fetch_dir, + *common_args.just_mr_paths->workspace_root)) { + auto repo_desc = repos->Get(*main, Expression::none_t{}); + auto repo = repo_desc->Get("repository", Expression::none_t{}); + auto repo_path = repo->Get("path", Expression::none_t{}); + auto repo_type = repo->Get("type", Expression::none_t{}); + if (repo_path->IsString() and repo_type->IsString() and + (repo_type->String() == "file")) { + auto repo_path_as_path = + std::filesystem::path(repo_path->String()); + if (not repo_path_as_path.is_absolute()) { + repo_path_as_path = std::filesystem::weakly_canonical( + std::filesystem::absolute( + common_args.just_mr_paths->setup_root / + repo_path_as_path)); + } + // only warn if repo workspace differs to invocation workspace + if (not is_subpath( + repo_path_as_path, + *common_args.just_mr_paths->workspace_root)) { + Logger::Log( + LogLevel::Warning, + "Writing distribution files to workspace location {}, " + "which is different to the workspace of the requested " + "main repository {}.", + nlohmann::json(fetch_dir->string()).dump(), + nlohmann::json(repo_path_as_path.string()).dump()); + } + } + } + } + + Logger::Log(LogLevel::Info, "Fetching to {}", fetch_dir->string()); + + // gather all repos to be fetched + std::vector<ArchiveRepoInfo> repos_to_fetch{}; + repos_to_fetch.reserve( + fetch_repos->to_include.size()); // pre-reserve a maximum size + for (auto const& repo_name : fetch_repos->to_include) { + auto repo_desc = repos->At(repo_name); + if (not repo_desc) { + Logger::Log(LogLevel::Error, + "Config: Missing config entry for repository {}", + nlohmann::json(repo_name).dump()); + return kExitFetchError; + } + auto repo = repo_desc->get()->At("repository"); + if (repo) { + auto resolved_repo_desc = + JustMR::Utils::ResolveRepo(repo->get(), repos); + if (not resolved_repo_desc) { + Logger::Log(LogLevel::Error, + "Config: Found cyclic dependency for repository {}", + nlohmann::json(repo_name).dump()); + return kExitFetchError; + } + // get repo_type + auto repo_type = (*resolved_repo_desc)->At("type"); + if (not repo_type) { + Logger::Log( + LogLevel::Error, + "Config: Mandatory key \"type\" missing for repository {}", + nlohmann::json(repo_name).dump()); + return kExitFetchError; + } + if (not repo_type->get()->IsString()) { + Logger::Log(LogLevel::Error, + "Config: Unsupported value {} for key \"type\" for " + "repository {}", + repo_type->get()->ToString(), + nlohmann::json(repo_name).dump()); + return kExitFetchError; + } + auto repo_type_str = repo_type->get()->String(); + if (not kCheckoutTypeMap.contains(repo_type_str)) { + Logger::Log(LogLevel::Error, + "Unknown repository type {} for {}", + nlohmann::json(repo_type_str).dump(), + nlohmann::json(repo_name).dump()); + return kExitFetchError; + } + // only do work if repo is archive type + if (kCheckoutTypeMap.at(repo_type_str) == CheckoutType::Archive) { + // check mandatory fields + auto repo_desc_content = (*resolved_repo_desc)->At("content"); + if (not repo_desc_content) { + Logger::Log(LogLevel::Error, + "Mandatory field \"content\" is missing"); + return kExitFetchError; + } + if (not repo_desc_content->get()->IsString()) { + Logger::Log( + LogLevel::Error, + "Unsupported value {} for mandatory field \"content\"", + repo_desc_content->get()->ToString()); + return kExitFetchError; + } + auto repo_desc_fetch = (*resolved_repo_desc)->At("fetch"); + if (not repo_desc_fetch) { + Logger::Log(LogLevel::Error, + "Mandatory field \"fetch\" is missing"); + return kExitFetchError; + } + if (not repo_desc_fetch->get()->IsString()) { + Logger::Log(LogLevel::Error, + "ArchiveCheckout: Unsupported value {} for " + "mandatory field \"fetch\"", + repo_desc_fetch->get()->ToString()); + return kExitFetchError; + } + auto repo_desc_subdir = + (*resolved_repo_desc)->Get("subdir", Expression::none_t{}); + auto subdir = + std::filesystem::path(repo_desc_subdir->IsString() + ? repo_desc_subdir->String() + : "") + .lexically_normal(); + auto repo_desc_distfile = + (*resolved_repo_desc) + ->Get("distfile", Expression::none_t{}); + auto repo_desc_sha256 = + (*resolved_repo_desc)->Get("sha256", Expression::none_t{}); + auto repo_desc_sha512 = + (*resolved_repo_desc)->Get("sha512", Expression::none_t{}); + + ArchiveRepoInfo archive_info = { + .archive = {.content = repo_desc_content->get()->String(), + .distfile = + repo_desc_distfile->IsString() + ? std::make_optional( + repo_desc_distfile->String()) + : std::nullopt, + .fetch_url = repo_desc_fetch->get()->String(), + .sha256 = repo_desc_sha256->IsString() + ? std::make_optional( + repo_desc_sha256->String()) + : std::nullopt, + .sha512 = repo_desc_sha512->IsString() + ? std::make_optional( + repo_desc_sha512->String()) + : std::nullopt, + .origin = repo_name, + .origin_from_distdir = false}, + .repo_type = repo_type_str, + .subdir = subdir.empty() ? "." : subdir.string(), + .pragma_special = std::nullopt // not used + }; + // add to list + repos_to_fetch.emplace_back(std::move(archive_info)); + } + } + else { + Logger::Log(LogLevel::Error, + "Config: Missing repository description for {}", + nlohmann::json(repo_name).dump()); + return kExitFetchError; + } + } + + // report progress + auto nr = repos_to_fetch.size(); + Logger::Log(LogLevel::Info, + "Found {} {} to fetch", + nr, + nr == 1 ? "archive" : "archives"); + + // create async maps + auto content_cas_map = CreateContentCASMap( + common_args.just_mr_paths, common_args.ca_info, common_args.jobs); + auto repo_fetch_map = + CreateRepoFetchMap(&content_cas_map, *fetch_dir, common_args.jobs); + + // set up progress observer + JustMRProgress::Instance().SetTotal(repos_to_fetch.size()); + std::atomic<bool> done{false}; + std::condition_variable cv{}; + auto reporter = JustMRProgressReporter::Reporter(); + auto observer = + std::thread([reporter, &done, &cv]() { reporter(&done, &cv); }); + + // do the fetch + bool failed{false}; + { + TaskSystem ts{common_args.jobs}; + repo_fetch_map.ConsumeAfterKeysReady( + &ts, + repos_to_fetch, + [&failed](auto const& values) { + // report any fetch fails + for (auto const& val : values) { + if (not *val) { + failed = true; + break; + } + } + }, + [&failed](auto const& msg, bool fatal) { + Logger::Log(fatal ? LogLevel::Error : LogLevel::Warning, + "While performing just-mr fetch:\n{}", + msg); + failed = failed or fatal; + }); + } + + // close progress observer + done = true; + cv.notify_all(); + observer.join(); + + if (failed) { + return kExitFetchError; + } + // report success + Logger::Log(LogLevel::Info, "Fetch completed"); + return kExitSuccess; +} diff --git a/src/other_tools/just_mr/fetch.hpp b/src/other_tools/just_mr/fetch.hpp new file mode 100644 index 00000000..be96630b --- /dev/null +++ b/src/other_tools/just_mr/fetch.hpp @@ -0,0 +1,28 @@ +// Copyright 2023 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_JUST_MR_FETCH_HPP +#define INCLUDED_SRC_OTHER_TOOLS_JUST_MR_FETCH_HPP + +#include "src/buildtool/build_engine/expression/configuration.hpp" +#include "src/other_tools/just_mr/cli.hpp" + +/// \brief Fetching of distfiles for a multi-repository build. +[[nodiscard]] auto MultiRepoFetch(std::shared_ptr<Configuration> const& config, + MultiRepoCommonArguments const& common_args, + MultiRepoSetupArguments const& setup_args, + MultiRepoFetchArguments const& fetch_args) + -> int; + +#endif // INCLUDED_SRC_OTHER_TOOLS_JUST_MR_FETCH_HPP diff --git a/src/other_tools/just_mr/launch.cpp b/src/other_tools/just_mr/launch.cpp new file mode 100644 index 00000000..0d8f263b --- /dev/null +++ b/src/other_tools/just_mr/launch.cpp @@ -0,0 +1,168 @@ +// Copyright 2023 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/just_mr/launch.hpp" + +#include <filesystem> + +#include "nlohmann/json.hpp" +#include "src/buildtool/build_engine/expression/configuration.hpp" +#include "src/buildtool/build_engine/expression/expression.hpp" +#include "src/buildtool/logging/log_level.hpp" +#include "src/buildtool/logging/logger.hpp" +#include "src/buildtool/multithreading/task_system.hpp" +#include "src/buildtool/storage/garbage_collector.hpp" +#include "src/other_tools/just_mr/exit_codes.hpp" +#include "src/other_tools/just_mr/setup.hpp" +#include "src/other_tools/just_mr/utils.hpp" +#include "src/utils/cpp/file_locking.hpp" + +auto CallJust(std::optional<std::filesystem::path> const& config_file, + MultiRepoCommonArguments const& common_args, + MultiRepoSetupArguments const& setup_args, + MultiRepoJustSubCmdsArguments const& just_cmd_args, + MultiRepoLogArguments const& log_args, + bool forward_build_root) -> int { + // check if subcmd_name can be taken from additional args + auto additional_args_offset = 0U; + auto subcommand = just_cmd_args.subcmd_name; + if (not subcommand and not just_cmd_args.additional_just_args.empty()) { + subcommand = just_cmd_args.additional_just_args[0]; + additional_args_offset++; + } + + bool use_config{false}; + bool use_build_root{false}; + bool use_launcher{false}; + bool supports_defines{false}; + std::optional<std::filesystem::path> mr_config_path{std::nullopt}; + + std::optional<LockFile> lock{}; + if (subcommand and kKnownJustSubcommands.contains(*subcommand)) { + // Read the config file if needed + if (kKnownJustSubcommands.at(*subcommand).config) { + lock = GarbageCollector::SharedLock(); + if (not lock) { + return kExitGenericFailure; + } + auto config = JustMR::Utils::ReadConfiguration(config_file); + + use_config = true; + mr_config_path = MultiRepoSetup(config, + common_args, + setup_args, + just_cmd_args, + /*interactive=*/false); + if (not mr_config_path) { + Logger::Log(LogLevel::Error, + "Failed to setup config while calling \"just {}\"", + *subcommand); + return kExitSetupError; + } + } + use_build_root = kKnownJustSubcommands.at(*subcommand).build_root; + use_launcher = kKnownJustSubcommands.at(*subcommand).launch; + supports_defines = kKnownJustSubcommands.at(*subcommand).defines; + } + // build just command + std::vector<std::string> cmd = {common_args.just_path->string()}; + if (subcommand) { + cmd.emplace_back(*subcommand); + } + if (use_config) { + cmd.emplace_back("-C"); + cmd.emplace_back(mr_config_path->string()); + } + if (use_build_root and forward_build_root) { + cmd.emplace_back("--local-build-root"); + cmd.emplace_back(*common_args.just_mr_paths->root); + } + if (use_launcher and common_args.local_launcher and + (common_args.local_launcher != kDefaultLauncher)) { + cmd.emplace_back("--local-launcher"); + cmd.emplace_back(nlohmann::json(*common_args.local_launcher).dump()); + } + // forward logging arguments + if (not log_args.log_files.empty()) { + cmd.emplace_back("--log-append"); + for (auto const& log_file : log_args.log_files) { + cmd.emplace_back("-f"); + cmd.emplace_back(log_file.string()); + } + } + if (log_args.log_limit and *log_args.log_limit != kDefaultLogLevel) { + cmd.emplace_back("--log-limit"); + cmd.emplace_back( + std::to_string(static_cast<std::underlying_type<LogLevel>::type>( + *log_args.log_limit))); + } + if (log_args.plain_log) { + cmd.emplace_back("--plain-log"); + } + if (supports_defines) { + auto overlay_config = Configuration(); + for (auto const& s : common_args.defines) { + try { + auto map = Expression::FromJson(nlohmann::json::parse(s)); + if (not map->IsMap()) { + Logger::Log(LogLevel::Error, + "Defines entry {} does not contain a map.", + nlohmann::json(s).dump()); + std::exit(kExitClargsError); + } + overlay_config = overlay_config.Update(map); + } catch (std::exception const& e) { + Logger::Log(LogLevel::Error, + "Parsing defines entry {} failed with error:\n{}", + nlohmann::json(s).dump(), + e.what()); + std::exit(kExitClargsError); + } + } + if (not overlay_config.Expr()->Map().empty()) { + cmd.emplace_back("-D"); + cmd.emplace_back(overlay_config.ToString()); + } + } + // add args read from just-mrrc + if (subcommand and just_cmd_args.just_args.contains(*subcommand)) { + for (auto const& subcmd_arg : just_cmd_args.just_args.at(*subcommand)) { + cmd.emplace_back(subcmd_arg); + } + } + // add (remaining) args given by user as clargs + for (auto it = just_cmd_args.additional_just_args.begin() + + additional_args_offset; + it != just_cmd_args.additional_just_args.end(); + ++it) { + cmd.emplace_back(*it); + } + + Logger::Log( + LogLevel::Info, "Setup finished, exec {}", nlohmann::json(cmd).dump()); + + // create argv + std::vector<char*> argv{}; + std::transform(std::begin(cmd), + std::end(cmd), + std::back_inserter(argv), + [](auto& str) { return str.data(); }); + argv.push_back(nullptr); + // run execvp; will only return if failure + [[maybe_unused]] auto res = + execvp(argv[0], static_cast<char* const*>(argv.data())); + // execvp returns only if command errored out + Logger::Log(LogLevel::Error, "execvp failed with error code {}", errno); + return kExitExecError; +} diff --git a/src/other_tools/just_mr/launch.hpp b/src/other_tools/just_mr/launch.hpp new file mode 100644 index 00000000..1a778a47 --- /dev/null +++ b/src/other_tools/just_mr/launch.hpp @@ -0,0 +1,31 @@ +// Copyright 2023 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_JUST_MR_LAUNCH_HPP +#define INCLUDED_SRC_OTHER_TOOLS_JUST_MR_LAUNCH_HPP + +#include <filesystem> + +#include "src/other_tools/just_mr/cli.hpp" + +/// \brief Runs execvp for configured command. Only returns if execvp fails. +[[nodiscard]] auto CallJust( + std::optional<std::filesystem::path> const& config_file, + MultiRepoCommonArguments const& common_args, + MultiRepoSetupArguments const& setup_args, + MultiRepoJustSubCmdsArguments const& just_cmd_args, + MultiRepoLogArguments const& log_args, + bool forward_build_root) -> int; + +#endif // INCLUDED_SRC_OTHER_TOOLS_JUST_MR_LAUNCH_HPP diff --git a/src/other_tools/just_mr/main.cpp b/src/other_tools/just_mr/main.cpp index ec332948..d11785b3 100644 --- a/src/other_tools/just_mr/main.cpp +++ b/src/other_tools/just_mr/main.cpp @@ -15,25 +15,26 @@ #include <filesystem> #include <utility> -#include <nlohmann/json.hpp> #include <unistd.h> +#include "CLI/CLI.hpp" +#include "gsl/gsl" +#include "nlohmann/json.hpp" #include "src/buildtool/build_engine/expression/configuration.hpp" +#include "src/buildtool/file_system/git_context.hpp" #include "src/buildtool/logging/log_config.hpp" #include "src/buildtool/logging/log_level.hpp" #include "src/buildtool/logging/log_sink_cmdline.hpp" #include "src/buildtool/logging/log_sink_file.hpp" +#include "src/buildtool/logging/logger.hpp" #include "src/buildtool/main/version.hpp" #include "src/buildtool/storage/garbage_collector.hpp" #include "src/other_tools/just_mr/cli.hpp" #include "src/other_tools/just_mr/exit_codes.hpp" -#include "src/other_tools/just_mr/progress_reporting/progress.hpp" -#include "src/other_tools/just_mr/progress_reporting/progress_reporter.hpp" -#include "src/other_tools/just_mr/progress_reporting/statistics.hpp" -#include "src/other_tools/ops_maps/git_update_map.hpp" -#include "src/other_tools/ops_maps/repo_fetch_map.hpp" -#include "src/other_tools/repo_map/repos_to_setup_map.hpp" -#include "src/other_tools/symlinks_map/resolve_symlinks_map.hpp" +#include "src/other_tools/just_mr/fetch.hpp" +#include "src/other_tools/just_mr/launch.hpp" +#include "src/other_tools/just_mr/setup.hpp" +#include "src/other_tools/just_mr/update.hpp" namespace { @@ -58,11 +59,6 @@ struct CommandLineArguments { MultiRepoJustSubCmdsArguments just_cmd; }; -struct SetupRepos { - std::vector<std::string> to_setup; - std::vector<std::string> to_include; -}; - /// \brief Setup arguments for just-mr itself, common to all subcommands. void SetupCommonCommandArguments( gsl::not_null<CLI::App*> const& app, @@ -519,907 +515,6 @@ void SetupLogging(MultiRepoLogArguments const& clargs) { return std::nullopt; // default return value } -[[nodiscard]] auto ReadConfiguration( - std::optional<std::filesystem::path> const& config_file_opt) noexcept - -> std::shared_ptr<Configuration> { - if (not config_file_opt) { - Logger::Log(LogLevel::Error, "Cannot find repository configuration."); - std::exit(kExitConfigError); - } - auto const& config_file = *config_file_opt; - - std::shared_ptr<Configuration> config{nullptr}; - if (not FileSystemManager::IsFile(config_file)) { - Logger::Log(LogLevel::Error, - "Cannot read config file {}.", - config_file.string()); - std::exit(kExitConfigError); - } - try { - std::ifstream fs(config_file); - auto map = Expression::FromJson(nlohmann::json::parse(fs)); - if (not map->IsMap()) { - Logger::Log(LogLevel::Error, - "Config file {} does not contain a JSON object.", - config_file.string()); - std::exit(kExitConfigError); - } - config = std::make_shared<Configuration>(map); - } catch (std::exception const& e) { - Logger::Log(LogLevel::Error, - "Parsing config file {} failed with error:\n{}", - config_file.string(), - e.what()); - std::exit(kExitConfigError); - } - return config; -} - -/// \brief Get the repo dependency closure for a given main repository. -/// For progress reporting we include -void ReachableRepositories(ExpressionPtr const& repos, - std::string const& main, - std::shared_ptr<SetupRepos> const& setup_repos) { - // use temporary sets to avoid duplicates - std::unordered_set<std::string> include_repos_set{}; - if (repos->IsMap()) { - // traversal of bindings - std::function<void(std::string const&)> traverse = - [&](std::string const& repo_name) { - if (not include_repos_set.contains(repo_name)) { - // if not found, add it and repeat for its bindings - include_repos_set.insert(repo_name); - // check bindings - auto repos_repo_name = - repos->Get(repo_name, Expression::none_t{}); - if (not repos_repo_name.IsNotNull()) { - return; - } - auto bindings = - repos_repo_name->Get("bindings", Expression::none_t{}); - if (bindings.IsNotNull() and bindings->IsMap()) { - for (auto const& bound : bindings->Map().Values()) { - if (bound.IsNotNull() and bound->IsString()) { - traverse(bound->String()); - } - } - } - } - }; - traverse(main); // traverse all bindings of main repository - - // Add overlay repositories - std::unordered_set<std::string> setup_repos_set{include_repos_set}; - for (auto const& repo : include_repos_set) { - auto repos_repo = repos->Get(repo, Expression::none_t{}); - if (repos_repo.IsNotNull()) { - // copy over any present alternative root dirs - for (auto const& layer : kAltDirs) { - auto layer_val = - repos_repo->Get(layer, Expression::none_t{}); - if (layer_val.IsNotNull() and layer_val->IsString()) { - auto repo_name = layer_val->String(); - setup_repos_set.insert(repo_name); - } - } - } - } - - // copy to vectors - setup_repos->to_setup.clear(); - setup_repos->to_setup.reserve(setup_repos_set.size()); - std::copy( - setup_repos_set.begin(), - setup_repos_set.end(), - std::inserter(setup_repos->to_setup, setup_repos->to_setup.end())); - setup_repos->to_include.clear(); - setup_repos->to_include.reserve(include_repos_set.size()); - std::copy(include_repos_set.begin(), - include_repos_set.end(), - std::inserter(setup_repos->to_include, - setup_repos->to_include.end())); - } -} - -void DefaultReachableRepositories( - ExpressionPtr const& repos, - std::shared_ptr<SetupRepos> const& setup_repos) { - if (repos.IsNotNull() and repos->IsMap()) { - setup_repos->to_setup = repos->Map().Keys(); - setup_repos->to_include = setup_repos->to_setup; - } -} - -[[nodiscard]] auto MultiRepoFetch(std::shared_ptr<Configuration> const& config, - CommandLineArguments const& arguments) - -> int { - // provide report - Logger::Log(LogLevel::Info, "Performing repositories fetch"); - - // find fetch dir - auto fetch_dir = arguments.fetch.fetch_dir; - if (not fetch_dir) { - for (auto const& d : arguments.common.just_mr_paths->distdirs) { - if (FileSystemManager::IsDirectory(d)) { - fetch_dir = std::filesystem::weakly_canonical( - std::filesystem::absolute(d)); - break; - } - } - } - if (not fetch_dir) { - auto considered = - nlohmann::json(arguments.common.just_mr_paths->distdirs); - Logger::Log(LogLevel::Error, - "No directory found to fetch to, considered {}", - considered.dump()); - return kExitFetchError; - } - - auto repos = (*config)["repositories"]; - if (not repos.IsNotNull()) { - Logger::Log(LogLevel::Error, - "Config: Mandatory key \"repositories\" " - "missing"); - return kExitFetchError; - } - auto fetch_repos = - std::make_shared<SetupRepos>(); // repos to setup and include - DefaultReachableRepositories(repos, fetch_repos); - - if (not arguments.setup.sub_all) { - auto main = arguments.common.main; - if (not main and not fetch_repos->to_include.empty()) { - main = *std::min_element(fetch_repos->to_include.begin(), - fetch_repos->to_include.end()); - } - if (main) { - ReachableRepositories(repos, *main, fetch_repos); - } - - std::function<bool(std::filesystem::path const&, - std::filesystem::path const&)> - is_subpath = [](std::filesystem::path const& path, - std::filesystem::path const& base) { - return (std::filesystem::proximate(path, base) == base); - }; - - // warn if fetch_dir is in invocation workspace - if (arguments.common.just_mr_paths->workspace_root and - is_subpath(*fetch_dir, - *arguments.common.just_mr_paths->workspace_root)) { - auto repo_desc = repos->Get(*main, Expression::none_t{}); - auto repo = repo_desc->Get("repository", Expression::none_t{}); - auto repo_path = repo->Get("path", Expression::none_t{}); - auto repo_type = repo->Get("type", Expression::none_t{}); - if (repo_path->IsString() and repo_type->IsString() and - (repo_type->String() == "file")) { - auto repo_path_as_path = - std::filesystem::path(repo_path->String()); - if (not repo_path_as_path.is_absolute()) { - repo_path_as_path = std::filesystem::weakly_canonical( - std::filesystem::absolute( - arguments.common.just_mr_paths->setup_root / - repo_path_as_path)); - } - // only warn if repo workspace differs to invocation workspace - if (not is_subpath( - repo_path_as_path, - *arguments.common.just_mr_paths->workspace_root)) { - Logger::Log( - LogLevel::Warning, - "Writing distribution files to workspace location {}, " - "which is different to the workspace of the requested " - "main repository {}.", - nlohmann::json(fetch_dir->string()).dump(), - nlohmann::json(repo_path_as_path.string()).dump()); - } - } - } - } - - Logger::Log(LogLevel::Info, "Fetching to {}", fetch_dir->string()); - - // gather all repos to be fetched - std::vector<ArchiveRepoInfo> repos_to_fetch{}; - repos_to_fetch.reserve( - fetch_repos->to_include.size()); // pre-reserve a maximum size - for (auto const& repo_name : fetch_repos->to_include) { - auto repo_desc = repos->At(repo_name); - if (not repo_desc) { - Logger::Log(LogLevel::Error, - "Config: Missing config entry for repository {}", - nlohmann::json(repo_name).dump()); - return kExitFetchError; - } - auto repo = repo_desc->get()->At("repository"); - if (repo) { - auto resolved_repo_desc = - JustMR::Utils::ResolveRepo(repo->get(), repos); - if (not resolved_repo_desc) { - Logger::Log(LogLevel::Error, - "Config: Found cyclic dependency for " - "repository {}", - nlohmann::json(repo_name).dump()); - return kExitFetchError; - } - // get repo_type - auto repo_type = (*resolved_repo_desc)->At("type"); - if (not repo_type) { - Logger::Log(LogLevel::Error, - "Config: Mandatory key \"type\" missing " - "for repository {}", - nlohmann::json(repo_name).dump()); - return kExitFetchError; - } - if (not repo_type->get()->IsString()) { - Logger::Log(LogLevel::Error, - "Config: Unsupported value {} for key \"type\" for " - "repository {}", - repo_type->get()->ToString(), - nlohmann::json(repo_name).dump()); - return kExitFetchError; - } - auto repo_type_str = repo_type->get()->String(); - if (not kCheckoutTypeMap.contains(repo_type_str)) { - Logger::Log(LogLevel::Error, - "Unknown repository type {} for {}", - nlohmann::json(repo_type_str).dump(), - nlohmann::json(repo_name).dump()); - return kExitFetchError; - } - // only do work if repo is archive type - if (kCheckoutTypeMap.at(repo_type_str) == CheckoutType::Archive) { - // check mandatory fields - auto repo_desc_content = (*resolved_repo_desc)->At("content"); - if (not repo_desc_content) { - Logger::Log(LogLevel::Error, - "Mandatory field \"content\" is missing"); - return kExitFetchError; - } - if (not repo_desc_content->get()->IsString()) { - Logger::Log( - LogLevel::Error, - "Unsupported value {} for mandatory field \"content\"", - repo_desc_content->get()->ToString()); - return kExitFetchError; - } - auto repo_desc_fetch = (*resolved_repo_desc)->At("fetch"); - if (not repo_desc_fetch) { - Logger::Log(LogLevel::Error, - "Mandatory field \"fetch\" is missing"); - return kExitFetchError; - } - if (not repo_desc_fetch->get()->IsString()) { - Logger::Log(LogLevel::Error, - "ArchiveCheckout: Unsupported value {} for " - "mandatory field \"fetch\"", - repo_desc_fetch->get()->ToString()); - return kExitFetchError; - } - auto repo_desc_subdir = - (*resolved_repo_desc)->Get("subdir", Expression::none_t{}); - auto subdir = - std::filesystem::path(repo_desc_subdir->IsString() - ? repo_desc_subdir->String() - : "") - .lexically_normal(); - auto repo_desc_distfile = - (*resolved_repo_desc) - ->Get("distfile", Expression::none_t{}); - auto repo_desc_sha256 = - (*resolved_repo_desc)->Get("sha256", Expression::none_t{}); - auto repo_desc_sha512 = - (*resolved_repo_desc)->Get("sha512", Expression::none_t{}); - - ArchiveRepoInfo archive_info = { - .archive = {.content = repo_desc_content->get()->String(), - .distfile = - repo_desc_distfile->IsString() - ? std::make_optional( - repo_desc_distfile->String()) - : std::nullopt, - .fetch_url = repo_desc_fetch->get()->String(), - .sha256 = repo_desc_sha256->IsString() - ? std::make_optional( - repo_desc_sha256->String()) - : std::nullopt, - .sha512 = repo_desc_sha512->IsString() - ? std::make_optional( - repo_desc_sha512->String()) - : std::nullopt, - .origin = repo_name, - .origin_from_distdir = false}, - .repo_type = repo_type_str, - .subdir = subdir.empty() ? "." : subdir.string(), - .pragma_special = std::nullopt // not used - }; - // add to list - repos_to_fetch.emplace_back(std::move(archive_info)); - } - } - else { - Logger::Log(LogLevel::Error, - "Config: Missing repository description for {}", - nlohmann::json(repo_name).dump()); - return kExitFetchError; - } - } - - // report progress - auto nr = repos_to_fetch.size(); - Logger::Log(LogLevel::Info, - "Found {} {} to fetch", - nr, - nr == 1 ? "archive" : "archives"); - - // create async maps - auto content_cas_map = CreateContentCASMap(arguments.common.just_mr_paths, - arguments.common.ca_info, - arguments.common.jobs); - auto repo_fetch_map = - CreateRepoFetchMap(&content_cas_map, *fetch_dir, arguments.common.jobs); - - // set up progress observer - JustMRProgress::Instance().SetTotal(repos_to_fetch.size()); - std::atomic<bool> done{false}; - std::condition_variable cv{}; - auto reporter = JustMRProgressReporter::Reporter(); - auto observer = - std::thread([reporter, &done, &cv]() { reporter(&done, &cv); }); - - // do the fetch - bool failed{false}; - { - TaskSystem ts{arguments.common.jobs}; - repo_fetch_map.ConsumeAfterKeysReady( - &ts, - repos_to_fetch, - [&failed](auto const& values) { - // report any fetch fails - for (auto const& val : values) { - if (not *val) { - failed = true; - break; - } - } - }, - [&failed](auto const& msg, bool fatal) { - Logger::Log(fatal ? LogLevel::Error : LogLevel::Warning, - "While performing just-mr fetch:\n{}", - msg); - failed = failed or fatal; - }); - } - - // close progress observer - done = true; - cv.notify_all(); - observer.join(); - - if (failed) { - return kExitFetchError; - } - // report success - Logger::Log(LogLevel::Info, "Fetch completed"); - return kExitSuccess; -} - -[[nodiscard]] auto MultiRepoUpdate(std::shared_ptr<Configuration> const& config, - CommandLineArguments const& arguments) - -> int { - // provide report - Logger::Log(LogLevel::Info, "Performing repositories update"); - - // Check trivial case - if (arguments.update.repos_to_update.empty()) { - // report success - Logger::Log(LogLevel::Info, "No update needed"); - // print config file - std::cout << config->ToJson().dump(2) << std::endl; - return kExitSuccess; - } - auto repos = (*config)["repositories"]; - if (not repos.IsNotNull()) { - Logger::Log(LogLevel::Error, - "Config: Mandatory key \"repositories\" " - "missing"); - return kExitUpdateError; - } - // gather repos to update - std::vector<std::pair<std::string, std::string>> repos_to_update{}; - repos_to_update.reserve(arguments.update.repos_to_update.size()); - for (auto const& repo_name : arguments.update.repos_to_update) { - auto repo_desc_parent = repos->At(repo_name); - if (not repo_desc_parent) { - Logger::Log(LogLevel::Error, - "Config: Missing config entry for repository {}", - nlohmann::json(repo_name).dump()); - return kExitUpdateError; - } - auto repo_desc = repo_desc_parent->get()->At("repository"); - if (repo_desc) { - auto resolved_repo_desc = - JustMR::Utils::ResolveRepo(repo_desc->get(), repos); - if (not resolved_repo_desc) { - Logger::Log(LogLevel::Error, - fmt::format("Config: Found cyclic dependency for " - "repository {}", - nlohmann::json(repo_name).dump())); - return kExitUpdateError; - } - // get repo_type - auto repo_type = (*resolved_repo_desc)->At("type"); - if (not repo_type) { - Logger::Log(LogLevel::Error, - "Config: Mandatory key \"type\" missing " - "for repository {}", - nlohmann::json(repo_name).dump()); - return kExitUpdateError; - } - if (not repo_type->get()->IsString()) { - Logger::Log(LogLevel::Error, - "Config: Unsupported value {} for key \"type\" for " - "repository {}", - repo_type->get()->ToString(), - nlohmann::json(repo_name).dump()); - return kExitUpdateError; - } - auto repo_type_str = repo_type->get()->String(); - if (not kCheckoutTypeMap.contains(repo_type_str)) { - Logger::Log(LogLevel::Error, - "Unknown repository type {} for {}", - nlohmann::json(repo_type_str).dump(), - nlohmann::json(repo_name).dump()); - return kExitUpdateError; - } - // only do work if repo is git type - if (kCheckoutTypeMap.at(repo_type_str) == CheckoutType::Git) { - auto repo_desc_repository = - (*resolved_repo_desc)->At("repository"); - if (not repo_desc_repository) { - Logger::Log( - LogLevel::Error, - "Config: Mandatory field \"repository\" is missing"); - return kExitUpdateError; - } - if (not repo_desc_repository->get()->IsString()) { - Logger::Log(LogLevel::Error, - "Config: Unsupported value {} for key " - "\"repository\" for repository {}", - repo_desc_repository->get()->ToString(), - nlohmann::json(repo_name).dump()); - return kExitUpdateError; - } - auto repo_desc_branch = (*resolved_repo_desc)->At("branch"); - if (not repo_desc_branch) { - Logger::Log( - LogLevel::Error, - "Config: Mandatory field \"branch\" is missing"); - return kExitUpdateError; - } - if (not repo_desc_branch->get()->IsString()) { - Logger::Log(LogLevel::Error, - "Config: Unsupported value {} for key " - "\"branch\" for repository {}", - repo_desc_branch->get()->ToString(), - nlohmann::json(repo_name).dump()); - return kExitUpdateError; - } - repos_to_update.emplace_back( - std::make_pair(repo_desc_repository->get()->String(), - repo_desc_branch->get()->String())); - } - else { - Logger::Log(LogLevel::Error, - "Config: Argument {} is not the name of a \"git\" " - "type repository", - nlohmann::json(repo_name).dump()); - return kExitUpdateError; - } - } - else { - Logger::Log(LogLevel::Error, - "Config: Missing repository description for {}", - nlohmann::json(repo_name).dump()); - return kExitUpdateError; - } - } - // Create fake repo for the anonymous remotes - auto tmp_dir = JustMR::Utils::CreateTypedTmpDir("update"); - if (not tmp_dir) { - Logger::Log(LogLevel::Error, "Failed to create commit update tmp dir"); - return kExitUpdateError; - } - // Init and open git repo - auto git_repo = - GitRepoRemote::InitAndOpen(tmp_dir->GetPath(), /*is_bare=*/true); - if (not git_repo) { - Logger::Log(LogLevel::Error, - "Failed to initialize repository in tmp dir {} for git " - "commit update", - tmp_dir->GetPath().string()); - return kExitUpdateError; - } - - // report progress - auto nr = repos_to_update.size(); - Logger::Log(LogLevel::Info, - "Discovered {} Git {} to update", - nr, - nr == 1 ? "repository" : "repositories"); - - // Initialize resulting config to be updated - auto mr_config = config->ToJson(); - // Create async map - auto git_update_map = - CreateGitUpdateMap(git_repo->GetGitCAS(), - arguments.common.git_path->string(), - *arguments.common.local_launcher, - arguments.common.jobs); - - // set up progress observer - JustMRProgress::Instance().SetTotal(repos_to_update.size()); - std::atomic<bool> done{false}; - std::condition_variable cv{}; - auto reporter = JustMRProgressReporter::Reporter(); - auto observer = - std::thread([reporter, &done, &cv]() { reporter(&done, &cv); }); - - // do the update - bool failed{false}; - { - TaskSystem ts{arguments.common.jobs}; - git_update_map.ConsumeAfterKeysReady( - &ts, - repos_to_update, - [&mr_config, - repos_to_update_names = - arguments.update.repos_to_update](auto const& values) { - for (auto const& repo_name : repos_to_update_names) { - auto i = static_cast<size_t>( - &repo_name - &repos_to_update_names[0]); // get index - mr_config["repositories"][repo_name]["repository"] - ["commit"] = *values[i]; - } - }, - [&failed](auto const& msg, bool fatal) { - Logger::Log(fatal ? LogLevel::Error : LogLevel::Warning, - "While performing just-mr update:\n{}", - msg); - failed = failed or fatal; - }); - } - - // close progress observer - done = true; - cv.notify_all(); - observer.join(); - - if (failed) { - return kExitUpdateError; - } - // report success - Logger::Log(LogLevel::Info, "Update completed"); - // print mr_config to stdout - std::cout << mr_config.dump(2) << std::endl; - return kExitSuccess; -} - -[[nodiscard]] auto MultiRepoSetup(std::shared_ptr<Configuration> const& config, - CommandLineArguments const& arguments, - bool interactive) - -> std::optional<std::filesystem::path> { - // provide report - Logger::Log(LogLevel::Info, "Performing repositories setup"); - // set anchor dir to setup_root; current dir will be reverted when anchor - // goes out of scope - auto cwd_anchor = FileSystemManager::ChangeDirectory( - arguments.common.just_mr_paths->setup_root); - - auto repos = (*config)["repositories"]; - auto setup_repos = - std::make_shared<SetupRepos>(); // repos to setup and include - nlohmann::json mr_config{}; // output config to populate - - auto main = - arguments.common.main; // get local copy of updated clarg 'main', as - // it might be updated again from config - - // check if config provides main repo name - if (not main) { - auto main_from_config = (*config)["main"]; - if (main_from_config.IsNotNull()) { - if (main_from_config->IsString()) { - main = main_from_config->String(); - } - else { - Logger::Log( - LogLevel::Error, - "Unsupported value {} for field \"main\" in configuration.", - main_from_config->ToString()); - } - } - } - // pass on main that was explicitly set via command line or config - if (main) { - mr_config["main"] = *main; - } - // get default repos to setup and to include - DefaultReachableRepositories(repos, setup_repos); - // check if main is to be taken as first repo name lexicographically - if (not main and not setup_repos->to_setup.empty()) { - main = *std::min_element(setup_repos->to_setup.begin(), - setup_repos->to_setup.end()); - } - // final check on which repos are to be set up - if (main and not arguments.setup.sub_all) { - ReachableRepositories(repos, *main, setup_repos); - } - - // setup the required async maps - auto crit_git_op_ptr = std::make_shared<CriticalGitOpGuard>(); - auto critical_git_op_map = CreateCriticalGitOpMap(crit_git_op_ptr); - auto content_cas_map = CreateContentCASMap(arguments.common.just_mr_paths, - arguments.common.ca_info, - arguments.common.jobs); - auto import_to_git_map = - CreateImportToGitMap(&critical_git_op_map, - arguments.common.git_path->string(), - *arguments.common.local_launcher, - arguments.common.jobs); - auto resolve_symlinks_map = CreateResolveSymlinksMap(); - - auto commit_git_map = - CreateCommitGitMap(&critical_git_op_map, - arguments.common.just_mr_paths, - arguments.common.git_path->string(), - *arguments.common.local_launcher, - arguments.common.jobs); - auto content_git_map = CreateContentGitMap(&content_cas_map, - &import_to_git_map, - &resolve_symlinks_map, - &critical_git_op_map, - arguments.common.jobs); - auto fpath_git_map = CreateFilePathGitMap(arguments.just_cmd.subcmd_name, - &critical_git_op_map, - &import_to_git_map, - &resolve_symlinks_map, - arguments.common.jobs); - auto distdir_git_map = CreateDistdirGitMap(&content_cas_map, - &import_to_git_map, - &critical_git_op_map, - arguments.common.jobs); - auto tree_id_git_map = - CreateTreeIdGitMap(&critical_git_op_map, - arguments.common.git_path->string(), - *arguments.common.local_launcher, - arguments.common.jobs); - auto repos_to_setup_map = CreateReposToSetupMap(config, - main, - interactive, - &commit_git_map, - &content_git_map, - &fpath_git_map, - &distdir_git_map, - &tree_id_git_map, - arguments.common.jobs); - - // set up progress observer - Logger::Log(LogLevel::Info, - "Found {} repositories to set up", - setup_repos->to_setup.size()); - JustMRProgress::Instance().SetTotal(setup_repos->to_setup.size()); - std::atomic<bool> done{false}; - std::condition_variable cv{}; - auto reporter = JustMRProgressReporter::Reporter(); - auto observer = - std::thread([reporter, &done, &cv]() { reporter(&done, &cv); }); - - // Populate workspace_root and TAKE_OVER fields - bool failed{false}; - { - TaskSystem ts{arguments.common.jobs}; - repos_to_setup_map.ConsumeAfterKeysReady( - &ts, - setup_repos->to_setup, - [&mr_config, config, setup_repos, main, interactive]( - auto const& values) { - nlohmann::json mr_repos{}; - for (auto const& repo : setup_repos->to_setup) { - auto i = static_cast<size_t>( - &repo - &setup_repos->to_setup[0]); // get index - mr_repos[repo] = *values[i]; - } - // populate ALT_DIRS - auto repos = (*config)["repositories"]; - if (repos.IsNotNull()) { - for (auto const& repo : setup_repos->to_include) { - auto desc = repos->Get(repo, Expression::none_t{}); - if (desc.IsNotNull()) { - if (not((main and (repo == *main)) and - interactive)) { - for (auto const& key : kAltDirs) { - auto val = - desc->Get(key, Expression::none_t{}); - if (val.IsNotNull() and - not((main and val->IsString() and - (val->String() == *main)) and - interactive)) { - mr_repos[repo][key] = - mr_repos[val->String()] - ["workspace_root"]; - } - } - } - } - } - } - // retain only the repos we need - for (auto const& repo : setup_repos->to_include) { - mr_config["repositories"][repo] = mr_repos[repo]; - } - }, - [&failed, interactive](auto const& msg, bool fatal) { - Logger::Log(fatal ? LogLevel::Error : LogLevel::Warning, - "While performing just-mr {}:\n{}", - interactive ? "setup-env" : "setup", - msg); - failed = failed or fatal; - }); - } - - // close progress observer - done = true; - cv.notify_all(); - observer.join(); - - if (failed) { - return std::nullopt; - } - // if successful, return the output config - return JustMR::Utils::AddToCAS(mr_config.dump(2)); -} - -/// \brief Runs execvp for given command. Only returns if execvp fails. -[[nodiscard]] auto CallJust( - std::optional<std::filesystem::path> const& config_file, - CommandLineArguments const& arguments, - bool forward_build_root) -> int { - // check if subcmd_name can be taken from additional args - auto additional_args_offset = 0U; - auto subcommand = arguments.just_cmd.subcmd_name; - if (not subcommand and - not arguments.just_cmd.additional_just_args.empty()) { - subcommand = arguments.just_cmd.additional_just_args[0]; - additional_args_offset++; - } - - bool use_config{false}; - bool use_build_root{false}; - bool use_launcher{false}; - bool supports_defines{false}; - std::optional<std::filesystem::path> mr_config_path{std::nullopt}; - - std::optional<LockFile> lock{}; - if (subcommand and kKnownJustSubcommands.contains(*subcommand)) { - // Read the config file if needed - if (kKnownJustSubcommands.at(*subcommand).config) { - lock = GarbageCollector::SharedLock(); - if (not lock) { - return kExitGenericFailure; - } - auto config = ReadConfiguration(config_file); - - use_config = true; - mr_config_path = - MultiRepoSetup(config, arguments, /*interactive=*/false); - if (not mr_config_path) { - Logger::Log(LogLevel::Error, - "Failed to setup config while calling \"just {}\"", - *subcommand); - return kExitSetupError; - } - } - use_build_root = kKnownJustSubcommands.at(*subcommand).build_root; - use_launcher = kKnownJustSubcommands.at(*subcommand).launch; - supports_defines = kKnownJustSubcommands.at(*subcommand).defines; - } - // build just command - std::vector<std::string> cmd = {arguments.common.just_path->string()}; - if (subcommand) { - cmd.emplace_back(*subcommand); - } - if (use_config) { - cmd.emplace_back("-C"); - cmd.emplace_back(mr_config_path->string()); - } - if (use_build_root and forward_build_root) { - cmd.emplace_back("--local-build-root"); - cmd.emplace_back(*arguments.common.just_mr_paths->root); - } - if (use_launcher and arguments.common.local_launcher and - (arguments.common.local_launcher != kDefaultLauncher)) { - cmd.emplace_back("--local-launcher"); - cmd.emplace_back( - nlohmann::json(*arguments.common.local_launcher).dump()); - } - // forward logging arguments - if (not arguments.log.log_files.empty()) { - cmd.emplace_back("--log-append"); - for (auto const& log_file : arguments.log.log_files) { - cmd.emplace_back("-f"); - cmd.emplace_back(log_file.string()); - } - } - if (arguments.log.log_limit and - *arguments.log.log_limit != kDefaultLogLevel) { - cmd.emplace_back("--log-limit"); - cmd.emplace_back( - std::to_string(static_cast<std::underlying_type<LogLevel>::type>( - *arguments.log.log_limit))); - } - if (arguments.log.plain_log) { - cmd.emplace_back("--plain-log"); - } - if (supports_defines) { - auto overlay_config = Configuration(); - for (auto const& s : arguments.common.defines) { - try { - auto map = Expression::FromJson(nlohmann::json::parse(s)); - if (not map->IsMap()) { - Logger::Log(LogLevel::Error, - "Defines entry {} does not contain a map.", - nlohmann::json(s).dump()); - std::exit(kExitClargsError); - } - overlay_config = overlay_config.Update(map); - } catch (std::exception const& e) { - Logger::Log(LogLevel::Error, - "Parsing defines entry {} failed with error:\n{}", - nlohmann::json(s).dump(), - e.what()); - std::exit(kExitClargsError); - } - } - if (not overlay_config.Expr()->Map().empty()) { - cmd.emplace_back("-D"); - cmd.emplace_back(overlay_config.ToString()); - } - } - // add args read from just-mrrc - if (subcommand and arguments.just_cmd.just_args.contains(*subcommand)) { - for (auto const& subcmd_arg : - arguments.just_cmd.just_args.at(*subcommand)) { - cmd.emplace_back(subcmd_arg); - } - } - // add (remaining) args given by user as clargs - for (auto it = arguments.just_cmd.additional_just_args.begin() + - additional_args_offset; - it != arguments.just_cmd.additional_just_args.end(); - ++it) { - cmd.emplace_back(*it); - } - - Logger::Log( - LogLevel::Info, "Setup finished, exec {}", nlohmann::json(cmd).dump()); - - // create argv - std::vector<char*> argv{}; - std::transform(std::begin(cmd), - std::end(cmd), - std::back_inserter(argv), - [](auto& str) { return str.data(); }); - argv.push_back(nullptr); - // run execvp; will only return if failure - [[maybe_unused]] auto res = - execvp(argv[0], static_cast<char* const*>(argv.data())); - // execvp returns only if command errored out - Logger::Log(LogLevel::Error, "execvp failed with error code {}", errno); - return kExitExecError; -} - } // namespace auto main(int argc, char* argv[]) -> int { @@ -1530,7 +625,12 @@ auto main(int argc, char* argv[]) -> int { // Run subcommands known to just and `do` if (arguments.cmd == SubCommand::kJustDo or arguments.cmd == SubCommand::kJustSubCmd) { - return CallJust(config_file, arguments, forward_build_root); + return CallJust(config_file, + arguments.common, + arguments.setup, + arguments.just_cmd, + arguments.log, + forward_build_root); } auto lock = GarbageCollector::SharedLock(); if (not lock) { @@ -1538,14 +638,16 @@ auto main(int argc, char* argv[]) -> int { } // The remaining options all need the config file - auto config = ReadConfiguration(config_file); + auto config = JustMR::Utils::ReadConfiguration(config_file); // Run subcommand `setup` or `setup-env` if (arguments.cmd == SubCommand::kSetup or arguments.cmd == SubCommand::kSetupEnv) { auto mr_config_path = MultiRepoSetup( config, - arguments, + arguments.common, + arguments.setup, + arguments.just_cmd, /*interactive=*/(arguments.cmd == SubCommand::kSetupEnv)); // dump resulting config to stdout if (not mr_config_path) { @@ -1560,12 +662,13 @@ auto main(int argc, char* argv[]) -> int { // Run subcommand `update` if (arguments.cmd == SubCommand::kUpdate) { - return MultiRepoUpdate(config, arguments); + return MultiRepoUpdate(config, arguments.common, arguments.update); } // Run subcommand `fetch` if (arguments.cmd == SubCommand::kFetch) { - return MultiRepoFetch(config, arguments); + return MultiRepoFetch( + config, arguments.common, arguments.setup, arguments.fetch); } // Unknown subcommand should fail diff --git a/src/other_tools/just_mr/setup.cpp b/src/other_tools/just_mr/setup.cpp new file mode 100644 index 00000000..669e9e85 --- /dev/null +++ b/src/other_tools/just_mr/setup.cpp @@ -0,0 +1,206 @@ +// Copyright 2023 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/just_mr/setup.hpp" + +#include <filesystem> + +#include "nlohmann/json.hpp" +#include "src/buildtool/logging/log_level.hpp" +#include "src/buildtool/logging/logger.hpp" +#include "src/buildtool/multithreading/task_system.hpp" +#include "src/other_tools/just_mr/exit_codes.hpp" +#include "src/other_tools/just_mr/progress_reporting/progress.hpp" +#include "src/other_tools/just_mr/progress_reporting/progress_reporter.hpp" +#include "src/other_tools/just_mr/utils.hpp" +#include "src/other_tools/ops_maps/critical_git_op_map.hpp" +#include "src/other_tools/repo_map/repos_to_setup_map.hpp" +#include "src/other_tools/root_maps/commit_git_map.hpp" +#include "src/other_tools/root_maps/content_git_map.hpp" +#include "src/other_tools/root_maps/distdir_git_map.hpp" +#include "src/other_tools/root_maps/fpath_git_map.hpp" +#include "src/other_tools/root_maps/tree_id_git_map.hpp" +#include "src/other_tools/symlinks_map/resolve_symlinks_map.hpp" + +auto MultiRepoSetup(std::shared_ptr<Configuration> const& config, + MultiRepoCommonArguments const& common_args, + MultiRepoSetupArguments const& setup_args, + MultiRepoJustSubCmdsArguments const& just_cmd_args, + bool interactive) -> std::optional<std::filesystem::path> { + // provide report + Logger::Log(LogLevel::Info, "Performing repositories setup"); + // set anchor dir to setup_root; current dir will be reverted when anchor + // goes out of scope + auto cwd_anchor = FileSystemManager::ChangeDirectory( + common_args.just_mr_paths->setup_root); + + auto repos = (*config)["repositories"]; + auto setup_repos = + std::make_shared<JustMR::SetupRepos>(); // repos to setup and include + nlohmann::json mr_config{}; // output config to populate + + auto main = common_args.main; // get local copy of updated clarg 'main', as + // it might be updated again from config + + // check if config provides main repo name + if (not main) { + auto main_from_config = (*config)["main"]; + if (main_from_config.IsNotNull()) { + if (main_from_config->IsString()) { + main = main_from_config->String(); + } + else { + Logger::Log( + LogLevel::Error, + "Unsupported value {} for field \"main\" in configuration.", + main_from_config->ToString()); + } + } + } + // pass on main that was explicitly set via command line or config + if (main) { + mr_config["main"] = *main; + } + // get default repos to setup and to include + JustMR::Utils::DefaultReachableRepositories(repos, setup_repos); + // check if main is to be taken as first repo name lexicographically + if (not main and not setup_repos->to_setup.empty()) { + main = *std::min_element(setup_repos->to_setup.begin(), + setup_repos->to_setup.end()); + } + // final check on which repos are to be set up + if (main and not setup_args.sub_all) { + JustMR::Utils::ReachableRepositories(repos, *main, setup_repos); + } + + // setup the required async maps + auto crit_git_op_ptr = std::make_shared<CriticalGitOpGuard>(); + auto critical_git_op_map = CreateCriticalGitOpMap(crit_git_op_ptr); + auto content_cas_map = CreateContentCASMap( + common_args.just_mr_paths, common_args.ca_info, common_args.jobs); + auto import_to_git_map = + CreateImportToGitMap(&critical_git_op_map, + common_args.git_path->string(), + *common_args.local_launcher, + common_args.jobs); + auto resolve_symlinks_map = CreateResolveSymlinksMap(); + + auto commit_git_map = CreateCommitGitMap(&critical_git_op_map, + common_args.just_mr_paths, + common_args.git_path->string(), + *common_args.local_launcher, + common_args.jobs); + auto content_git_map = CreateContentGitMap(&content_cas_map, + &import_to_git_map, + &resolve_symlinks_map, + &critical_git_op_map, + common_args.jobs); + auto fpath_git_map = CreateFilePathGitMap(just_cmd_args.subcmd_name, + &critical_git_op_map, + &import_to_git_map, + &resolve_symlinks_map, + common_args.jobs); + auto distdir_git_map = CreateDistdirGitMap(&content_cas_map, + &import_to_git_map, + &critical_git_op_map, + common_args.jobs); + auto tree_id_git_map = CreateTreeIdGitMap(&critical_git_op_map, + common_args.git_path->string(), + *common_args.local_launcher, + common_args.jobs); + auto repos_to_setup_map = CreateReposToSetupMap(config, + main, + interactive, + &commit_git_map, + &content_git_map, + &fpath_git_map, + &distdir_git_map, + &tree_id_git_map, + common_args.jobs); + + // set up progress observer + Logger::Log(LogLevel::Info, + "Found {} repositories to set up", + setup_repos->to_setup.size()); + JustMRProgress::Instance().SetTotal(setup_repos->to_setup.size()); + std::atomic<bool> done{false}; + std::condition_variable cv{}; + auto reporter = JustMRProgressReporter::Reporter(); + auto observer = + std::thread([reporter, &done, &cv]() { reporter(&done, &cv); }); + + // Populate workspace_root and TAKE_OVER fields + bool failed{false}; + { + TaskSystem ts{common_args.jobs}; + repos_to_setup_map.ConsumeAfterKeysReady( + &ts, + setup_repos->to_setup, + [&mr_config, config, setup_repos, main, interactive]( + auto const& values) { + nlohmann::json mr_repos{}; + for (auto const& repo : setup_repos->to_setup) { + auto i = static_cast<size_t>( + &repo - &setup_repos->to_setup[0]); // get index + mr_repos[repo] = *values[i]; + } + // populate ALT_DIRS + auto repos = (*config)["repositories"]; + if (repos.IsNotNull()) { + for (auto const& repo : setup_repos->to_include) { + auto desc = repos->Get(repo, Expression::none_t{}); + if (desc.IsNotNull()) { + if (not((main and (repo == *main)) and + interactive)) { + for (auto const& key : kAltDirs) { + auto val = + desc->Get(key, Expression::none_t{}); + if (val.IsNotNull() and + not((main and val->IsString() and + (val->String() == *main)) and + interactive)) { + mr_repos[repo][key] = + mr_repos[val->String()] + ["workspace_root"]; + } + } + } + } + } + } + // retain only the repos we need + for (auto const& repo : setup_repos->to_include) { + mr_config["repositories"][repo] = mr_repos[repo]; + } + }, + [&failed, interactive](auto const& msg, bool fatal) { + Logger::Log(fatal ? LogLevel::Error : LogLevel::Warning, + "While performing just-mr {}:\n{}", + interactive ? "setup-env" : "setup", + msg); + failed = failed or fatal; + }); + } + + // close progress observer + done = true; + cv.notify_all(); + observer.join(); + + if (failed) { + return std::nullopt; + } + // if successful, return the output config + return JustMR::Utils::AddToCAS(mr_config.dump(2)); +} diff --git a/src/other_tools/just_mr/setup.hpp b/src/other_tools/just_mr/setup.hpp new file mode 100644 index 00000000..da452fcc --- /dev/null +++ b/src/other_tools/just_mr/setup.hpp @@ -0,0 +1,31 @@ +// Copyright 2023 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_JUST_MR_SETUP_HPP +#define INCLUDED_SRC_OTHER_TOOLS_JUST_MR_SETUP_HPP + +#include <filesystem> + +#include "src/buildtool/build_engine/expression/configuration.hpp" +#include "src/other_tools/just_mr/cli.hpp" + +/// \brief Setup for a multi-repository build. +[[nodiscard]] auto MultiRepoSetup( + std::shared_ptr<Configuration> const& config, + MultiRepoCommonArguments const& common_args, + MultiRepoSetupArguments const& setup_args, + MultiRepoJustSubCmdsArguments const& just_cmd_args, + bool interactive) -> std::optional<std::filesystem::path>; + +#endif // INCLUDED_SRC_OTHER_TOOLS_JUST_MR_SETUP_HPP diff --git a/src/other_tools/just_mr/update.cpp b/src/other_tools/just_mr/update.cpp new file mode 100644 index 00000000..435827d5 --- /dev/null +++ b/src/other_tools/just_mr/update.cpp @@ -0,0 +1,226 @@ +// Copyright 2023 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/just_mr/update.hpp" + +#include <filesystem> + +#include "nlohmann/json.hpp" +#include "src/buildtool/logging/log_level.hpp" +#include "src/buildtool/logging/logger.hpp" +#include "src/buildtool/multithreading/task_system.hpp" +#include "src/other_tools/git_operations/git_repo_remote.hpp" +#include "src/other_tools/just_mr/exit_codes.hpp" +#include "src/other_tools/just_mr/progress_reporting/progress.hpp" +#include "src/other_tools/just_mr/progress_reporting/progress_reporter.hpp" +#include "src/other_tools/just_mr/utils.hpp" +#include "src/other_tools/ops_maps/git_update_map.hpp" + +auto MultiRepoUpdate(std::shared_ptr<Configuration> const& config, + MultiRepoCommonArguments const& common_args, + MultiRepoUpdateArguments const& update_args) -> int { + // provide report + Logger::Log(LogLevel::Info, "Performing repositories update"); + + // Check trivial case + if (update_args.repos_to_update.empty()) { + // report success + Logger::Log(LogLevel::Info, "No update needed"); + // print config file + std::cout << config->ToJson().dump(2) << std::endl; + return kExitSuccess; + } + auto repos = (*config)["repositories"]; + if (not repos.IsNotNull()) { + Logger::Log(LogLevel::Error, + "Config: Mandatory key \"repositories\" missing"); + return kExitUpdateError; + } + // gather repos to update + std::vector<std::pair<std::string, std::string>> repos_to_update{}; + repos_to_update.reserve(update_args.repos_to_update.size()); + for (auto const& repo_name : update_args.repos_to_update) { + auto repo_desc_parent = repos->At(repo_name); + if (not repo_desc_parent) { + Logger::Log(LogLevel::Error, + "Config: Missing config entry for repository {}", + nlohmann::json(repo_name).dump()); + return kExitUpdateError; + } + auto repo_desc = repo_desc_parent->get()->At("repository"); + if (repo_desc) { + auto resolved_repo_desc = + JustMR::Utils::ResolveRepo(repo_desc->get(), repos); + if (not resolved_repo_desc) { + Logger::Log(LogLevel::Error, + fmt::format("Config: Found cyclic dependency for " + "repository {}", + nlohmann::json(repo_name).dump())); + return kExitUpdateError; + } + // get repo_type + auto repo_type = (*resolved_repo_desc)->At("type"); + if (not repo_type) { + Logger::Log( + LogLevel::Error, + "Config: Mandatory key \"type\" missing for repository {}", + nlohmann::json(repo_name).dump()); + return kExitUpdateError; + } + if (not repo_type->get()->IsString()) { + Logger::Log(LogLevel::Error, + "Config: Unsupported value {} for key \"type\" for " + "repository {}", + repo_type->get()->ToString(), + nlohmann::json(repo_name).dump()); + return kExitUpdateError; + } + auto repo_type_str = repo_type->get()->String(); + if (not kCheckoutTypeMap.contains(repo_type_str)) { + Logger::Log(LogLevel::Error, + "Unknown repository type {} for {}", + nlohmann::json(repo_type_str).dump(), + nlohmann::json(repo_name).dump()); + return kExitUpdateError; + } + // only do work if repo is git type + if (kCheckoutTypeMap.at(repo_type_str) == CheckoutType::Git) { + auto repo_desc_repository = + (*resolved_repo_desc)->At("repository"); + if (not repo_desc_repository) { + Logger::Log( + LogLevel::Error, + "Config: Mandatory field \"repository\" is missing"); + return kExitUpdateError; + } + if (not repo_desc_repository->get()->IsString()) { + Logger::Log(LogLevel::Error, + "Config: Unsupported value {} for key " + "\"repository\" for repository {}", + repo_desc_repository->get()->ToString(), + nlohmann::json(repo_name).dump()); + return kExitUpdateError; + } + auto repo_desc_branch = (*resolved_repo_desc)->At("branch"); + if (not repo_desc_branch) { + Logger::Log( + LogLevel::Error, + "Config: Mandatory field \"branch\" is missing"); + return kExitUpdateError; + } + if (not repo_desc_branch->get()->IsString()) { + Logger::Log(LogLevel::Error, + "Config: Unsupported value {} for key " + "\"branch\" for repository {}", + repo_desc_branch->get()->ToString(), + nlohmann::json(repo_name).dump()); + return kExitUpdateError; + } + repos_to_update.emplace_back( + std::make_pair(repo_desc_repository->get()->String(), + repo_desc_branch->get()->String())); + } + else { + Logger::Log(LogLevel::Error, + "Config: Argument {} is not the name of a \"git\" " + "type repository", + nlohmann::json(repo_name).dump()); + return kExitUpdateError; + } + } + else { + Logger::Log(LogLevel::Error, + "Config: Missing repository description for {}", + nlohmann::json(repo_name).dump()); + return kExitUpdateError; + } + } + // Create fake repo for the anonymous remotes + auto tmp_dir = JustMR::Utils::CreateTypedTmpDir("update"); + if (not tmp_dir) { + Logger::Log(LogLevel::Error, "Failed to create commit update tmp dir"); + return kExitUpdateError; + } + // Init and open git repo + auto git_repo = + GitRepoRemote::InitAndOpen(tmp_dir->GetPath(), /*is_bare=*/true); + if (not git_repo) { + Logger::Log(LogLevel::Error, + "Failed to initialize repository in tmp dir {} for git " + "commit update", + tmp_dir->GetPath().string()); + return kExitUpdateError; + } + + // report progress + auto nr = repos_to_update.size(); + Logger::Log(LogLevel::Info, + "Discovered {} Git {} to update", + nr, + nr == 1 ? "repository" : "repositories"); + + // Initialize resulting config to be updated + auto mr_config = config->ToJson(); + // Create async map + auto git_update_map = CreateGitUpdateMap(git_repo->GetGitCAS(), + common_args.git_path->string(), + *common_args.local_launcher, + common_args.jobs); + + // set up progress observer + JustMRProgress::Instance().SetTotal(repos_to_update.size()); + std::atomic<bool> done{false}; + std::condition_variable cv{}; + auto reporter = JustMRProgressReporter::Reporter(); + auto observer = + std::thread([reporter, &done, &cv]() { reporter(&done, &cv); }); + + // do the update + bool failed{false}; + { + TaskSystem ts{common_args.jobs}; + git_update_map.ConsumeAfterKeysReady( + &ts, + repos_to_update, + [&mr_config, repos_to_update_names = update_args.repos_to_update]( + auto const& values) { + for (auto const& repo_name : repos_to_update_names) { + auto i = static_cast<size_t>( + &repo_name - &repos_to_update_names[0]); // get index + mr_config["repositories"][repo_name]["repository"] + ["commit"] = *values[i]; + } + }, + [&failed](auto const& msg, bool fatal) { + Logger::Log(fatal ? LogLevel::Error : LogLevel::Warning, + "While performing just-mr update:\n{}", + msg); + failed = failed or fatal; + }); + } + + // close progress observer + done = true; + cv.notify_all(); + observer.join(); + + if (failed) { + return kExitUpdateError; + } + // report success + Logger::Log(LogLevel::Info, "Update completed"); + // print mr_config to stdout + std::cout << mr_config.dump(2) << std::endl; + return kExitSuccess; +} diff --git a/src/other_tools/just_mr/update.hpp b/src/other_tools/just_mr/update.hpp new file mode 100644 index 00000000..4e9d5985 --- /dev/null +++ b/src/other_tools/just_mr/update.hpp @@ -0,0 +1,27 @@ +// Copyright 2023 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_JUST_MR_UPDATE_HPP +#define INCLUDED_SRC_OTHER_TOOLS_JUST_MR_UPDATE_HPP + +#include "src/buildtool/build_engine/expression/configuration.hpp" +#include "src/other_tools/just_mr/cli.hpp" + +/// \brief Update of Git repos commit information for a multi-repository build. +[[nodiscard]] auto MultiRepoUpdate(std::shared_ptr<Configuration> const& config, + MultiRepoCommonArguments const& common_args, + MultiRepoUpdateArguments const& update_args) + -> int; + +#endif // INCLUDED_SRC_OTHER_TOOLS_JUST_MR_UPDATE_HPP diff --git a/src/other_tools/just_mr/utils.cpp b/src/other_tools/just_mr/utils.cpp index 57ef6830..6bce59a9 100644 --- a/src/other_tools/just_mr/utils.cpp +++ b/src/other_tools/just_mr/utils.cpp @@ -16,6 +16,7 @@ #include "src/buildtool/file_system/file_storage.hpp" #include "src/buildtool/storage/storage.hpp" +#include "src/other_tools/just_mr/exit_codes.hpp" #include "src/utils/cpp/path.hpp" namespace JustMR::Utils { @@ -136,4 +137,114 @@ auto ResolveRepo(ExpressionPtr const& repo_desc, } } +void ReachableRepositories( + ExpressionPtr const& repos, + std::string const& main, + std::shared_ptr<JustMR::SetupRepos> const& setup_repos) { + // use temporary sets to avoid duplicates + std::unordered_set<std::string> include_repos_set{}; + if (repos->IsMap()) { + // traversal of bindings + std::function<void(std::string const&)> traverse = + [&](std::string const& repo_name) { + if (not include_repos_set.contains(repo_name)) { + // if not found, add it and repeat for its bindings + include_repos_set.insert(repo_name); + // check bindings + auto repos_repo_name = + repos->Get(repo_name, Expression::none_t{}); + if (not repos_repo_name.IsNotNull()) { + return; + } + auto bindings = + repos_repo_name->Get("bindings", Expression::none_t{}); + if (bindings.IsNotNull() and bindings->IsMap()) { + for (auto const& bound : bindings->Map().Values()) { + if (bound.IsNotNull() and bound->IsString()) { + traverse(bound->String()); + } + } + } + } + }; + traverse(main); // traverse all bindings of main repository + + // Add overlay repositories + std::unordered_set<std::string> setup_repos_set{include_repos_set}; + for (auto const& repo : include_repos_set) { + auto repos_repo = repos->Get(repo, Expression::none_t{}); + if (repos_repo.IsNotNull()) { + // copy over any present alternative root dirs + for (auto const& layer : kAltDirs) { + auto layer_val = + repos_repo->Get(layer, Expression::none_t{}); + if (layer_val.IsNotNull() and layer_val->IsString()) { + auto repo_name = layer_val->String(); + setup_repos_set.insert(repo_name); + } + } + } + } + + // copy to vectors + setup_repos->to_setup.clear(); + setup_repos->to_setup.reserve(setup_repos_set.size()); + std::copy( + setup_repos_set.begin(), + setup_repos_set.end(), + std::inserter(setup_repos->to_setup, setup_repos->to_setup.end())); + setup_repos->to_include.clear(); + setup_repos->to_include.reserve(include_repos_set.size()); + std::copy(include_repos_set.begin(), + include_repos_set.end(), + std::inserter(setup_repos->to_include, + setup_repos->to_include.end())); + } +} + +void DefaultReachableRepositories( + ExpressionPtr const& repos, + std::shared_ptr<JustMR::SetupRepos> const& setup_repos) { + if (repos.IsNotNull() and repos->IsMap()) { + setup_repos->to_setup = repos->Map().Keys(); + setup_repos->to_include = setup_repos->to_setup; + } +} + +auto ReadConfiguration( + std::optional<std::filesystem::path> const& config_file_opt) noexcept + -> std::shared_ptr<Configuration> { + if (not config_file_opt) { + Logger::Log(LogLevel::Error, "Cannot find repository configuration."); + std::exit(kExitConfigError); + } + auto const& config_file = *config_file_opt; + + std::shared_ptr<Configuration> config{nullptr}; + if (not FileSystemManager::IsFile(config_file)) { + Logger::Log(LogLevel::Error, + "Cannot read config file {}.", + config_file.string()); + std::exit(kExitConfigError); + } + try { + std::ifstream fs(config_file); + auto map = Expression::FromJson(nlohmann::json::parse(fs)); + if (not map->IsMap()) { + Logger::Log(LogLevel::Error, + "Config file {} does not contain a JSON object.", + config_file.string()); + std::exit(kExitConfigError); + } + config = std::make_shared<Configuration>(map); + } catch (std::exception const& e) { + Logger::Log(LogLevel::Error, + "Parsing config file {} failed with error:\n{}", + config_file.string(), + e.what()); + std::exit(kExitConfigError); + } + return config; +} + } // namespace JustMR::Utils diff --git a/src/other_tools/just_mr/utils.hpp b/src/other_tools/just_mr/utils.hpp index 92d679e2..9d6f3b7f 100644 --- a/src/other_tools/just_mr/utils.hpp +++ b/src/other_tools/just_mr/utils.hpp @@ -151,6 +151,11 @@ struct CAInfo { using PathsPtr = std::shared_ptr<JustMR::Paths>; using CAInfoPtr = std::shared_ptr<JustMR::CAInfo>; +struct SetupRepos { + std::vector<std::string> to_setup; + std::vector<std::string> to_include; +}; + namespace Utils { /// \brief Get location of Git repository. Defaults to the Git cache root when @@ -208,6 +213,22 @@ auto ResolveRepo(ExpressionPtr const& repo_desc, ExpressionPtr const& repos) noexcept -> std::optional<ExpressionPtr>; +/// \brief Get the repo dependency closure for a given main repository. +void ReachableRepositories( + ExpressionPtr const& repos, + std::string const& main, + std::shared_ptr<JustMR::SetupRepos> const& setup_repos); + +/// \brief By default, we set up and include the full repo dependency closure. +void DefaultReachableRepositories( + ExpressionPtr const& repos, + std::shared_ptr<JustMR::SetupRepos> const& setup_repos); + +/// \brief Read in a just-mr configuration file. +[[nodiscard]] auto ReadConfiguration( + std::optional<std::filesystem::path> const& config_file_opt) noexcept + -> std::shared_ptr<Configuration>; + } // namespace Utils } // namespace JustMR |