diff options
author | Oliver Reiche <oliver.reiche@huawei.com> | 2025-06-23 14:56:04 +0200 |
---|---|---|
committer | Oliver Reiche <oliver.reiche@huawei.com> | 2025-06-24 14:56:58 +0200 |
commit | bbcc5977f49646941ac35060bb74a27eda5fbd76 (patch) | |
tree | 4e11ded4722ea740fbeea12510efd34fe698c04b | |
parent | c498bf564fa5d781c176f65c7a9a2d43376a81f1 (diff) | |
download | justbuild-bbcc5977f49646941ac35060bb74a27eda5fbd76.tar.gz |
ExecutionAPI: Support output_paths in requests
... and prepare local execution for clients
using only RBEv2.1 (setting only output_paths).
14 files changed, 351 insertions, 49 deletions
diff --git a/src/buildtool/execution_api/bazel_msg/bazel_msg_factory.cpp b/src/buildtool/execution_api/bazel_msg/bazel_msg_factory.cpp index 68da1b87..5e6e6a45 100644 --- a/src/buildtool/execution_api/bazel_msg/bazel_msg_factory.cpp +++ b/src/buildtool/execution_api/bazel_msg/bazel_msg_factory.cpp @@ -189,12 +189,21 @@ struct DirectoryNodeBundle final { std::copy(request.command_line->begin(), request.command_line->end(), pb::back_inserter(msg.mutable_arguments())); + + // DEPRECATED as of v2.1: use output_paths instead, combining + // output_files and output_dirs. std::copy(request.output_files->begin(), request.output_files->end(), pb::back_inserter(msg.mutable_output_files())); std::copy(request.output_dirs->begin(), request.output_dirs->end(), pb::back_inserter(msg.mutable_output_directories())); + + // NEW in v2.2 + std::copy(request.output_paths->begin(), + request.output_paths->end(), + pb::back_inserter(msg.mutable_output_paths())); + std::copy(request.env_vars->begin(), request.env_vars->end(), pb::back_inserter(msg.mutable_environment_variables())); diff --git a/src/buildtool/execution_api/bazel_msg/bazel_msg_factory.hpp b/src/buildtool/execution_api/bazel_msg/bazel_msg_factory.hpp index 17c08f4d..d6557ae8 100644 --- a/src/buildtool/execution_api/bazel_msg/bazel_msg_factory.hpp +++ b/src/buildtool/execution_api/bazel_msg/bazel_msg_factory.hpp @@ -219,6 +219,9 @@ struct BazelMsgFactory::ActionDigestRequest final { /// \brief The paths of output directories. VectorPtr<std::string> const output_dirs; + /// \brief Generic output paths (indicates protocol >=v2.1 should be used). + VectorPtr<std::string> const output_paths; + /// \brief The environment variables set. VectorPtr<bazel_re::Command_EnvironmentVariable> const env_vars; diff --git a/src/buildtool/execution_api/common/execution_api.hpp b/src/buildtool/execution_api/common/execution_api.hpp index 001c9189..d61a664e 100644 --- a/src/buildtool/execution_api/common/execution_api.hpp +++ b/src/buildtool/execution_api/common/execution_api.hpp @@ -45,15 +45,18 @@ class IExecutionApi { auto operator=(IExecutionApi&&) -> IExecutionApi& = default; virtual ~IExecutionApi() = default; - /// \brief Create a new action. + /// \brief Create a new action (>=RBEv2.0). /// \param[in] root_digest Digest of the build root. /// \param[in] command Command as argv vector - /// \param[in] cwd Working direcotry, relative to execution root + /// \param[in] cwd Working directory, relative to execution root /// \param[in] output_files List of paths to output files, relative to cwd /// \param[in] output_dirs List of paths to output directories. /// \param[in] env_vars The environment variables to set. /// \param[in] properties Platform properties to set. + /// \param[in] force_legacy Force use of legacy API RBEv2.0 /// \returns The new action. + /// Note that types of output files and directories are not verified. + /// NOLINTNEXTLINE(google-default-arguments) [[nodiscard]] virtual auto CreateAction( ArtifactDigest const& root_digest, std::vector<std::string> const& command, @@ -61,8 +64,8 @@ class IExecutionApi { std::vector<std::string> const& output_files, std::vector<std::string> const& output_dirs, std::map<std::string, std::string> const& env_vars, - std::map<std::string, std::string> const& properties) const noexcept - -> IExecutionAction::Ptr = 0; + std::map<std::string, std::string> const& properties, + bool force_legacy = false) const noexcept -> IExecutionAction::Ptr = 0; /// \brief Retrieve artifacts from CAS and store to specified paths. /// Tree artifacts are resolved its containing file artifacts are diff --git a/src/buildtool/execution_api/local/local_action.cpp b/src/buildtool/execution_api/local/local_action.cpp index d9733f18..8050c3d7 100644 --- a/src/buildtool/execution_api/local/local_action.cpp +++ b/src/buildtool/execution_api/local/local_action.cpp @@ -134,8 +134,11 @@ auto LocalAction::Execute(Logger const* logger) noexcept if (auto result = local_context_.storage->ActionCache().CachedResult(*action)) { if (result->exit_code() == 0 and - ActionResultContainsExpectedOutputs( - *result, output_files_, output_dirs_)) { + (mode_ == RequestMode::kV2_1 + ? ActionResultContainsExpectedOutputs(*result, + output_paths_) + : ActionResultContainsExpectedOutputs( + *result, output_files_, output_dirs_))) { return create_response( logger, action->hash(), @@ -376,6 +379,16 @@ auto LocalAction::CreateDirectoryStructure( } return true; }; + + if (mode_ == RequestMode::kV2_1) { + return std::all_of( + output_paths_.begin(), + output_paths_.end(), + [this, &exec_path, &create_dir](auto const& local_path) { + auto dir = (exec_path / cwd_ / local_path).parent_path(); + return create_dir(dir); + }); + } return std::all_of(output_files_.begin(), output_files_.end(), [this, &exec_path, &create_dir](auto const& local_path) { @@ -487,11 +500,109 @@ auto LocalAction::CollectOutputDirOrSymlink( return std::nullopt; } +auto LocalAction::CollectOutputPath( + std::filesystem::path const& exec_path, + std::string const& local_path) const noexcept -> std::optional<OutputPath> { + auto out_path = exec_path / local_path; + auto type = FileSystemManager::Type(out_path, /*allow_upwards=*/true); + if (not type) { + logger_.Emit(LogLevel::Error, "expected known type at {}", local_path); + return std::nullopt; + } + if (IsSymlinkObject(*type)) { + if (auto content = FileSystemManager::ReadSymlink(out_path)) { + // in native mode: check validity of symlink + if (ProtocolTraits::IsNative( + local_context_.storage->GetHashFunction().GetType()) and + not PathIsNonUpwards(*content)) { + logger_.Emit( + LogLevel::Error, "found invalid symlink at {}", local_path); + return std::nullopt; + } + if (local_context_.storage->CAS().StoreBlob(*content)) { + auto out_symlink = bazel_re::OutputSymlink{}; + out_symlink.set_path(local_path); + out_symlink.set_target(*content); + return out_symlink; + } + } + } + else if (IsFileObject(*type)) { + bool is_executable = IsExecutableObject(*type); + auto digest = local_context_.storage->CAS().StoreBlob</*kOwner=*/true>( + out_path, is_executable); + if (digest) { + auto out_file = bazel_re::OutputFile{}; + out_file.set_path(local_path); + *out_file.mutable_digest() = + ArtifactDigestFactory::ToBazel(*digest); + out_file.set_is_executable(is_executable); + return out_file; + } + } + else if (IsTreeObject(*type)) { + if (auto digest = CreateDigestFromLocalOwnedTree( + *local_context_.storage, out_path)) { + auto out_dir = bazel_re::OutputDirectory{}; + out_dir.set_path(local_path); + (*out_dir.mutable_tree_digest()) = + ArtifactDigestFactory::ToBazel(*digest); + return out_dir; + } + logger_.Emit(LogLevel::Error, + "found invalid entries in directory at {}", + local_path); + } + else { + logger_.Emit(LogLevel::Error, + "expected file, directory, or symlink at {}", + local_path); + } + return std::nullopt; +} + auto LocalAction::CollectAndStoreOutputs( bazel_re::ActionResult* result, std::filesystem::path const& exec_path) const noexcept -> bool { try { logger_.Emit(LogLevel::Trace, "collecting outputs:"); + if (mode_ == RequestMode::kV2_1) { + for (auto const& path : output_paths_) { + auto out = CollectOutputPath(exec_path, path); + if (not out) { + logger_.Emit(LogLevel::Error, + "could not collect output path {}", + path); + return false; + } + if (std::holds_alternative<bazel_re::OutputSymlink>(*out)) { + auto out_symlink = std::get<bazel_re::OutputSymlink>(*out); + logger_.Emit(LogLevel::Trace, + " - symlink {}: {}", + path, + out_symlink.target()); + result->mutable_output_symlinks()->Add( + std::move(out_symlink)); + } + else if (std::holds_alternative<bazel_re::OutputFile>(*out)) { + auto out_file = std::get<bazel_re::OutputFile>(*out); + auto const& digest = out_file.digest().hash(); + logger_.Emit( + LogLevel::Trace, " - file {}: {}", path, digest); + result->mutable_output_files()->Add(std::move(out_file)); + } + else { + auto out_dir = std::get<bazel_re::OutputDirectory>(*out); + auto const& digest = out_dir.tree_digest().hash(); + logger_.Emit( + LogLevel::Trace, " - dir {}: {}", path, digest); + result->mutable_output_directories()->Add( + std::move(out_dir)); + } + } + return true; + } + // if mode_ is RequestMode::kV2_0 or RequestMode::kBestEffort for (auto const& path : output_files_) { auto out = CollectOutputFileOrSymlink(exec_path, path); if (not out) { @@ -506,6 +617,9 @@ auto LocalAction::CollectAndStoreOutputs( " - symlink {}: {}", path, out_symlink.target()); + if (mode_ == RequestMode::kBestEffort) { + *result->mutable_output_symlinks()->Add() = out_symlink; + } result->mutable_output_file_symlinks()->Add( std::move(out_symlink)); } @@ -530,6 +644,9 @@ auto LocalAction::CollectAndStoreOutputs( " - symlink {}: {}", path, out_symlink.target()); + if (mode_ == RequestMode::kBestEffort) { + *result->mutable_output_symlinks()->Add() = out_symlink; + } result->mutable_output_directory_symlinks()->Add( std::move(out_symlink)); } diff --git a/src/buildtool/execution_api/local/local_action.hpp b/src/buildtool/execution_api/local/local_action.hpp index 7610def0..47f912d8 100644 --- a/src/buildtool/execution_api/local/local_action.hpp +++ b/src/buildtool/execution_api/local/local_action.hpp @@ -18,6 +18,7 @@ #include <algorithm> #include <chrono> #include <compare> +#include <cstdint> #include <filesystem> #include <functional> // IWYU pragma: keep #include <map> @@ -45,6 +46,12 @@ class LocalAction final : public IExecutionAction { friend class LocalApi; + enum class RequestMode : std::uint8_t { + kV2_0, // RBEv2.0 + kV2_1, // >=RBEv2.1 + kBestEffort // construct both, let the server pick + }; + public: struct Output { bazel_re::ActionResult action; @@ -56,6 +63,9 @@ class LocalAction final : public IExecutionAction { std::variant<bazel_re::OutputFile, bazel_re::OutputSymlink>; using OutputDirOrSymlink = std::variant<bazel_re::OutputDirectory, bazel_re::OutputSymlink>; + using OutputPath = std::variant<bazel_re::OutputFile, + bazel_re::OutputDirectory, + bazel_re::OutputSymlink>; using FileCopies = std::unordered_map<Artifact::ObjectInfo, TmpDir::Ptr>; @@ -76,31 +86,65 @@ class LocalAction final : public IExecutionAction { std::string const cwd_; std::vector<std::string> output_files_; std::vector<std::string> output_dirs_; + std::vector<std::string> output_paths_; std::map<std::string, std::string> const env_vars_; std::vector<bazel_re::Platform_Property> const properties_; std::chrono::milliseconds timeout_{kDefaultTimeout}; CacheFlag cache_flag_{CacheFlag::CacheOutput}; + RequestMode mode_{}; + + explicit LocalAction(gsl::not_null<LocalContext const*> local_context, + ArtifactDigest root_digest, + std::vector<std::string> command, + std::string cwd, + std::vector<std::string> output_files, + std::vector<std::string> output_dirs, + std::map<std::string, std::string> env_vars, + std::map<std::string, std::string> const& properties, + bool best_effort) noexcept + : local_context_{*local_context}, + root_digest_{std::move(root_digest)}, + cmdline_{std::move(command)}, + cwd_{std::move(cwd)}, + output_files_{std::move(output_files)}, + output_dirs_{std::move(output_dirs)}, + env_vars_{std::move(env_vars)}, + properties_{BazelMsgFactory::CreateMessageVectorFromMap< + bazel_re::Platform_Property>(properties)}, + mode_{best_effort ? RequestMode::kBestEffort : RequestMode::kV2_0} { + std::sort(output_files_.begin(), output_files_.end()); + std::sort(output_dirs_.begin(), output_dirs_.end()); + if (best_effort) { + output_paths_.reserve(output_files_.size() + output_dirs_.size()); + output_paths_.insert(output_paths_.end(), + output_files_.begin(), + output_files_.end()); + output_paths_.insert( + output_paths_.end(), output_dirs_.begin(), output_dirs_.end()); + std::sort(output_paths_.begin(), output_paths_.end()); + } + } + // Alternative constructor with combined output_paths for files and dirs, as + // it is used by RBEv2.1 and above. explicit LocalAction( gsl::not_null<LocalContext const*> local_context, ArtifactDigest root_digest, std::vector<std::string> command, std::string cwd, - std::vector<std::string> output_files, - std::vector<std::string> output_dirs, + std::vector<std::string> output_paths, std::map<std::string, std::string> env_vars, std::map<std::string, std::string> const& properties) noexcept : local_context_{*local_context}, root_digest_{std::move(root_digest)}, cmdline_{std::move(command)}, cwd_{std::move(cwd)}, - output_files_{std::move(output_files)}, - output_dirs_{std::move(output_dirs)}, + output_paths_{std::move(output_paths)}, env_vars_{std::move(env_vars)}, properties_{BazelMsgFactory::CreateMessageVectorFromMap< - bazel_re::Platform_Property>(properties)} { - std::sort(output_files_.begin(), output_files_.end()); - std::sort(output_dirs_.begin(), output_dirs_.end()); + bazel_re::Platform_Property>(properties)}, + mode_{RequestMode::kV2_1} { + std::sort(output_paths_.begin(), output_paths_.end()); } [[nodiscard]] auto CreateActionDigest(ArtifactDigest const& exec_dir, @@ -114,6 +158,7 @@ class LocalAction final : public IExecutionAction { .cwd = &cwd_, .output_files = &output_files_, .output_dirs = &output_dirs_, + .output_paths = &output_paths_, .env_vars = &env_vars, .properties = &properties_, .exec_dir = &exec_dir, @@ -153,6 +198,10 @@ class LocalAction final : public IExecutionAction { std::string const& local_path) const noexcept -> std::optional<OutputDirOrSymlink>; + [[nodiscard]] auto CollectOutputPath(std::filesystem::path const& exec_path, + std::string const& local_path) + const noexcept -> std::optional<OutputPath>; + [[nodiscard]] auto CollectAndStoreOutputs( bazel_re::ActionResult* result, std::filesystem::path const& exec_path) const noexcept -> bool; diff --git a/src/buildtool/execution_api/local/local_api.cpp b/src/buildtool/execution_api/local/local_api.cpp index 3eede672..c0a3a30e 100644 --- a/src/buildtool/execution_api/local/local_api.cpp +++ b/src/buildtool/execution_api/local/local_api.cpp @@ -59,6 +59,7 @@ LocalApi::LocalApi(gsl::not_null<LocalContext const*> const& local_context, : local_context_{*local_context}, git_api_{CreateFallbackApi(*local_context->storage, repo_config)} {} +// NOLINTNEXTLINE(google-default-arguments) auto LocalApi::CreateAction( ArtifactDigest const& root_digest, std::vector<std::string> const& command, @@ -66,8 +67,13 @@ auto LocalApi::CreateAction( std::vector<std::string> const& output_files, std::vector<std::string> const& output_dirs, std::map<std::string, std::string> const& env_vars, - std::map<std::string, std::string> const& properties) const noexcept - -> IExecutionAction::Ptr { + std::map<std::string, std::string> const& properties, + bool force_legacy) const noexcept -> IExecutionAction::Ptr { + if (ProtocolTraits::IsNative(GetHashType())) { + // fall back to legacy for native + force_legacy = true; + } + bool best_effort = not force_legacy; return IExecutionAction::Ptr{new (std::nothrow) LocalAction{&local_context_, root_digest, command, @@ -75,6 +81,24 @@ auto LocalApi::CreateAction( output_files, output_dirs, env_vars, + properties, + best_effort}}; +} + +auto LocalApi::CreateAction( + ArtifactDigest const& root_digest, + std::vector<std::string> const& command, + std::string const& cwd, + std::vector<std::string> const& output_paths, + std::map<std::string, std::string> const& env_vars, + std::map<std::string, std::string> const& properties) const noexcept + -> IExecutionAction::Ptr { + return IExecutionAction::Ptr{new (std::nothrow) LocalAction{&local_context_, + root_digest, + command, + cwd, + output_paths, + env_vars, properties}}; } diff --git a/src/buildtool/execution_api/local/local_api.hpp b/src/buildtool/execution_api/local/local_api.hpp index e2374258..1b6ddeab 100644 --- a/src/buildtool/execution_api/local/local_api.hpp +++ b/src/buildtool/execution_api/local/local_api.hpp @@ -40,6 +40,7 @@ class LocalApi final : public IExecutionApi { explicit LocalApi(gsl::not_null<LocalContext const*> const& local_context, RepositoryConfig const* repo_config = nullptr) noexcept; + // NOLINTNEXTLINE(google-default-arguments) [[nodiscard]] auto CreateAction( ArtifactDigest const& root_digest, std::vector<std::string> const& command, @@ -47,9 +48,30 @@ class LocalApi final : public IExecutionApi { std::vector<std::string> const& output_files, std::vector<std::string> const& output_dirs, std::map<std::string, std::string> const& env_vars, - std::map<std::string, std::string> const& properties) const noexcept + std::map<std::string, std::string> const& properties, + bool force_legacy = false) const noexcept -> IExecutionAction::Ptr final; + /// \brief Create a new action (>=RBEv2.1). + /// \param[in] root_digest Digest of the build root. + /// \param[in] command Command as argv vector + /// \param[in] cwd Working directory, relative to execution root + /// \param[in] output_paths List of output file/dir paths, relative to cwd + /// \param[in] env_vars The environment variables to set. + /// \param[in] properties Platform properties to set. + /// \returns The new action. + /// Note that, due to missing file/dir separation, the execution response + /// will not report output file symlinks and directory symlinks separately. + /// (see \ref LocalResponse::DirectorySymlinks) + [[nodiscard]] auto CreateAction( + ArtifactDigest const& root_digest, + std::vector<std::string> const& command, + std::string const& cwd, + std::vector<std::string> const& output_paths, + std::map<std::string, std::string> const& env_vars, + std::map<std::string, std::string> const& properties) const noexcept + -> IExecutionAction::Ptr; + // NOLINTNEXTLINE(google-default-arguments) [[nodiscard]] auto RetrieveToPaths( std::vector<Artifact::ObjectInfo> const& artifacts_info, diff --git a/src/buildtool/execution_api/remote/bazel/bazel_action.cpp b/src/buildtool/execution_api/remote/bazel/bazel_action.cpp index 6fa8eedc..604b680d 100644 --- a/src/buildtool/execution_api/remote/bazel/bazel_action.cpp +++ b/src/buildtool/execution_api/remote/bazel/bazel_action.cpp @@ -27,15 +27,15 @@ #include "src/buildtool/execution_api/utils/outputscheck.hpp" #include "src/buildtool/logging/log_level.hpp" -BazelAction::BazelAction( - std::shared_ptr<BazelNetwork> network, - ArtifactDigest root_digest, - std::vector<std::string> command, - std::string cwd, - std::vector<std::string> output_files, - std::vector<std::string> output_dirs, - std::map<std::string, std::string> const& env_vars, - std::map<std::string, std::string> const& properties) noexcept +BazelAction::BazelAction(std::shared_ptr<BazelNetwork> network, + ArtifactDigest root_digest, + std::vector<std::string> command, + std::string cwd, + std::vector<std::string> output_files, + std::vector<std::string> output_dirs, + std::map<std::string, std::string> const& env_vars, + std::map<std::string, std::string> const& properties, + bool best_effort) noexcept : network_{std::move(network)}, root_digest_{std::move(root_digest)}, cmdline_{std::move(command)}, @@ -45,9 +45,18 @@ BazelAction::BazelAction( env_vars_{BazelMsgFactory::CreateMessageVectorFromMap< bazel_re::Command_EnvironmentVariable>(env_vars)}, properties_{BazelMsgFactory::CreateMessageVectorFromMap< - bazel_re::Platform_Property>(properties)} { + bazel_re::Platform_Property>(properties)}, + mode_{best_effort ? RequestMode::kBestEffort : RequestMode::kV2_0} { std::sort(output_files_.begin(), output_files_.end()); std::sort(output_dirs_.begin(), output_dirs_.end()); + if (best_effort) { + output_paths_.reserve(output_files_.size() + output_dirs_.size()); + output_paths_.insert( + output_paths_.end(), output_files_.begin(), output_files_.end()); + output_paths_.insert( + output_paths_.end(), output_dirs_.begin(), output_dirs_.end()); + std::sort(output_paths_.begin(), output_paths_.end()); + } } auto BazelAction::Execute(Logger const* logger) noexcept @@ -90,13 +99,15 @@ auto BazelAction::Execute(Logger const* logger) noexcept }; if (do_cache) { - if (auto result = - network_->GetCachedActionResult(*action, output_files_)) { + if (auto result = network_->GetCachedActionResult( + *action, + mode_ == RequestMode::kV2_0 ? output_files_ : output_paths_)) { if (result->exit_code() == 0 and - ActionResultContainsExpectedOutputs( - *result, output_files_, output_dirs_) - - ) { + (mode_ == RequestMode::kV2_0 + ? ActionResultContainsExpectedOutputs( + *result, output_files_, output_dirs_) + : ActionResultContainsExpectedOutputs(*result, + output_paths_))) { return create_response( logger, action->hash(), @@ -154,6 +165,7 @@ auto BazelAction::CreateBundlesForAction( .cwd = &cwd_, .output_files = &output_files_, .output_dirs = &output_dirs_, + .output_paths = &output_paths_, .env_vars = &env_vars_, .properties = &properties_, .exec_dir = &exec_dir, diff --git a/src/buildtool/execution_api/remote/bazel/bazel_action.hpp b/src/buildtool/execution_api/remote/bazel/bazel_action.hpp index 08cb338c..ea3c4b53 100644 --- a/src/buildtool/execution_api/remote/bazel/bazel_action.hpp +++ b/src/buildtool/execution_api/remote/bazel/bazel_action.hpp @@ -16,6 +16,7 @@ #define INCLUDED_SRC_BUILDTOOL_EXECUTION_API_REMOTE_BAZEL_BAZEL_ACTION_HPP #include <chrono> +#include <cstdint> #include <map> #include <memory> #include <optional> @@ -36,6 +37,11 @@ class BazelAction final : public IExecutionAction { friend class BazelApi; + enum class RequestMode : std::uint8_t { + kV2_0, // RBEv2.0 + kBestEffort // RBEv2.0 and >=RBEv2.1, let the server pick + }; + public: auto Execute(Logger const* logger) noexcept -> IExecutionResponse::Ptr final; @@ -51,20 +57,22 @@ class BazelAction final : public IExecutionAction { std::string const cwd_; std::vector<std::string> output_files_; std::vector<std::string> output_dirs_; + std::vector<std::string> output_paths_; std::vector<bazel_re::Command_EnvironmentVariable> const env_vars_; std::vector<bazel_re::Platform_Property> const properties_; CacheFlag cache_flag_{CacheFlag::CacheOutput}; std::chrono::milliseconds timeout_{kDefaultTimeout}; + RequestMode mode_{}; - explicit BazelAction( - std::shared_ptr<BazelNetwork> network, - ArtifactDigest root_digest, - std::vector<std::string> command, - std::string cwd, - std::vector<std::string> output_files, - std::vector<std::string> output_dirs, - std::map<std::string, std::string> const& env_vars, - std::map<std::string, std::string> const& properties) noexcept; + explicit BazelAction(std::shared_ptr<BazelNetwork> network, + ArtifactDigest root_digest, + std::vector<std::string> command, + std::string cwd, + std::vector<std::string> output_files, + std::vector<std::string> output_dirs, + std::map<std::string, std::string> const& env_vars, + std::map<std::string, std::string> const& properties, + bool best_effort) noexcept; [[nodiscard]] auto CreateBundlesForAction( std::unordered_set<ArtifactBlob>* blobs, diff --git a/src/buildtool/execution_api/remote/bazel/bazel_api.cpp b/src/buildtool/execution_api/remote/bazel/bazel_api.cpp index c90b7024..00bb2e75 100644 --- a/src/buildtool/execution_api/remote/bazel/bazel_api.cpp +++ b/src/buildtool/execution_api/remote/bazel/bazel_api.cpp @@ -16,6 +16,7 @@ #include <algorithm> #include <atomic> +#include <compare> #include <cstdio> #include <exception> #include <functional> @@ -37,6 +38,7 @@ #include "src/buildtool/execution_api/common/stream_dumper.hpp" #include "src/buildtool/execution_api/common/tree_reader.hpp" #include "src/buildtool/execution_api/remote/bazel/bazel_action.hpp" +#include "src/buildtool/execution_api/remote/bazel/bazel_capabilities_client.hpp" #include "src/buildtool/execution_api/remote/bazel/bazel_network.hpp" #include "src/buildtool/execution_api/remote/bazel/bazel_network_reader.hpp" #include "src/buildtool/file_system/file_system_manager.hpp" @@ -49,6 +51,9 @@ namespace { +auto constexpr kVersion2dot1 = + Capabilities::Version{.major = 2, .minor = 1, .patch = 0}; + [[nodiscard]] auto RetrieveToCas( std::unordered_set<Artifact::ObjectInfo> const& infos, IExecutionApi const& api, @@ -146,6 +151,7 @@ BazelApi::BazelApi(BazelApi&& other) noexcept = default; // implement destructor in cpp, where all members are complete types BazelApi::~BazelApi() = default; +// NOLINTNEXTLINE(google-default-arguments) auto BazelApi::CreateAction( ArtifactDigest const& root_digest, std::vector<std::string> const& command, @@ -153,8 +159,26 @@ auto BazelApi::CreateAction( std::vector<std::string> const& output_files, std::vector<std::string> const& output_dirs, std::map<std::string, std::string> const& env_vars, - std::map<std::string, std::string> const& properties) const noexcept - -> IExecutionAction::Ptr { + std::map<std::string, std::string> const& properties, + bool force_legacy) const noexcept -> IExecutionAction::Ptr { + if (ProtocolTraits::IsNative(GetHashType())) { + // fall back to legacy for native + force_legacy = true; + } + + bool best_effort = not force_legacy; + if (not force_legacy and + network_->GetCapabilities()->high_api_version < kVersion2dot1) { + best_effort = false; + } + if (not best_effort and + network_->GetCapabilities()->low_api_version >= kVersion2dot1) { + Logger::Log(LogLevel::Warning, + "Server does not support RBEv2.0, falling back to newer " + "API version (best effort)."); + best_effort = true; + } + return std::unique_ptr<BazelAction>{new (std::nothrow) BazelAction{network_, root_digest, @@ -163,7 +187,8 @@ auto BazelApi::CreateAction( output_files, output_dirs, env_vars, - properties}}; + properties, + best_effort}}; } // NOLINTNEXTLINE(google-default-arguments) diff --git a/src/buildtool/execution_api/remote/bazel/bazel_api.hpp b/src/buildtool/execution_api/remote/bazel/bazel_api.hpp index df60204f..99e43578 100644 --- a/src/buildtool/execution_api/remote/bazel/bazel_api.hpp +++ b/src/buildtool/execution_api/remote/bazel/bazel_api.hpp @@ -58,6 +58,7 @@ class BazelApi final : public IExecutionApi { auto operator=(BazelApi&&) -> BazelApi& = delete; ~BazelApi() final; + // NOLINTNEXTLINE(google-default-arguments) [[nodiscard]] auto CreateAction( ArtifactDigest const& root_digest, std::vector<std::string> const& command, @@ -65,7 +66,8 @@ class BazelApi final : public IExecutionApi { std::vector<std::string> const& output_files, std::vector<std::string> const& output_dirs, std::map<std::string, std::string> const& env_vars, - std::map<std::string, std::string> const& properties) const noexcept + std::map<std::string, std::string> const& properties, + bool force_legacy = false) const noexcept -> IExecutionAction::Ptr final; // NOLINTNEXTLINE(google-default-arguments) diff --git a/src/buildtool/execution_api/serve/mr_local_api.hpp b/src/buildtool/execution_api/serve/mr_local_api.hpp index d9f323cf..738f52fe 100644 --- a/src/buildtool/execution_api/serve/mr_local_api.hpp +++ b/src/buildtool/execution_api/serve/mr_local_api.hpp @@ -55,8 +55,8 @@ class MRLocalApi final : public IExecutionApi { std::vector<std::string> const& /*output_files*/, std::vector<std::string> const& /*output_dirs*/, std::map<std::string, std::string> const& /*env_vars*/, - std::map<std::string, std::string> const& /*properties*/) const noexcept - -> IExecutionAction::Ptr final { + std::map<std::string, std::string> const& /*properties*/, + bool /*force_legacy*/) const noexcept -> IExecutionAction::Ptr final { // Execution not supported return nullptr; } diff --git a/src/buildtool/execution_api/utils/outputscheck.hpp b/src/buildtool/execution_api/utils/outputscheck.hpp index da20ee90..026fce35 100644 --- a/src/buildtool/execution_api/utils/outputscheck.hpp +++ b/src/buildtool/execution_api/utils/outputscheck.hpp @@ -62,6 +62,34 @@ } } +// overload for treating file and directory paths alike +[[nodiscard]] static inline auto ActionResultContainsExpectedOutputs( + const bazel_re::ActionResult& result, + const std::vector<std::string>& expected_paths) noexcept -> bool { + try { + std::set<std::string> actual_output_paths{}; + for (auto const& file : result.output_files()) { + actual_output_paths.emplace(file.path()); + } + for (auto const& file : result.output_file_symlinks()) { + actual_output_paths.emplace(file.path()); + } + for (auto const& file : result.output_directory_symlinks()) { + actual_output_paths.emplace(file.path()); + } + for (auto const& dir : result.output_directories()) { + actual_output_paths.emplace(dir.path()); + } + return std::all_of(expected_paths.begin(), + expected_paths.end(), + [&actual_output_paths](auto const& expected) { + return actual_output_paths.contains(expected); + }); + } catch (...) { + return false; + } +} + #endif #endif // INCLUDED_SRC_BUILDTOOL_EXECUTION_API_UTILS_OUTPUTSCHECK_HPP diff --git a/test/buildtool/execution_engine/executor/executor.test.cpp b/test/buildtool/execution_engine/executor/executor.test.cpp index 0db5501a..8d37f9aa 100644 --- a/test/buildtool/execution_engine/executor/executor.test.cpp +++ b/test/buildtool/execution_engine/executor/executor.test.cpp @@ -205,8 +205,8 @@ class TestApi : public IExecutionApi { std::vector<std::string> const& /*unused*/, std::vector<std::string> const& /*unused*/, std::map<std::string, std::string> const& /*unused*/, - std::map<std::string, std::string> const& /*unused*/) const noexcept - -> IExecutionAction::Ptr final { + std::map<std::string, std::string> const& /*unused*/, + bool /*unused*/) const noexcept -> IExecutionAction::Ptr final { return std::make_unique<TestAction>(config_); } [[nodiscard]] auto RetrieveToPaths( |