summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Cristian Sarbu <paul.cristian.sarbu@huawei.com>2023-08-23 15:07:11 +0200
committerPaul Cristian Sarbu <paul.cristian.sarbu@huawei.com>2023-08-23 15:23:20 +0200
commit9fab20246c23193b1a68387cf0b5f63236639a58 (patch)
treebcf2fc7275fb395e44c3bb10b27c84c128549aa8
parent796d9a27daba4dd16f31a7b0237fff00218d754b (diff)
downloadjustbuild-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/TARGETS99
-rw-r--r--src/other_tools/just_mr/fetch.cpp300
-rw-r--r--src/other_tools/just_mr/fetch.hpp28
-rw-r--r--src/other_tools/just_mr/launch.cpp168
-rw-r--r--src/other_tools/just_mr/launch.hpp31
-rw-r--r--src/other_tools/just_mr/main.cpp941
-rw-r--r--src/other_tools/just_mr/setup.cpp206
-rw-r--r--src/other_tools/just_mr/setup.hpp31
-rw-r--r--src/other_tools/just_mr/update.cpp226
-rw-r--r--src/other_tools/just_mr/update.hpp27
-rw-r--r--src/other_tools/just_mr/utils.cpp111
-rw-r--r--src/other_tools/just_mr/utils.hpp21
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