// Copyright 2022 Huawei Cloud Computing Technology Co., Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "src/buildtool/build_engine/target_map/built_in_rules.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // std::move #include #include "fmt/core.h" #include "src/buildtool/build_engine/analysed_target/analysed_target.hpp" #include "src/buildtool/build_engine/analysed_target/target_graph_information.hpp" #include "src/buildtool/build_engine/base_maps/entity_name.hpp" #include "src/buildtool/build_engine/base_maps/entity_name_data.hpp" #include "src/buildtool/build_engine/base_maps/field_reader.hpp" #include "src/buildtool/build_engine/expression/configuration.hpp" #include "src/buildtool/build_engine/expression/evaluator.hpp" #include "src/buildtool/build_engine/expression/expression.hpp" #include "src/buildtool/build_engine/expression/expression_ptr.hpp" #include "src/buildtool/build_engine/expression/function_map.hpp" #include "src/buildtool/build_engine/expression/linked_map.hpp" #include "src/buildtool/build_engine/expression/target_result.hpp" #include "src/buildtool/build_engine/target_map/export.hpp" #include "src/buildtool/build_engine/target_map/utils.hpp" #include "src/buildtool/common/action_description.hpp" #include "src/buildtool/common/artifact_description.hpp" #include "src/buildtool/common/artifact_digest_factory.hpp" #include "src/buildtool/common/repository_config.hpp" #include "src/buildtool/common/tree.hpp" #include "src/buildtool/common/tree_overlay.hpp" #include "src/buildtool/file_system/object_type.hpp" #include "src/buildtool/storage/storage.hpp" #include "src/utils/cpp/path.hpp" #include "src/utils/cpp/vector.hpp" namespace { auto const kGenericRuleFields = std::unordered_set{"arguments_config", "cmds", "cwd", "deps", "env", "execution properties", "sh -c", "tainted", "timeout scaling", "type", "out_dirs", "outs"}; auto const kBlobGenRuleFields = std::unordered_set{"arguments_config", "data", "deps", "name", "tainted", "type"}; auto const kTreeRuleFields = std::unordered_set{"arguments_config", "deps", "name", "tainted", "type"}; auto const kInstallRuleFields = std::unordered_set{"arguments_config", "deps", "dirs", "files", "tainted", "type"}; auto const kConfigureRuleFields = std::unordered_set{"arguments_config", "config", "doc", "tainted", "target", "type"}; void ReportArtifactWithDependencyOrigin( const ExpressionPtr& artifact, const std::unordered_map& deps_by_target, std::stringstream* msg) { *msg << " - " << artifact->ToString() << " from\n"; for (auto const& [name, analysis_result] : deps_by_target) { for (auto const& [path, value] : analysis_result->Artifacts().Map()) { if (value == artifact) { *msg << " - " << name.ToString() << ", artifact at " << nlohmann::json(path).dump() << "\n"; } } for (auto const& [path, value] : analysis_result->RunFiles().Map()) { if (value == artifact) { *msg << " - " << name.ToString() << ", runfile at " << nlohmann::json(path).dump() << "\n"; } } } } void ReportStagingConflict( const std::string& location, const ExpressionPtr& stage_a, const ExpressionPtr& stage_b, const std::unordered_map& deps_by_target, const BuildMaps::Target::TargetMap::LoggerPtr& logger) { std::stringstream msg{}; auto artifact_a = stage_a->Get(location, Expression::kNone); auto artifact_b = stage_b->Get(location, Expression::kNone); msg << "Staging conflict on path " << nlohmann::json(location).dump() << " between\n"; ReportArtifactWithDependencyOrigin(artifact_a, deps_by_target, &msg); ReportArtifactWithDependencyOrigin(artifact_b, deps_by_target, &msg); (*logger)(msg.str(), true); } void BlobGenRuleWithDeps( const gsl::not_null& context, const std::vector& transition_keys, const std::vector& dependency_values, const BuildMaps::Base::FieldReader::Ptr& desc, const BuildMaps::Target::ConfiguredTarget& key, const BuildMaps::Target::TargetMap::SetterPtr& setter, const BuildMaps::Target::TargetMap::LoggerPtr& logger, const gsl::not_null& result_map, const ObjectType& blob_type) { // Associate keys and values std::unordered_map deps_by_transition; deps_by_transition.reserve(transition_keys.size()); for (std::size_t i = 0; i < transition_keys.size(); ++i) { deps_by_transition.emplace(transition_keys[i], *dependency_values[i]); } auto param_vars = desc->ReadStringList("arguments_config"); if (not param_vars) { return; } auto param_config = key.config.Prune(*param_vars); auto vars_set = std::unordered_set{}; vars_set.insert(param_vars->begin(), param_vars->end()); for (auto const& dep : dependency_values) { vars_set.insert((*dep)->Vars().begin(), (*dep)->Vars().end()); } auto effective_conf = key.config.Prune(vars_set); std::vector all_deps{}; all_deps.reserve(dependency_values.size()); for (auto const& dep : dependency_values) { all_deps.emplace_back((*dep)->GraphInformation().Node()); } auto deps_info = TargetGraphInformation{ std::make_shared( BuildMaps::Target::ConfiguredTarget{key.target, effective_conf}), all_deps, {}, {}}; auto string_fields_fcts = FunctionMap::MakePtr(FunctionMap::underlying_map_t{ {"outs", [&deps_by_transition, &key, context]( auto&& eval, auto const& expr, auto const& env) { return BuildMaps::Target::Utils::keys_expr( BuildMaps::Target::Utils::obtainTargetByName( eval, expr, env, key.target, context->repo_config, deps_by_transition) ->Artifacts()); }}, {"runfiles", [&deps_by_transition, &key, context]( auto&& eval, auto const& expr, auto const& env) { return BuildMaps::Target::Utils::keys_expr( BuildMaps::Target::Utils::obtainTargetByName( eval, expr, env, key.target, context->repo_config, deps_by_transition) ->RunFiles()); }}}); auto tainted = std::set{}; auto got_tainted = BuildMaps::Target::Utils::getTainted( &tainted, param_config, desc->ReadOptionalExpression("tainted", Expression::kEmptyList), logger); if (not got_tainted) { return; } for (auto const& dep : dependency_values) { if (not std::includes(tainted.begin(), tainted.end(), (*dep)->Tainted().begin(), (*dep)->Tainted().end())) { (*logger)( "Not tainted with all strings the dependencies are tainted " "with", true); return; } } std::set implied_export{}; for (auto const& dep : dependency_values) { implied_export.insert((*dep)->ImpliedExport().begin(), (*dep)->ImpliedExport().end()); } auto name_exp = desc->ReadOptionalExpression( "name", ExpressionPtr{std::string{"out.txt"}}); if (not name_exp) { return; } auto name_val = name_exp.Evaluate( param_config, string_fields_fcts, [logger](auto const& msg) { (*logger)(fmt::format("While evaluating name:\n{}", msg), true); }); if (not name_val) { return; } if (not name_val->IsString()) { (*logger)(fmt::format("name should evaluate to a string, but got {}", name_val->ToString()), true); return; } auto data_exp = desc->ReadOptionalExpression("data", ExpressionPtr{std::string{""}}); if (not data_exp) { return; } auto data_val = data_exp.Evaluate( param_config, string_fields_fcts, [logger](auto const& msg) { (*logger)(fmt::format("While evaluating data:\n{}", msg), true); }); if (not data_val) { return; } if (not data_val->IsString()) { (*logger)(fmt::format("data should evaluate to a string, but got {}", data_val->ToString()), true); return; } // if symlink target, we only accept non-upwards if (IsSymlinkObject(blob_type) and not PathIsNonUpwards(data_val->String())) { (*logger)(fmt::format("data string {} does not constitute a " "non-upwards symlink target path", data_val->String()), true); return; } auto stage = ExpressionPtr{Expression::map_t{ name_val->String(), ExpressionPtr{ArtifactDescription::CreateKnown( ArtifactDigestFactory::HashDataAs( context->storage->GetHashFunction(), data_val->String()), blob_type)}}}; auto analysis_result = std::make_shared( TargetResult{.artifact_stage = stage, .provides = ExpressionPtr{Expression::map_t{}}, .runfiles = stage}, std::vector{}, std::vector{data_val->String()}, std::vector{}, std::vector{}, std::move(vars_set), std::move(tainted), std::move(implied_export), std::move(deps_info)); analysis_result = result_map->Add(key.target, effective_conf, std::move(analysis_result)); (*setter)(std::move(analysis_result)); } void BlobGenRule( const gsl::not_null& context, const nlohmann::json& desc_json, const BuildMaps::Target::ConfiguredTarget& key, const BuildMaps::Target::TargetMap::SubCallerPtr& subcaller, const BuildMaps::Target::TargetMap::SetterPtr& setter, const BuildMaps::Target::TargetMap::LoggerPtr& logger, const gsl::not_null& result_map, const ObjectType& blob_type) { auto desc = BuildMaps::Base::FieldReader::CreatePtr( desc_json, key.target, IsSymlinkObject(blob_type) ? "symlink target" : "file-generation target", logger); desc->ExpectFields(kBlobGenRuleFields); auto param_vars = desc->ReadStringList("arguments_config"); if (not param_vars) { return; } auto param_config = key.config.Prune(*param_vars); // Collect dependencies: deps auto const& empty_list = Expression::kEmptyList; auto deps_exp = desc->ReadOptionalExpression("deps", empty_list); if (not deps_exp) { return; } auto deps_value = deps_exp.Evaluate(param_config, {}, [&logger](auto const& msg) { (*logger)(fmt::format("While evaluating deps:\n{}", msg), true); }); if (not deps_value) { return; } if (not deps_value->IsList()) { (*logger)(fmt::format("Expected deps to evaluate to a list of targets, " "but found {}", deps_value->ToString()), true); return; } std::vector dependency_keys; std::vector transition_keys; dependency_keys.reserve(deps_value->List().size()); transition_keys.reserve(deps_value->List().size()); auto empty_transition = Configuration{Expression::kEmptyMap}; for (auto const& dep_name : deps_value->List()) { auto dep_target = BuildMaps::Base::ParseEntityNameFromExpression( dep_name, key.target, context->repo_config, [&logger, &dep_name](std::string const& parse_err) { (*logger)(fmt::format("Parsing dep entry {} failed with:\n{}", dep_name->ToString(), parse_err), true); }); if (not dep_target) { return; } dependency_keys.emplace_back( BuildMaps::Target::ConfiguredTarget{*dep_target, key.config}); transition_keys.emplace_back( BuildMaps::Target::ConfiguredTarget{*dep_target, empty_transition}); } (*subcaller)( dependency_keys, [context, transition_keys = std::move(transition_keys), desc, setter, logger, key, result_map, blob_type](auto const& values) { BlobGenRuleWithDeps(context, transition_keys, values, desc, key, setter, logger, result_map, blob_type); }, logger); } void FileGenRule( const gsl::not_null& context, const nlohmann::json& desc_json, const BuildMaps::Target::ConfiguredTarget& key, const BuildMaps::Target::TargetMap::SubCallerPtr& subcaller, const BuildMaps::Target::TargetMap::SetterPtr& setter, const BuildMaps::Target::TargetMap::LoggerPtr& logger, const gsl::not_null& result_map) { BlobGenRule(context, desc_json, key, subcaller, setter, logger, result_map, ObjectType::File); } void SymlinkRule( const gsl::not_null& context, const nlohmann::json& desc_json, const BuildMaps::Target::ConfiguredTarget& key, const BuildMaps::Target::TargetMap::SubCallerPtr& subcaller, const BuildMaps::Target::TargetMap::SetterPtr& setter, const BuildMaps::Target::TargetMap::LoggerPtr& logger, const gsl::not_null& result_map) { BlobGenRule(context, desc_json, key, subcaller, setter, logger, result_map, ObjectType::Symlink); } void TreeRuleWithDeps( const std::vector& dependency_values, const std::vector& dependency_keys, const std::string& name, const BuildMaps::Target::ConfiguredTarget& key, const BuildMaps::Base::FieldReader::Ptr& desc, const BuildMaps::Target::TargetMap::SetterPtr& setter, const BuildMaps::Target::TargetMap::LoggerPtr& logger, const gsl::not_null& result_map, std::optional disjoint_overlay) { auto param_vars = desc->ReadStringList("arguments_config"); if (not param_vars) { return; } auto param_config = key.config.Prune(*param_vars); auto tainted = std::set{}; auto got_tainted = BuildMaps::Target::Utils::getTainted( &tainted, param_config, desc->ReadOptionalExpression("tainted", Expression::kEmptyList), logger); if (not got_tainted) { return; } for (auto const& dep : dependency_values) { if (not std::includes(tainted.begin(), tainted.end(), (*dep)->Tainted().begin(), (*dep)->Tainted().end())) { (*logger)( "Not tainted with all strings the dependencies are tainted " "with", true); return; } } std::set implied_export{}; for (auto const& dep : dependency_values) { implied_export.insert((*dep)->ImpliedExport().begin(), (*dep)->ImpliedExport().end()); } auto vars_set = std::unordered_set{}; vars_set.insert(param_vars->begin(), param_vars->end()); for (auto const& dep : dependency_values) { vars_set.insert((*dep)->Vars().begin(), (*dep)->Vars().end()); } auto effective_conf = key.config.Prune(vars_set); std::vector all_deps{}; all_deps.reserve(dependency_values.size()); for (auto const& dep : dependency_values) { all_deps.emplace_back((*dep)->GraphInformation().Node()); } auto deps_info = TargetGraphInformation{ std::make_shared( BuildMaps::Target::ConfiguredTarget{key.target, effective_conf}), all_deps, {}, {}}; // Compute the resulting stage auto result_stage = Expression::map_t::underlying_map_t{}; std::vector trees{}; std::vector tree_overlays{}; if (disjoint_overlay) { TreeOverlay::to_overlay_t dep_trees{}; for (auto const& dep : dependency_values) { std::unordered_map tree_content; for (auto const& [input_path, artifact] : (*dep)->Artifacts()->Map()) { auto norm_path = ToNormalPath(std::filesystem::path{input_path}); tree_content.emplace(std::move(norm_path), artifact->Artifact()); } auto tree = std::make_shared(std::move(tree_content)); auto tree_id = tree->Id(); trees.emplace_back(std::move(tree)); dep_trees.emplace_back(ArtifactDescription::CreateTree(tree_id)); } auto overlay_tree = std::make_shared(std::move(dep_trees), *disjoint_overlay); auto overlay_tree_id = overlay_tree->Id(); tree_overlays.emplace_back(std::move(overlay_tree)); result_stage.emplace( name, ArtifactDescription::CreateTreeOverlay(overlay_tree_id)); } else { auto stage = ExpressionPtr{Expression::map_t{}}; for (auto const& dep : dependency_values) { auto to_stage = ExpressionPtr{ Expression::map_t{(*dep)->RunFiles(), (*dep)->Artifacts()}}; auto dup = stage->Map().FindConflictingDuplicate(to_stage->Map()); if (dup) { std::unordered_map deps_by_target; deps_by_target.reserve(dependency_keys.size()); for (std::size_t i = 0; i < dependency_keys.size(); ++i) { deps_by_target.emplace(dependency_keys[i].target, *dependency_values[i]); } ReportStagingConflict( dup->get(), stage, to_stage, deps_by_target, logger); return; } stage = ExpressionPtr{Expression::map_t{stage, to_stage}}; } // Result is the associated tree, located at name auto conflict = BuildMaps::Target::Utils::tree_conflict(stage); if (conflict) { (*logger)(fmt::format("TREE conflict on subtree {}", *conflict), true); return; } std::unordered_map tree_content; tree_content.reserve(stage->Map().size()); for (auto const& [input_path, artifact] : stage->Map()) { auto norm_path = ToNormalPath(std::filesystem::path{input_path}); tree_content.emplace(std::move(norm_path), artifact->Artifact()); } auto tree = std::make_shared(std::move(tree_content)); auto tree_id = tree->Id(); trees.emplace_back(std::move(tree)); result_stage.emplace(name, ArtifactDescription::CreateTree(tree_id)); } auto result = ExpressionPtr{Expression::map_t{result_stage}}; auto analysis_result = std::make_shared( TargetResult{.artifact_stage = result, .provides = ExpressionPtr{Expression::map_t{}}, .runfiles = result}, std::vector{}, std::vector{}, std::move(trees), std::move(tree_overlays), std::move(vars_set), std::move(tainted), std::move(implied_export), std::move(deps_info)); analysis_result = result_map->Add(key.target, effective_conf, std::move(analysis_result)); (*setter)(std::move(analysis_result)); } void CommonTreeRule( const gsl::not_null& context, const nlohmann::json& desc_json, const BuildMaps::Target::ConfiguredTarget& key, const BuildMaps::Target::TargetMap::SubCallerPtr& subcaller, const BuildMaps::Target::TargetMap::SetterPtr& setter, const BuildMaps::Target::TargetMap::LoggerPtr& logger, const gsl::not_null& result_map, std::optional disjoint_overlay) { std::string rule_name; if (disjoint_overlay) { rule_name = *disjoint_overlay ? "disjoint_tree_overlay" : "tree overlay"; } else { rule_name = "tree"; } auto desc = BuildMaps::Base::FieldReader::CreatePtr( desc_json, key.target, fmt::format("{} target", rule_name), logger); desc->ExpectFields(kTreeRuleFields); auto param_vars = desc->ReadStringList("arguments_config"); if (not param_vars) { return; } auto param_config = key.config.Prune(*param_vars); // Collect dependencies: deps auto const& empty_list = Expression::kEmptyList; auto deps_exp = desc->ReadOptionalExpression("deps", empty_list); if (not deps_exp) { return; } auto deps_value = deps_exp.Evaluate(param_config, {}, [&logger](auto const& msg) { (*logger)(fmt::format("While evaluating deps:\n{}", msg), true); }); if (not deps_value) { return; } if (not deps_value->IsList()) { (*logger)(fmt::format("Expected deps to evaluate to a list of targets, " "but found {}", deps_value->ToString()), true); return; } std::vector dependency_keys; for (auto const& dep_name : deps_value->List()) { auto dep_target = BuildMaps::Base::ParseEntityNameFromExpression( dep_name, key.target, context->repo_config, [&logger, &dep_name](std::string const& parse_err) { (*logger)(fmt::format("Parsing dep entry {} failed with:\n{}", dep_name->ToString(), parse_err), true); }); if (not dep_target) { return; } dependency_keys.emplace_back( BuildMaps::Target::ConfiguredTarget{*dep_target, key.config}); } auto name_exp = desc->ReadOptionalExpression("name", ExpressionPtr{std::string{""}}); if (not name_exp) { return; } auto name_value = name_exp.Evaluate(param_config, {}, [&logger](auto const& msg) { (*logger)(fmt::format("While evaluating name:\n{}", msg), true); }); if (not name_value) { return; } if (not name_value->IsString()) { (*logger)( fmt::format("Expected name to evaluate to a string, but got {}", name_value->ToString()), true); return; } (*subcaller)( dependency_keys, [name = name_value->String(), dependency_keys, desc, setter, logger, key, result_map, disjoint_overlay](auto const& values) { TreeRuleWithDeps(values, dependency_keys, name, key, desc, setter, logger, result_map, disjoint_overlay); }, logger); } void TreeRule( const gsl::not_null& context, const nlohmann::json& desc_json, const BuildMaps::Target::ConfiguredTarget& key, const BuildMaps::Target::TargetMap::SubCallerPtr& subcaller, const BuildMaps::Target::TargetMap::SetterPtr& setter, const BuildMaps::Target::TargetMap::LoggerPtr& logger, const gsl::not_null& result_map) { CommonTreeRule(context, desc_json, key, subcaller, setter, logger, result_map, std::nullopt); } void DisjointTreeOverlayRule( const gsl::not_null& context, const nlohmann::json& desc_json, const BuildMaps::Target::ConfiguredTarget& key, const BuildMaps::Target::TargetMap::SubCallerPtr& subcaller, const BuildMaps::Target::TargetMap::SetterPtr& setter, const BuildMaps::Target::TargetMap::LoggerPtr& logger, const gsl::not_null& result_map) { CommonTreeRule( context, desc_json, key, subcaller, setter, logger, result_map, true); } void TreeOverlayRule( const gsl::not_null& context, const nlohmann::json& desc_json, const BuildMaps::Target::ConfiguredTarget& key, const BuildMaps::Target::TargetMap::SubCallerPtr& subcaller, const BuildMaps::Target::TargetMap::SetterPtr& setter, const BuildMaps::Target::TargetMap::LoggerPtr& logger, const gsl::not_null& result_map) { CommonTreeRule( context, desc_json, key, subcaller, setter, logger, result_map, false); } void InstallRuleWithDeps( const std::vector& dependency_keys, const std::vector& dependency_values, const BuildMaps::Base::FieldReader::Ptr& desc, const BuildMaps::Target::ConfiguredTarget& key, const std::vector& deps, const std::unordered_map& files, const std::vector>& dirs, const BuildMaps::Target::TargetMap::SetterPtr& setter, const BuildMaps::Target::TargetMap::LoggerPtr& logger, const gsl::not_null& result_map) { // Associate keys and values std::unordered_map deps_by_target; deps_by_target.reserve(dependency_keys.size()); for (std::size_t i = 0; i < dependency_keys.size(); ++i) { deps_by_target.emplace(dependency_keys[i].target, *dependency_values[i]); } // Compute the effective dependency on config variables std::unordered_set effective_vars; auto param_vars = desc->ReadStringList("arguments_config"); effective_vars.insert(param_vars->begin(), param_vars->end()); for (auto const& [target_name, target] : deps_by_target) { effective_vars.insert(target->Vars().begin(), target->Vars().end()); } auto effective_conf = key.config.Prune(effective_vars); std::vector all_deps{}; all_deps.reserve(dependency_values.size()); for (auto const& dep : dependency_values) { all_deps.emplace_back((*dep)->GraphInformation().Node()); } auto deps_info = TargetGraphInformation{ std::make_shared( BuildMaps::Target::ConfiguredTarget{key.target, effective_conf}), all_deps, {}, {}}; // Compute and verify taintedness auto tainted = std::set{}; auto got_tainted = BuildMaps::Target::Utils::getTainted( &tainted, key.config.Prune(*param_vars), desc->ReadOptionalExpression("tainted", Expression::kEmptyList), logger); if (not got_tainted) { return; } for (auto const& dep : dependency_values) { if (not std::includes(tainted.begin(), tainted.end(), (*dep)->Tainted().begin(), (*dep)->Tainted().end())) { (*logger)( "Not tainted with all strings the dependencies are tainted " "with", true); return; } } // Compute implied export targets std::set implied_export{}; for (auto const& dep : dependency_values) { implied_export.insert((*dep)->ImpliedExport().begin(), (*dep)->ImpliedExport().end()); } // Stage deps (runfiles only) auto stage = ExpressionPtr{Expression::map_t{}}; for (auto const& dep : deps) { auto to_stage = deps_by_target.at(dep)->RunFiles(); auto dup = stage->Map().FindConflictingDuplicate(to_stage->Map()); if (dup) { ReportStagingConflict( dup->get(), stage, to_stage, deps_by_target, logger); return; } stage = ExpressionPtr{Expression::map_t{stage, to_stage}}; } // stage files (artifacts, but fall back to runfiles) auto files_stage = Expression::map_t::underlying_map_t{}; for (auto const& [path, target] : files) { if (stage->Map().contains(path)) { (*logger)(fmt::format("Staging conflict for path {}", path), true); return; } auto artifacts = deps_by_target[target]->Artifacts(); if (artifacts->Map().empty()) { // If no artifacts are present, fall back to runfiles artifacts = deps_by_target[target]->RunFiles(); } if (artifacts->Map().empty()) { (*logger)(fmt::format( "No artifacts or runfiles for {} to be staged to {}", target.ToString(), path), true); return; } if (artifacts->Map().size() != 1) { (*logger)( fmt::format("Not precisely one entry for {} to be staged to {}", target.ToString(), path), true); return; } files_stage.emplace(path, artifacts->Map().Values()[0]); } stage = ExpressionPtr{Expression::map_t{stage, files_stage}}; // stage dirs (artifacts and runfiles) for (auto const& subdir : dirs) { auto subdir_stage = Expression::map_t::underlying_map_t{}; auto dir_path = std::filesystem::path{subdir.second}; auto target = deps_by_target.at(subdir.first); // within a target, artifacts and runfiles may overlap, but artifacts // take preference for (auto const& [path, artifact] : target->Artifacts()->Map()) { subdir_stage.emplace(ToNormalPath(dir_path / path).string(), artifact); } for (auto const& [path, artifact] : target->RunFiles()->Map()) { subdir_stage.emplace(ToNormalPath(dir_path / path).string(), artifact); } auto to_stage = ExpressionPtr{Expression::map_t{subdir_stage}}; auto dup = stage->Map().FindConflictingDuplicate(to_stage->Map()); if (dup) { ReportStagingConflict( dup->get(), stage, to_stage, deps_by_target, logger); return; } stage = ExpressionPtr{Expression::map_t{stage, to_stage}}; } auto conflict = BuildMaps::Target::Utils::tree_conflict(stage); if (conflict) { (*logger)(fmt::format("TREE conflict on subtree {}", *conflict), true); return; } auto const& empty_map = Expression::kEmptyMap; auto result = std::make_shared( TargetResult{ .artifact_stage = stage, .provides = empty_map, .runfiles = stage}, std::vector{}, std::vector{}, std::vector{}, std::vector{}, std::move(effective_vars), std::move(tainted), std::move(implied_export), std::move(deps_info)); result = result_map->Add(key.target, effective_conf, std::move(result)); (*setter)(std::move(result)); } void InstallRule( const gsl::not_null& context, const nlohmann::json& desc_json, const BuildMaps::Target::ConfiguredTarget& key, const BuildMaps::Target::TargetMap::SubCallerPtr& subcaller, const BuildMaps::Target::TargetMap::SetterPtr& setter, const BuildMaps::Target::TargetMap::LoggerPtr& logger, const gsl::not_null& result_map) { auto desc = BuildMaps::Base::FieldReader::CreatePtr( desc_json, key.target, "install target", logger); desc->ExpectFields(kInstallRuleFields); auto param_vars = desc->ReadStringList("arguments_config"); if (not param_vars) { return; } auto param_config = key.config.Prune(*param_vars); // Collect dependencies: deps auto const& empty_list = Expression::kEmptyList; auto deps_exp = desc->ReadOptionalExpression("deps", empty_list); if (not deps_exp) { return; } auto deps_value = deps_exp.Evaluate(param_config, {}, [&logger](auto const& msg) { (*logger)(fmt::format("While evaluating deps:\n{}", msg), true); }); if (not deps_value) { return; } if (not deps_value->IsList()) { (*logger)(fmt::format("Expected deps to evaluate to a list of targets, " "but found {}", deps_value->ToString()), true); return; } std::vector dependency_keys; std::vector deps; deps.reserve(deps_value->List().size()); for (auto const& dep_name : deps_value->List()) { auto dep_target = BuildMaps::Base::ParseEntityNameFromExpression( dep_name, key.target, context->repo_config, [&logger, &dep_name](std::string const& parse_err) { (*logger)(fmt::format("Parsing dep entry {} failed with:\n{}", dep_name->ToString(), parse_err), true); }); if (not dep_target) { return; } dependency_keys.emplace_back( BuildMaps::Target::ConfiguredTarget{*dep_target, key.config}); deps.emplace_back(*dep_target); } // Collect dependencies: files auto const& empty_map = Expression::kEmptyMap; auto files_exp = desc->ReadOptionalExpression("files", empty_map); if (not files_exp) { return; } if (not files_exp->IsMap()) { (*logger)(fmt::format("Expected files to be a map of target " "expressions, but found {}", files_exp->ToString()), true); return; } auto files = std::unordered_map{}; files.reserve(files_exp->Map().size()); for (auto const& [path, dep_exp] : files_exp->Map()) { auto dep_name = dep_exp.Evaluate( param_config, {}, [&logger, &path = path](auto const& msg) { (*logger)( fmt::format( "While evaluating files entry for {}:\n{}", path, msg), true); }); if (not dep_name) { return; } auto dep_target = BuildMaps::Base::ParseEntityNameFromExpression( dep_name, key.target, context->repo_config, [&logger, &dep_name, &path = path](std::string const& parse_err) { (*logger)(fmt::format("Parsing file entry {} for key {} failed " "with:\n{}", dep_name->ToString(), path, parse_err), true); }); if (not dep_target) { return; } dependency_keys.emplace_back( BuildMaps::Target::ConfiguredTarget{*dep_target, key.config}); files.emplace(path, *dep_target); } // Collect dependencies: dirs auto dirs_exp = desc->ReadOptionalExpression("dirs", empty_list); if (not dirs_exp) { return; } auto dirs_value = dirs_exp.Evaluate(param_config, {}, [&logger](auto const& msg) { (*logger)(fmt::format("While evaluating deps:\n{}", msg), true); }); if (not dirs_value) { return; } if (not dirs_value->IsList()) { (*logger)(fmt::format("Expected dirs to evaluate to a list of " "path-target pairs, but found {}", dirs_value->ToString()), true); return; } auto dirs = std::vector>{}; dirs.reserve(dirs_value->List().size()); for (auto const& entry : dirs_value->List()) { if (not entry->IsList() or entry->List().size() != 2 or not entry->List()[1]->IsString()) { (*logger)(fmt::format("Expected dirs to evaluate to a list of " "target-path pairs, but found entry {}", entry->ToString()), true); return; } auto dep_target = BuildMaps::Base::ParseEntityNameFromExpression( entry->List()[0], key.target, context->repo_config, [&logger, &entry](std::string const& parse_err) { (*logger)(fmt::format("Parsing dir entry {} for path {} failed " "with:\n{}", entry->List()[0]->ToString(), entry->List()[1]->String(), parse_err), true); }); if (not dep_target) { return; } dependency_keys.emplace_back( BuildMaps::Target::ConfiguredTarget{*dep_target, key.config}); dirs.emplace_back(*dep_target, entry->List()[1]->String()); } (*subcaller)( dependency_keys, [dependency_keys, deps = std::move(deps), files = std::move(files), dirs = std::move(dirs), desc, setter, logger, key, result_map](auto const& values) { InstallRuleWithDeps(dependency_keys, values, desc, key, deps, files, dirs, setter, logger, result_map); }, logger); } void GenericRuleWithDeps( const std::vector& transition_keys, const std::vector& dependency_values, const BuildMaps::Base::FieldReader::Ptr& desc, const BuildMaps::Target::ConfiguredTarget& key, const gsl::not_null& repo_config, const BuildMaps::Target::TargetMap::SetterPtr& setter, const BuildMaps::Target::TargetMap::LoggerPtr& logger, const gsl::not_null& result_map) { // Associate dependency keys with values std::unordered_map deps_by_transition; deps_by_transition.reserve(transition_keys.size()); for (std::size_t i = 0; i < transition_keys.size(); ++i) { deps_by_transition.emplace(transition_keys[i], *dependency_values[i]); } // Compute the effective dependency on config variables std::unordered_set effective_vars; auto param_vars = desc->ReadStringList("arguments_config"); effective_vars.insert(param_vars->begin(), param_vars->end()); for (auto const& [transition, target] : deps_by_transition) { effective_vars.insert(target->Vars().begin(), target->Vars().end()); } auto effective_conf = key.config.Prune(effective_vars); std::vector all_deps{}; all_deps.reserve(dependency_values.size()); for (auto const& dep : dependency_values) { all_deps.emplace_back((*dep)->GraphInformation().Node()); } auto deps_info = TargetGraphInformation{ std::make_shared( BuildMaps::Target::ConfiguredTarget{key.target, effective_conf}), all_deps, {}, {}}; // Compute and verify taintedness auto tainted = std::set{}; auto got_tainted = BuildMaps::Target::Utils::getTainted( &tainted, key.config.Prune(*param_vars), desc->ReadOptionalExpression("tainted", Expression::kEmptyList), logger); if (not got_tainted) { return; } for (auto const& dep : dependency_values) { if (not std::includes(tainted.begin(), tainted.end(), (*dep)->Tainted().begin(), (*dep)->Tainted().end())) { (*logger)( "Not tainted with all strings the dependencies are tainted " "with", true); return; } } // Compute implied export targets std::set implied_export{}; for (auto const& dep : dependency_values) { implied_export.insert((*dep)->ImpliedExport().begin(), (*dep)->ImpliedExport().end()); } // Evaluate cmd, outs, env auto string_fields_fcts = FunctionMap::MakePtr(FunctionMap::underlying_map_t{ {"outs", [&deps_by_transition, &key, repo_config]( auto&& eval, auto const& expr, auto const& env) { return BuildMaps::Target::Utils::keys_expr( BuildMaps::Target::Utils::obtainTargetByName( eval, expr, env, key.target, repo_config, deps_by_transition) ->Artifacts()); }}, {"runfiles", [&deps_by_transition, &key, repo_config]( auto&& eval, auto const& expr, auto const& env) { return BuildMaps::Target::Utils::keys_expr( BuildMaps::Target::Utils::obtainTargetByName( eval, expr, env, key.target, repo_config, deps_by_transition) ->RunFiles()); }}}); auto const& empty_list = Expression::kEmptyList; auto param_config = key.config.Prune(*param_vars); auto outs_exp = desc->ReadOptionalExpression("outs", empty_list); auto out_dirs_exp = desc->ReadOptionalExpression("out_dirs", empty_list); std::vector outs{}; std::vector out_dirs{}; if (outs_exp) { auto outs_value = outs_exp.Evaluate( param_config, string_fields_fcts, [&logger](auto const& msg) { (*logger)(fmt::format("While evaluating outs:\n{}", msg), true); }); if (not outs_value) { return; } if (not outs_value->IsList()) { (*logger)(fmt::format("outs has to evaluate to a list of " "strings, but found {}", outs_value->ToString()), true); return; } if (not outs_value->List().empty()) { outs.reserve(outs_value->List().size()); for (auto const& x : outs_value->List()) { if (not x->IsString()) { (*logger)(fmt::format("outs has to evaluate to a list of " "strings, but found entry {}", x->ToString()), true); return; } outs.emplace_back(ToNormalPath(x->String()).string()); } } } if (out_dirs_exp) { auto out_dirs_value = out_dirs_exp.Evaluate( param_config, string_fields_fcts, [&logger](auto const& msg) { (*logger)(fmt::format("While evaluating out_dirs:\n{}", msg), true); }); if (not out_dirs_value) { return; } if (not out_dirs_value->IsList()) { (*logger)(fmt::format("out_dirs has to evaluate to a list of " "strings, but found {}", out_dirs_value->ToString()), true); return; } if (not out_dirs_value->List().empty()) { out_dirs.reserve(out_dirs_value->List().size()); for (auto const& x : out_dirs_value->List()) { if (not x->IsString()) { (*logger)( fmt::format("out_dirs has to evaluate to a list of " "strings, but found entry {}", x->ToString()), true); return; } out_dirs.emplace_back(ToNormalPath(x->String()).string()); } } } if (outs.empty() and out_dirs.empty()) { (*logger)( R"(At least one of "outs" and "out_dirs" must be specified for "generic")", true); return; } sort_and_deduplicate(&outs); sort_and_deduplicate(&out_dirs); // looking for same paths in both outs and out_dirs std::vector intersection; std::set_intersection(outs.begin(), outs.end(), out_dirs.begin(), out_dirs.end(), std::back_inserter(intersection)); if (not intersection.empty()) { (*logger)(fmt::format("outs and out_dirs for generic must be disjoint. " "Found repeated entries:\n{}", nlohmann::json(intersection).dump()), true); return; } auto cmd_exp = desc->ReadOptionalExpression("cmds", empty_list); if (not cmd_exp) { return; } auto cmd_value = cmd_exp.Evaluate( param_config, string_fields_fcts, [&logger](auto const& msg) { (*logger)(fmt::format("While evaluating cmds:\n{}", msg), true); }); if (not cmd_value) { return; } if (not cmd_value->IsList()) { (*logger)(fmt::format( "cmds has to evaluate to a list of strings, but found {}", cmd_value->ToString()), true); return; } std::stringstream cmd_ss{}; for (auto const& x : cmd_value->List()) { if (not x->IsString()) { (*logger)(fmt::format("cmds has to evaluate to a list of strings, " "but found entry {}", x->ToString()), true); return; } cmd_ss << x->String(); cmd_ss << "\n"; } auto cwd_exp = desc->ReadOptionalExpression("cwd", Expression::kEmptyString); if (not cwd_exp) { return; } auto cwd_value = cwd_exp.Evaluate( param_config, string_fields_fcts, [&logger](auto const& msg) { (*logger)(fmt::format("While evaluating cwd:\n{}", msg), true); }); if (not cwd_value) { return; } if (not cwd_value->IsString()) { (*logger)(fmt::format("cwd has to evaluate to a string, but found {}", cwd_value->ToString()), true); return; } if (not PathIsNonUpwards(cwd_value->String())) { (*logger)(fmt::format("cwd has to evaluate to a non-upwards relative " "path, but found {}", cwd_value->ToString()), true); return; } auto const& empty_map_exp = Expression::kEmptyMapExpr; auto env_exp = desc->ReadOptionalExpression("env", empty_map_exp); if (not env_exp) { return; } auto env_val = env_exp.Evaluate( param_config, string_fields_fcts, [&logger](auto const& msg) { (*logger)(fmt::format("While evaluating env:\n{}", msg), true); }); if (not env_val) { return; } if (not env_val->IsMap()) { (*logger)( fmt::format("env has to evaluate to map of strings, but found {}", env_val->ToString()), true); return; } for (auto const& [var_name, x] : env_val->Map()) { if (not x->IsString()) { (*logger)(fmt::format("env has to evaluate to map of strings, but " "found entry {}", x->ToString()), true); return; } } auto sh_exp = desc->ReadOptionalExpression("sh -c", Expression::kEmptyList); if (not sh_exp) { return; } auto sh_val = sh_exp.Evaluate( param_config, string_fields_fcts, [&logger](auto const& msg) { (*logger)(fmt::format("While evaluating sh:\n{}", msg), true); }); if (not sh_val) { return; } if (sh_val->IsNone()) { sh_val = Expression::kEmptyList; } if (not sh_val->IsList()) { (*logger)(fmt::format("sh has evaluate to list of strings or null, but " "found {}", sh_val->ToString()), true); return; } for (auto const& entry : sh_val->List()) { if (not entry->IsString()) { (*logger)(fmt::format("sh has evaluate to list of strings or null, " "but found {}\nwith non-string entry {}", sh_val->ToString(), entry->ToString()), true); return; } } static ExpressionPtr const kShC = Expression::FromJson(R"( ["sh", "-c"] )"_json); if (sh_val->List().empty()) { sh_val = kShC; } auto scale_exp = desc->ReadOptionalExpression("timeout scaling", Expression::kOne); if (not scale_exp) { return; } auto scale_val = scale_exp.Evaluate( param_config, string_fields_fcts, [&logger](auto const& msg) { (*logger)(fmt::format("While evaluating timeout scaling:\n{}", msg), true); }); if (not scale_val) { return; } if (not(scale_val->IsNumber() or scale_val->IsNone())) { (*logger)(fmt::format("timeout scaling has evaluate to a number (or " "null for default), but found {}", scale_val->ToString()), true); return; } auto props_exp = desc->ReadOptionalExpression("execution properties", Expression::kEmptyMapExpr); if (not props_exp) { return; } auto props_val = props_exp.Evaluate( param_config, string_fields_fcts, [&logger](auto const& msg) { (*logger)( fmt::format("While evaluating execution properties:\n{}", msg), true); }); if (not props_val) { return; } if (props_val->IsNone()) { props_val = Expression::kEmptyMap; } if (not props_val->IsMap()) { (*logger)(fmt::format("execution properties has to evaluate to a map " "(or null for default), but found {}", props_val->ToString()), true); return; } for (auto const& [prop_name, prop_val] : props_val->Map()) { if (not prop_val->IsString()) { (*logger)( fmt::format("execution properties has to evaluate to a map (or " "null for default), but found {} for key {}", nlohmann::json(prop_name).dump(), prop_val->ToString()), true); return; } } // Construct inputs; in case of conflicts, artifacts take precedence // over runfiles. auto inputs = ExpressionPtr{Expression::map_t{}}; for (auto const& dep : dependency_values) { inputs = ExpressionPtr{Expression::map_t{inputs, (*dep)->RunFiles()}}; } for (auto const& dep : dependency_values) { inputs = ExpressionPtr{Expression::map_t{inputs, (*dep)->Artifacts()}}; } auto inputs_conflict = BuildMaps::Target::Utils::tree_conflict(inputs); // While syntactical conflicts are resolved in a latest wins (with artifacts // after runfiles), semantic path conflicts are an error. if (inputs_conflict) { (*logger)(fmt::format("Input artifacts have staging conflict on {}", nlohmann::json(*inputs_conflict).dump()), /*fatal=*/true); return; } std::vector trees{}; inputs = BuildMaps::Target::Utils::add_dir_for( cwd_value->String(), inputs, &trees); std::vector argv{}; argv.reserve(sh_val->List().size() + 1); for (auto const& entry : sh_val->List()) { argv.emplace_back(entry->String()); } argv.emplace_back(cmd_ss.str()); // Construct our single action, and its artifacts auto action = BuildMaps::Target::Utils::createAction( outs, out_dirs, argv, cwd_value->String(), env_val, std::nullopt, false, scale_val->IsNumber() ? scale_val->Number() : 1.0, props_val, inputs); auto action_identifier = action->Id(); Expression::map_t::underlying_map_t artifacts; for (const auto& container : {outs, out_dirs}) { for (const auto& path : container) { artifacts.emplace( path, ExpressionPtr{ArtifactDescription::CreateAction( action_identifier, std::filesystem::path{path})}); } } auto artifacts_stage = ExpressionPtr{Expression::map_t{artifacts}}; auto artifacts_conflict = BuildMaps::Target::Utils::tree_conflict(artifacts_stage); if (artifacts_conflict) { (*logger)(fmt::format("artifacts have staging conflicts on {}", nlohmann::json(*artifacts_conflict).dump()), /*fatal=*/true); return; } auto const& empty_map = Expression::kEmptyMap; auto result = std::make_shared( TargetResult{.artifact_stage = std::move(artifacts_stage), .provides = empty_map, .runfiles = empty_map}, std::vector{action}, std::vector{}, std::move(trees), std::vector{}, std::move(effective_vars), std::move(tainted), std::move(implied_export), std::move(deps_info)); result = result_map->Add(key.target, effective_conf, std::move(result)); (*setter)(std::move(result)); } void GenericRule( const gsl::not_null& context, const nlohmann::json& desc_json, const BuildMaps::Target::ConfiguredTarget& key, const BuildMaps::Target::TargetMap::SubCallerPtr& subcaller, const BuildMaps::Target::TargetMap::SetterPtr& setter, const BuildMaps::Target::TargetMap::LoggerPtr& logger, const gsl::not_null& result_map) { auto desc = BuildMaps::Base::FieldReader::CreatePtr( desc_json, key.target, "generic target", logger); desc->ExpectFields(kGenericRuleFields); auto param_vars = desc->ReadStringList("arguments_config"); if (not param_vars) { return; } auto param_config = key.config.Prune(*param_vars); auto const& empty_list = Expression::kEmptyList; auto deps_exp = desc->ReadOptionalExpression("deps", empty_list); if (not deps_exp) { return; } auto deps_value = deps_exp.Evaluate(param_config, {}, [&logger](auto const& msg) { (*logger)(fmt::format("While evaluating deps:\n{}", msg), true); }); if (not deps_value->IsList()) { (*logger)(fmt::format("Expected deps to evaluate to a list of targets, " "but found {}", deps_value->ToString()), true); return; } std::vector dependency_keys; std::vector transition_keys; dependency_keys.reserve(deps_value->List().size()); transition_keys.reserve(deps_value->List().size()); auto empty_transition = Configuration{Expression::kEmptyMap}; for (auto const& dep_name : deps_value->List()) { auto dep_target = BuildMaps::Base::ParseEntityNameFromExpression( dep_name, key.target, context->repo_config, [&logger, &dep_name](std::string const& parse_err) { (*logger)(fmt::format("Parsing dep entry {} failed with:\n{}", dep_name->ToString(), parse_err), true); }); if (not dep_target) { return; } dependency_keys.emplace_back( BuildMaps::Target::ConfiguredTarget{*dep_target, key.config}); transition_keys.emplace_back( BuildMaps::Target::ConfiguredTarget{*dep_target, empty_transition}); } (*subcaller)( dependency_keys, [transition_keys = std::move(transition_keys), desc, setter, logger, key, context, result_map](auto const& values) { GenericRuleWithDeps(transition_keys, values, desc, key, context->repo_config, setter, logger, result_map); }, logger); } void ConfigureRule( const gsl::not_null& context, const nlohmann::json& desc_json, const BuildMaps::Target::ConfiguredTarget& key, const BuildMaps::Target::TargetMap::SubCallerPtr& subcaller, const BuildMaps::Target::TargetMap::SetterPtr& setter, const BuildMaps::Target::TargetMap::LoggerPtr& logger, const gsl::not_null& result_map) { auto desc = BuildMaps::Base::FieldReader::CreatePtr( desc_json, key.target, "configure target", logger); desc->ExpectFields(kConfigureRuleFields); auto param_vars = desc->ReadStringList("arguments_config"); if (not param_vars) { return; } auto param_config = key.config.Prune(*param_vars); auto configured_target_name_exp = desc->ReadExpression("target"); if (not configured_target_name_exp) { return; } auto configured_target_name = configured_target_name_exp.Evaluate( param_config, {}, [logger](std::string const& msg) { (*logger)( fmt::format("Evaluating 'target' failed with error:\n{}", msg), true); }); if (not configured_target_name) { return; } auto configured_target = BuildMaps::Base::ParseEntityNameFromExpression( configured_target_name, key.target, context->repo_config, [&logger, &configured_target_name](std::string const& parse_err) { (*logger)(fmt::format("Parsing target name {} failed with:\n{}", configured_target_name->ToString(), parse_err), true); }); if (not configured_target) { return; } // Compute and verify taintedness auto tainted = std::set{}; auto got_tainted = BuildMaps::Target::Utils::getTainted( &tainted, param_config, desc->ReadOptionalExpression("tainted", Expression::kEmptyList), logger); if (not got_tainted) { return; } auto eval_config = desc->ReadOptionalExpression("config", Expression::kEmptyMapExpr); if (not eval_config->IsMap()) { (*logger)(fmt::format("eval_config has to be an expr, but found {}", eval_config->ToString()), true); return; } eval_config = eval_config.Evaluate( param_config, {}, [logger](std::string const& msg) { (*logger)( fmt::format("Evaluating 'config' failed with error:\n{}", msg), true); }); if (not eval_config) { return; } if (not eval_config->IsMap()) { (*logger)(fmt::format("'config' must evaluate to map, but found {}", eval_config->ToString()), true); return; } auto target_config = key.config.Update(eval_config); auto target_to_configure = BuildMaps::Target::ConfiguredTarget{ std::move(*configured_target), std::move(target_config)}; (*subcaller)( {std::move(target_to_configure)}, [setter, logger, vars = std::move(*param_vars), result_map, transition = Configuration{std::move(eval_config)}, tainted = std::move(tainted), key](auto const& values) { auto& configured_target = *values[0]; if (not std::includes(tainted.begin(), tainted.end(), configured_target->Tainted().begin(), configured_target->Tainted().end())) { (*logger)( "Not tainted with all strings the dependencies are tainted " "with", true); return; } std::unordered_set vars_set{}; for (auto const& x : configured_target->Vars()) { if (not transition.VariableFixed(x)) { vars_set.insert(x); } } vars_set.insert(vars.begin(), vars.end()); auto effective_conf = key.config.Prune(vars_set); auto deps_info = TargetGraphInformation{ std::make_shared( BuildMaps::Target::ConfiguredTarget{key.target, effective_conf}), {configured_target->GraphInformation().Node()}, {}, {}}; auto analysis_result = std::make_shared( configured_target->Result(), std::vector{}, std::vector{}, std::vector{}, std::vector{}, std::move(vars_set), tainted, configured_target->ImpliedExport(), std::move(deps_info)); analysis_result = result_map->Add(key.target, std::move(effective_conf), std::move(analysis_result)); (*setter)(std::move(analysis_result)); }, logger); } auto const kBuiltIns = std::unordered_map< std::string, std::function&, const nlohmann::json&, const BuildMaps::Target::ConfiguredTarget&, const BuildMaps::Target::TargetMap::SubCallerPtr&, const BuildMaps::Target::TargetMap::SetterPtr&, const BuildMaps::Target::TargetMap::LoggerPtr&, const gsl::not_null&)>>{ {"export", ExportRule}, {"file_gen", FileGenRule}, {"tree", TreeRule}, {"tree_overlay", TreeOverlayRule}, {"disjoint_tree_overlay", DisjointTreeOverlayRule}, {"symlink", SymlinkRule}, {"generic", GenericRule}, {"install", InstallRule}, {"configure", ConfigureRule}}; } // namespace namespace BuildMaps::Target { auto IsBuiltInRule(nlohmann::json const& rule_type) -> bool { if (not rule_type.is_string()) { // Names for built-in rules are always strings return false; } auto rule_name = rule_type.get(); return kBuiltIns.contains(rule_name); } auto HandleBuiltin(const gsl::not_null& context, const nlohmann::json& rule_type, const nlohmann::json& desc, const BuildMaps::Target::ConfiguredTarget& key, const BuildMaps::Target::TargetMap::SubCallerPtr& subcaller, const BuildMaps::Target::TargetMap::SetterPtr& setter, const BuildMaps::Target::TargetMap::LoggerPtr& logger, const gsl::not_null& result_map) -> bool { if (not rule_type.is_string()) { // Names for built-in rules are always strings return false; } auto rule_name = rule_type.get(); auto it = kBuiltIns.find(rule_name); if (it == kBuiltIns.end()) { return false; } auto target_logger = std::make_shared( [logger, rule_name, key](auto msg, auto fatal) { (*logger)(fmt::format( "While evaluating {} target {}:\n{}", rule_name, key.ToShortString(Evaluator::GetExpressionLogLimit()), msg), fatal); }); (it->second)( context, desc, key, subcaller, setter, target_logger, result_map); return true; } } // namespace BuildMaps::Target