diff options
Diffstat (limited to 'src/buildtool/build_engine/target_map/target_map.cpp')
-rw-r--r-- | src/buildtool/build_engine/target_map/target_map.cpp | 1338 |
1 files changed, 1338 insertions, 0 deletions
diff --git a/src/buildtool/build_engine/target_map/target_map.cpp b/src/buildtool/build_engine/target_map/target_map.cpp new file mode 100644 index 00000000..327dbe02 --- /dev/null +++ b/src/buildtool/build_engine/target_map/target_map.cpp @@ -0,0 +1,1338 @@ +#include "src/buildtool/build_engine/target_map/target_map.hpp" + +#include <algorithm> +#include <memory> +#include <set> +#include <string> +#include <utility> + +#include "nlohmann/json.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/function_map.hpp" +#include "src/buildtool/build_engine/target_map/built_in_rules.hpp" +#include "src/buildtool/build_engine/target_map/utils.hpp" + +namespace { + +using namespace std::string_literals; + +[[nodiscard]] auto ReadActionOutputExpr(ExpressionPtr const& out_exp, + std::string const& field_name) + -> ActionDescription::outputs_t { + if (not out_exp->IsList()) { + throw Evaluator::EvaluationError{ + fmt::format("{} has to be a list of strings, but found {}", + field_name, + out_exp->ToString())}; + } + ActionDescription::outputs_t outputs; + outputs.reserve(out_exp->List().size()); + for (auto const& out_path : out_exp->List()) { + if (not out_path->IsString()) { + throw Evaluator::EvaluationError{ + fmt::format("{} has to be a list of strings, but found {}", + field_name, + out_exp->ToString())}; + } + outputs.emplace_back(out_path->String()); + } + return outputs; +} + +struct TargetData { + using Ptr = std::shared_ptr<TargetData>; + + std::vector<std::string> target_vars; + std::unordered_map<std::string, ExpressionPtr> config_exprs; + std::unordered_map<std::string, ExpressionPtr> string_exprs; + std::unordered_map<std::string, ExpressionPtr> target_exprs; + ExpressionPtr tainted_expr; + bool parse_target_names{}; + + TargetData(std::vector<std::string> target_vars, + std::unordered_map<std::string, ExpressionPtr> config_exprs, + std::unordered_map<std::string, ExpressionPtr> string_exprs, + std::unordered_map<std::string, ExpressionPtr> target_exprs, + ExpressionPtr tainted_expr, + bool parse_target_names) + : target_vars{std::move(target_vars)}, + config_exprs{std::move(config_exprs)}, + string_exprs{std::move(string_exprs)}, + target_exprs{std::move(target_exprs)}, + tainted_expr{std::move(tainted_expr)}, + parse_target_names{parse_target_names} {} + + [[nodiscard]] static auto FromFieldReader( + BuildMaps::Base::UserRulePtr const& rule, + BuildMaps::Base::FieldReader::Ptr const& desc) -> TargetData::Ptr { + desc->ExpectFields(rule->ExpectedFields()); + + auto target_vars = desc->ReadStringList("arguments_config"); + auto tainted_expr = + desc->ReadOptionalExpression("tainted", Expression::kEmptyList); + + auto convert_to_exprs = + [&desc](gsl::not_null< + std::unordered_map<std::string, ExpressionPtr>*> const& + expr_map, + std::vector<std::string> const& field_names) -> bool { + for (auto const& field_name : field_names) { + auto expr = desc->ReadOptionalExpression( + field_name, Expression::kEmptyList); + if (not expr) { + return false; + } + expr_map->emplace(field_name, std::move(expr)); + } + return true; + }; + + std::unordered_map<std::string, ExpressionPtr> config_exprs; + std::unordered_map<std::string, ExpressionPtr> string_exprs; + std::unordered_map<std::string, ExpressionPtr> target_exprs; + if (target_vars and tainted_expr and + convert_to_exprs(&config_exprs, rule->ConfigFields()) and + convert_to_exprs(&string_exprs, rule->StringFields()) and + convert_to_exprs(&target_exprs, rule->TargetFields())) { + return std::make_shared<TargetData>(std::move(*target_vars), + std::move(config_exprs), + std::move(string_exprs), + std::move(target_exprs), + std::move(tainted_expr), + /*parse_target_names=*/true); + } + return nullptr; + } + + [[nodiscard]] static auto FromTargetNode( + BuildMaps::Base::UserRulePtr const& rule, + TargetNode::Abstract const& node, + ExpressionPtr const& rule_map, + gsl::not_null<AsyncMapConsumerLoggerPtr> const& logger) + -> TargetData::Ptr { + + auto const& string_fields = node.string_fields->Map(); + auto const& target_fields = node.target_fields->Map(); + + std::unordered_map<std::string, ExpressionPtr> config_exprs; + std::unordered_map<std::string, ExpressionPtr> string_exprs; + std::unordered_map<std::string, ExpressionPtr> target_exprs; + + for (auto const& field_name : rule->ConfigFields()) { + if (target_fields.Find(field_name)) { + (*logger)( + fmt::format( + "Expected config field '{}' in string_fields of " + "abstract node type '{}', and not in target_fields", + field_name, + node.node_type), + /*fatal=*/true); + return nullptr; + } + auto const& config_expr = + string_fields.Find(field_name) + .value_or(std::reference_wrapper{Expression::kEmptyList}) + .get(); + config_exprs.emplace(field_name, config_expr); + } + + for (auto const& field_name : rule->StringFields()) { + if (target_fields.Find(field_name)) { + (*logger)( + fmt::format( + "Expected string field '{}' in string_fields of " + "abstract node type '{}', and not in target_fields", + field_name, + node.node_type), + /*fatal=*/true); + return nullptr; + } + auto const& string_expr = + string_fields.Find(field_name) + .value_or(std::reference_wrapper{Expression::kEmptyList}) + .get(); + string_exprs.emplace(field_name, string_expr); + } + + for (auto const& field_name : rule->TargetFields()) { + if (string_fields.Find(field_name)) { + (*logger)( + fmt::format( + "Expected target field '{}' in target_fields of " + "abstract node type '{}', and not in string_fields", + field_name, + node.node_type), + /*fatal=*/true); + return nullptr; + } + auto const& target_expr = + target_fields.Find(field_name) + .value_or(std::reference_wrapper{Expression::kEmptyList}) + .get(); + auto const& nodes = target_expr->List(); + Expression::list_t targets{}; + targets.reserve(nodes.size()); + for (auto const& node_expr : nodes) { + targets.emplace_back(ExpressionPtr{BuildMaps::Base::EntityName{ + BuildMaps::Base::AnonymousTarget{rule_map, node_expr}}}); + } + target_exprs.emplace(field_name, targets); + } + + return std::make_shared<TargetData>(std::vector<std::string>{}, + std::move(config_exprs), + std::move(string_exprs), + std::move(target_exprs), + Expression::kEmptyList, + /*parse_target_names=*/false); + } +}; + +void withDependencies( + const std::vector<BuildMaps::Target::ConfiguredTarget>& transition_keys, + const std::vector<AnalysedTargetPtr const*>& dependency_values, + const BuildMaps::Base::UserRulePtr& rule, + const TargetData::Ptr& data, + const BuildMaps::Target::ConfiguredTarget& key, + std::unordered_map<std::string, ExpressionPtr> params, + const BuildMaps::Target::TargetMap::SetterPtr& setter, + const BuildMaps::Target::TargetMap::LoggerPtr& logger, + const gsl::not_null<BuildMaps::Target::ResultTargetMap*>& result_map) { + // Associate dependency keys with values + std::unordered_map<BuildMaps::Target::ConfiguredTarget, AnalysedTargetPtr> + deps_by_transition; + deps_by_transition.reserve(transition_keys.size()); + for (size_t i = 0; i < transition_keys.size(); ++i) { + deps_by_transition.emplace(transition_keys[i], *dependency_values[i]); + } + + // Compute the effective dependecy on config variables + std::unordered_set<std::string> effective_vars; + auto const& param_vars = data->target_vars; + effective_vars.insert(param_vars.begin(), param_vars.end()); + auto const& config_vars = rule->ConfigVars(); + effective_vars.insert(config_vars.begin(), config_vars.end()); + for (auto const& [transition, target] : deps_by_transition) { + for (auto const& x : target->Vars()) { + if (not transition.config.VariableFixed(x)) { + effective_vars.insert(x); + } + } + } + auto effective_conf = key.config.Prune(effective_vars); + + // Compute and verify taintedness + auto tainted = std::set<std::string>{}; + auto got_tainted = BuildMaps::Target::Utils::getTainted( + &tainted, key.config.Prune(param_vars), data->tainted_expr, logger); + if (not got_tainted) { + return; + } + tainted.insert(rule->Tainted().begin(), rule->Tainted().end()); + 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; + } + } + + // Evaluate string parameters + auto string_fields_fcts = + FunctionMap::MakePtr(FunctionMap::underlying_map_t{ + {"outs", + [&deps_by_transition, &key]( + auto&& eval, auto const& expr, auto const& env) { + return BuildMaps::Target::Utils::keys_expr( + BuildMaps::Target::Utils::obtainTargetByName( + eval, expr, env, key.target, deps_by_transition) + ->Artifacts()); + }}, + {"runfiles", + [&deps_by_transition, &key]( + auto&& eval, auto const& expr, auto const& env) { + return BuildMaps::Target::Utils::keys_expr( + BuildMaps::Target::Utils::obtainTargetByName( + eval, expr, env, key.target, deps_by_transition) + ->RunFiles()); + }}}); + auto param_config = key.config.Prune(param_vars); + params.reserve(params.size() + rule->StringFields().size()); + for (auto const& field_name : rule->StringFields()) { + auto const& field_exp = data->string_exprs[field_name]; + auto field_value = field_exp.Evaluate( + param_config, + string_fields_fcts, + [&logger, &field_name](auto const& msg) { + (*logger)(fmt::format("While evaluating string field {}:\n{}", + field_name, + msg), + true); + }); + if (not field_value) { + return; + } + if (not field_value->IsList()) { + (*logger)(fmt::format("String field {} should be a list of " + "strings, but found {}", + field_name, + field_value->ToString()), + true); + return; + } + for (auto const& entry : field_value->List()) { + if (not entry->IsString()) { + (*logger)(fmt::format("String field {} should be a list of " + "strings, but found entry {}", + field_name, + entry->ToString()), + true); + return; + } + } + params.emplace(field_name, std::move(field_value)); + } + + // Evaluate main expression + auto expression_config = key.config.Prune(config_vars); + std::vector<ActionDescription> actions{}; + std::vector<std::string> blobs{}; + std::vector<Tree> trees{}; + auto main_exp_fcts = FunctionMap::MakePtr( + {{"FIELD", + [¶ms](auto&& eval, auto const& expr, auto const& env) { + auto name = eval(expr["name"], env); + if (not name->IsString()) { + throw Evaluator::EvaluationError{ + fmt::format("FIELD argument 'name' should evaluate to a " + "string, but got {}", + name->ToString())}; + } + auto it = params.find(name->String()); + if (it == params.end()) { + throw Evaluator::EvaluationError{ + fmt::format("FIELD '{}' unknown", name->String())}; + } + return it->second; + }}, + {"DEP_ARTIFACTS", + [&deps_by_transition]( + auto&& eval, auto const& expr, auto const& env) { + return BuildMaps::Target::Utils::obtainTarget( + eval, expr, env, deps_by_transition) + ->Artifacts(); + }}, + {"DEP_RUNFILES", + [&deps_by_transition]( + auto&& eval, auto const& expr, auto const& env) { + return BuildMaps::Target::Utils::obtainTarget( + eval, expr, env, deps_by_transition) + ->RunFiles(); + }}, + {"DEP_PROVIDES", + [&deps_by_transition]( + auto&& eval, auto const& expr, auto const& env) { + auto const& provided = BuildMaps::Target::Utils::obtainTarget( + eval, expr, env, deps_by_transition) + ->Provides(); + auto provider = eval(expr["provider"], env); + auto provided_value = provided->At(provider->String()); + if (provided_value) { + return provided_value->get(); + } + auto const& empty_list = Expression::kEmptyList; + return eval(expr->Get("default", empty_list), env); + }}, + {"ACTION", + [&actions, &rule](auto&& eval, auto const& expr, auto const& env) { + auto const& empty_map_exp = Expression::kEmptyMapExpr; + auto inputs_exp = eval(expr->Get("inputs", empty_map_exp), env); + if (not inputs_exp->IsMap()) { + throw Evaluator::EvaluationError{fmt::format( + "inputs has to be a map of artifacts, but found {}", + inputs_exp->ToString())}; + } + for (auto const& [input_path, artifact] : inputs_exp->Map()) { + if (not artifact->IsArtifact()) { + throw Evaluator::EvaluationError{ + fmt::format("inputs has to be a map of Artifacts, " + "but found {} for {}", + artifact->ToString(), + input_path)}; + } + } + auto conflict = + BuildMaps::Target::Utils::tree_conflict(inputs_exp); + if (conflict) { + throw Evaluator::EvaluationError{ + fmt::format("inputs conflicts on subtree {}", *conflict)}; + } + + Expression::map_t::underlying_map_t result; + auto outputs = ReadActionOutputExpr( + eval(expr->Get("outs", Expression::list_t{}), env), "outs"); + auto output_dirs = ReadActionOutputExpr( + eval(expr->Get("out_dirs", Expression::list_t{}), env), + "out_dirs"); + if (outputs.empty() and output_dirs.empty()) { + throw Evaluator::EvaluationError{ + "either outs or out_dirs must be specified for ACTION"}; + } + + std::sort(outputs.begin(), outputs.end()); + std::sort(output_dirs.begin(), output_dirs.end()); + std::vector<std::string> dups{}; + std::set_intersection(outputs.begin(), + outputs.end(), + output_dirs.begin(), + output_dirs.end(), + std::back_inserter(dups)); + if (not dups.empty()) { + throw Evaluator::EvaluationError{ + "outs and out_dirs for ACTION must be disjoint"}; + } + + std::vector<std::string> cmd; + auto cmd_exp = eval(expr->Get("cmd", Expression::list_t{}), env); + if (not cmd_exp->IsList()) { + throw Evaluator::EvaluationError{fmt::format( + "cmd has to be a list of strings, but found {}", + cmd_exp->ToString())}; + } + if (cmd_exp->List().empty()) { + throw Evaluator::EvaluationError{ + "cmd must not be an empty list"}; + } + cmd.reserve(cmd_exp->List().size()); + for (auto const& arg : cmd_exp->List()) { + if (not arg->IsString()) { + throw Evaluator::EvaluationError{fmt::format( + "cmd has to be a list of strings, but found {}", + cmd_exp->ToString())}; + } + cmd.emplace_back(arg->String()); + } + auto env_exp = eval(expr->Get("env", empty_map_exp), env); + if (not env_exp->IsMap()) { + throw Evaluator::EvaluationError{ + fmt::format("env has to be a map of string, but found {}", + env_exp->ToString())}; + } + for (auto const& [env_var, env_value] : env_exp->Map()) { + if (not env_value->IsString()) { + throw Evaluator::EvaluationError{fmt::format( + "env has to be a map of string, but found {}", + env_exp->ToString())}; + } + } + auto may_fail_exp = expr->Get("may_fail", Expression::list_t{}); + if (not may_fail_exp->IsList()) { + throw Evaluator::EvaluationError{ + fmt::format("may_fail has to be a list of " + "strings, but found {}", + may_fail_exp->ToString())}; + } + for (auto const& entry : may_fail_exp->List()) { + if (not entry->IsString()) { + throw Evaluator::EvaluationError{ + fmt::format("may_fail has to be a list of " + "strings, but found {}", + may_fail_exp->ToString())}; + } + if (rule->Tainted().find(entry->String()) == + rule->Tainted().end()) { + throw Evaluator::EvaluationError{ + fmt::format("may_fail contains entry {} the the rule " + "is not tainted with", + entry->ToString())}; + } + } + std::optional<std::string> may_fail = std::nullopt; + if (not may_fail_exp->List().empty()) { + auto fail_msg = + eval(expr->Get("fail_message", "action failed"s), env); + if (not fail_msg->IsString()) { + throw Evaluator::EvaluationError{fmt::format( + "fail_message has to evalute to a string, but got {}", + fail_msg->ToString())}; + } + may_fail = std::optional{fail_msg->String()}; + } + auto no_cache_exp = expr->Get("no_cache", Expression::list_t{}); + if (not no_cache_exp->IsList()) { + throw Evaluator::EvaluationError{ + fmt::format("no_cache has to be a list of" + "strings, but found {}", + no_cache_exp->ToString())}; + } + for (auto const& entry : no_cache_exp->List()) { + if (not entry->IsString()) { + throw Evaluator::EvaluationError{ + fmt::format("no_cache has to be a list of" + "strings, but found {}", + no_cache_exp->ToString())}; + } + if (rule->Tainted().find(entry->String()) == + rule->Tainted().end()) { + throw Evaluator::EvaluationError{ + fmt::format("no_cache contains entry {} the the rule " + "is not tainted with", + entry->ToString())}; + } + } + bool no_cache = not no_cache_exp->List().empty(); + auto action = + BuildMaps::Target::Utils::createAction(outputs, + output_dirs, + std::move(cmd), + env_exp, + may_fail, + no_cache, + inputs_exp); + auto action_id = action.Id(); + actions.emplace_back(std::move(action)); + for (auto const& out : outputs) { + result.emplace(out, + ExpressionPtr{ArtifactDescription{ + action_id, std::filesystem::path{out}}}); + } + for (auto const& out : output_dirs) { + result.emplace(out, + ExpressionPtr{ArtifactDescription{ + action_id, std::filesystem::path{out}}}); + } + + return ExpressionPtr{Expression::map_t{result}}; + }}, + {"BLOB", + [&blobs](auto&& eval, auto const& expr, auto const& env) { + auto data = eval(expr->Get("data", ""s), env); + if (not data->IsString()) { + throw Evaluator::EvaluationError{ + fmt::format("BLOB data has to be a string, but got {}", + data->ToString())}; + } + blobs.emplace_back(data->String()); + return ExpressionPtr{ArtifactDescription{ + {ComputeHash(data->String()), data->String().size()}, + ObjectType::File}}; + }}, + {"TREE", + [&trees](auto&& eval, auto const& expr, auto const& env) { + auto val = eval(expr->Get("$1", Expression::kEmptyMapExpr), env); + if (not val->IsMap()) { + throw Evaluator::EvaluationError{ + fmt::format("TREE argument has to be a map of artifacts, " + "but found {}", + val->ToString())}; + } + std::unordered_map<std::string, ArtifactDescription> artifacts; + artifacts.reserve(val->Map().size()); + for (auto const& [input_path, artifact] : val->Map()) { + if (not artifact->IsArtifact()) { + throw Evaluator::EvaluationError{fmt::format( + "TREE argument has to be a map of artifacts, " + "but found {} for {}", + artifact->ToString(), + input_path)}; + } + auto norm_path = std::filesystem::path{input_path} + .lexically_normal() + .string(); + if (norm_path == "." or norm_path.empty()) { + if (val->Map().size() > 1) { + throw Evaluator::EvaluationError{ + "input path '.' or '' for TREE is only allowed " + "for trees with single input artifact"}; + } + if (not artifact->Artifact().IsTree()) { + throw Evaluator::EvaluationError{ + "input path '.' or '' for TREE must be tree " + "artifact"}; + } + return artifact; + } + artifacts.emplace(std::move(norm_path), artifact->Artifact()); + } + auto conflict = BuildMaps::Target::Utils::tree_conflict(val); + if (conflict) { + throw Evaluator::EvaluationError{ + fmt::format("TREE conflicts on subtree {}", *conflict)}; + } + auto tree = Tree{std::move(artifacts)}; + auto tree_id = tree.Id(); + trees.emplace_back(std::move(tree)); + return ExpressionPtr{ArtifactDescription{tree_id}}; + }}, + {"VALUE_NODE", + [](auto&& eval, auto const& expr, auto const& env) { + auto val = eval(expr->Get("$1", Expression::kNone), env); + if (not val->IsResult()) { + throw Evaluator::EvaluationError{ + "argument '$1' for VALUE_NODE not a RESULT type."}; + } + return ExpressionPtr{TargetNode{std::move(val)}}; + }}, + {"ABSTRACT_NODE", + [](auto&& eval, auto const& expr, auto const& env) { + auto type = eval(expr->Get("node_type", Expression::kNone), env); + if (not type->IsString()) { + throw Evaluator::EvaluationError{ + "argument 'node_type' for ABSTRACT_NODE not a string."}; + } + auto string_fields = eval( + expr->Get("string_fields", Expression::kEmptyMapExpr), env); + if (not string_fields->IsMap()) { + throw Evaluator::EvaluationError{ + "argument 'string_fields' for ABSTRACT_NODE not a map."}; + } + auto target_fields = eval( + expr->Get("target_fields", Expression::kEmptyMapExpr), env); + if (not target_fields->IsMap()) { + throw Evaluator::EvaluationError{ + "argument 'target_fields' for ABSTRACT_NODE not a map."}; + } + + std::optional<std::string> dup_key{std::nullopt}; + auto check_entries = + [&dup_key](auto const& map, + auto const& type_check, + std::string const& fields_name, + std::string const& type_name, + std::optional<ExpressionPtr> const& disjoint_map = + std::nullopt) { + for (auto const& [key, list] : map->Map()) { + if (not list->IsList()) { + throw Evaluator::EvaluationError{fmt::format( + "value for key {} in argument '{}' for " + "ABSTRACT_NODE is not a list.", + key, + fields_name)}; + } + for (auto const& entry : list->List()) { + if (not type_check(entry)) { + throw Evaluator::EvaluationError{fmt::format( + "list entry for {} in argument '{}' for " + "ABSTRACT_NODE is not a {}:\n{}", + key, + fields_name, + type_name, + entry->ToString())}; + } + } + if (disjoint_map) { + if ((*disjoint_map)->Map().Find(key)) { + dup_key = key; + return; + } + } + } + }; + + auto is_string = [](auto const& e) { return e->IsString(); }; + check_entries(string_fields, + is_string, + "string_fields", + "string", + target_fields); + if (dup_key) { + throw Evaluator::EvaluationError{ + fmt::format("string_fields and target_fields are not " + "disjoint maps, found duplicate key: {}.", + *dup_key)}; + } + + auto is_node = [](auto const& e) { return e->IsNode(); }; + check_entries( + target_fields, is_node, "target_fields", "target node"); + + return ExpressionPtr{ + TargetNode{TargetNode::Abstract{type->String(), + std::move(string_fields), + std::move(target_fields)}}}; + }}, + {"RESULT", [](auto&& eval, auto const& expr, auto const& env) { + auto const& empty_map_exp = Expression::kEmptyMapExpr; + auto artifacts = eval(expr->Get("artifacts", empty_map_exp), env); + auto runfiles = eval(expr->Get("runfiles", empty_map_exp), env); + auto provides = eval(expr->Get("provides", empty_map_exp), env); + if (not artifacts->IsMap()) { + throw Evaluator::EvaluationError{fmt::format( + "artifacts has to be a map of artifacts, but found {}", + artifacts->ToString())}; + } + for (auto const& [path, entry] : artifacts->Map()) { + if (not entry->IsArtifact()) { + throw Evaluator::EvaluationError{ + fmt::format("artifacts has to be a map of artifacts, " + "but found {} for {}", + entry->ToString(), + path)}; + } + } + if (not runfiles->IsMap()) { + throw Evaluator::EvaluationError{fmt::format( + "runfiles has to be a map of artifacts, but found {}", + runfiles->ToString())}; + } + for (auto const& [path, entry] : runfiles->Map()) { + if (not entry->IsArtifact()) { + throw Evaluator::EvaluationError{ + fmt::format("runfiles has to be a map of artifacts, " + "but found {} for {}", + entry->ToString(), + path)}; + } + } + if (not provides->IsMap()) { + throw Evaluator::EvaluationError{ + fmt::format("provides has to be a map, but found {}", + provides->ToString())}; + } + return ExpressionPtr{TargetResult{artifacts, provides, runfiles}}; + }}}); + + auto result = rule->Expression()->Evaluate( + expression_config, main_exp_fcts, [logger](auto const& msg) { + (*logger)( + fmt::format("While evaluating defining expression of rule:\n{}", + msg), + true); + }); + if (not result) { + return; + } + if (not result->IsResult()) { + (*logger)(fmt::format("Defining expression should evaluate to a " + "RESULT, but got: {}", + result->ToString()), + true); + return; + } + auto analysis_result = + std::make_shared<AnalysedTarget>((*std::move(result)).Result(), + std::move(actions), + std::move(blobs), + std::move(trees), + std::move(effective_vars), + std::move(tainted)); + analysis_result = + result_map->Add(key.target, effective_conf, std::move(analysis_result)); + (*setter)(std::move(analysis_result)); +} + +[[nodiscard]] auto isTransition( + const ExpressionPtr& ptr, + std::function<void(std::string const&)> const& logger) -> bool { + if (not ptr->IsList()) { + logger(fmt::format("expected list, but got {}", ptr->ToString())); + return false; + } + for (const auto& entry : ptr->List()) { + if (not entry->IsMap()) { + logger(fmt::format("expected list of dicts, but found {}", + ptr->ToString())); + return false; + } + } + + return true; +} + +void withRuleDefinition( + const BuildMaps::Base::UserRulePtr& rule, + const TargetData::Ptr& data, + 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<BuildMaps::Target::ResultTargetMap*> result_map) { + auto param_config = key.config.Prune(data->target_vars); + + // Evaluate the config_fields + + std::unordered_map<std::string, ExpressionPtr> params; + params.reserve(rule->ConfigFields().size() + rule->TargetFields().size() + + rule->ImplicitTargetExps().size()); + for (auto field_name : rule->ConfigFields()) { + auto const& field_expression = data->config_exprs[field_name]; + auto field_value = field_expression.Evaluate( + param_config, {}, [&logger, &field_name](auto const& msg) { + (*logger)(fmt::format("While evaluating config fieled {}:\n{}", + field_name, + msg), + true); + }); + if (not field_value) { + return; + } + if (not field_value->IsList()) { + (*logger)(fmt::format("Config field {} should evaluate to a list " + "of strings, but got{}", + field_name, + field_value->ToString()), + true); + return; + } + for (auto const& entry : field_value->List()) { + if (not entry->IsString()) { + (*logger)(fmt::format("Config field {} should evaluate to a " + "list of strings, but got{}", + field_name, + field_value->ToString()), + true); + return; + } + } + params.emplace(field_name, field_value); + } + + // Evaluate config transitions + + auto config_trans_fcts = FunctionMap::MakePtr( + "FIELD", [¶ms](auto&& eval, auto const& expr, auto const& env) { + auto name = eval(expr["name"], env); + if (not name->IsString()) { + throw Evaluator::EvaluationError{ + fmt::format("FIELD argument 'name' should evaluate to a " + "string, but got {}", + name->ToString())}; + } + auto it = params.find(name->String()); + if (it == params.end()) { + throw Evaluator::EvaluationError{ + fmt::format("FIELD {} unknown", name->String())}; + } + return it->second; + }); + + auto const& config_vars = rule->ConfigVars(); + auto expression_config = key.config.Prune(config_vars); + + std::unordered_map<std::string, ExpressionPtr> config_transitions; + config_transitions.reserve(rule->TargetFields().size() + + rule->ImplicitTargets().size() + + rule->AnonymousDefinitions().size()); + for (auto const& target_field_name : rule->TargetFields()) { + auto exp = rule->ConfigTransitions().at(target_field_name); + auto transition_logger = [&logger, + &target_field_name](auto const& msg) { + (*logger)( + fmt::format("While evaluating config transition for {}:\n{}", + target_field_name, + msg), + true); + }; + auto transition = exp->Evaluate( + expression_config, config_trans_fcts, transition_logger); + if (not transition) { + return; + } + if (not isTransition(transition, transition_logger)) { + return; + } + config_transitions.emplace(target_field_name, transition); + } + for (const auto& name_value : rule->ImplicitTargets()) { + auto implicit_field_name = name_value.first; + auto exp = rule->ConfigTransitions().at(implicit_field_name); + auto transition_logger = [&logger, + &implicit_field_name](auto const& msg) { + (*logger)(fmt::format("While evaluating config transition for " + "implicit {}:\n{}", + implicit_field_name, + msg), + true); + }; + auto transition = exp->Evaluate( + expression_config, config_trans_fcts, transition_logger); + if (not transition) { + return; + } + if (not isTransition(transition, transition_logger)) { + return; + } + config_transitions.emplace(implicit_field_name, transition); + } + for (const auto& entry : rule->AnonymousDefinitions()) { + auto const& anon_field_name = entry.first; + auto exp = rule->ConfigTransitions().at(anon_field_name); + auto transition_logger = [&logger, &anon_field_name](auto const& msg) { + (*logger)(fmt::format("While evaluating config transition for " + "anonymous {}:\n{}", + anon_field_name, + msg), + true); + }; + auto transition = exp->Evaluate( + expression_config, config_trans_fcts, transition_logger); + if (not transition) { + return; + } + if (not isTransition(transition, transition_logger)) { + return; + } + config_transitions.emplace(anon_field_name, transition); + } + + // Request dependencies + + std::unordered_map<std::string, std::vector<std::size_t>> anon_positions; + anon_positions.reserve(rule->AnonymousDefinitions().size()); + for (auto const& [_, def] : rule->AnonymousDefinitions()) { + anon_positions.emplace(def.target, std::vector<std::size_t>{}); + } + + std::vector<BuildMaps::Target::ConfiguredTarget> dependency_keys; + std::vector<BuildMaps::Target::ConfiguredTarget> transition_keys; + for (auto target_field_name : rule->TargetFields()) { + auto const& deps_expression = data->target_exprs[target_field_name]; + auto deps_names = deps_expression.Evaluate( + param_config, {}, [logger, target_field_name](auto const& msg) { + (*logger)( + fmt::format("While evaluating target parameter {}:\n{}", + target_field_name, + msg), + true); + }); + if (not deps_names->IsList()) { + (*logger)(fmt::format("Target parameter {} should evaluate to a " + "list, but got {}", + target_field_name, + deps_names->ToString()), + true); + return; + } + Expression::list_t dep_target_exps; + if (data->parse_target_names) { + dep_target_exps.reserve(deps_names->List().size()); + for (const auto& dep_name : deps_names->List()) { + auto target = BuildMaps::Base::ParseEntityNameFromExpression( + dep_name, + key.target, + [&logger, &target_field_name, &dep_name]( + std::string const& parse_err) { + (*logger)(fmt::format("Parsing entry {} in target " + "field {} failed with:\n{}", + dep_name->ToString(), + target_field_name, + parse_err), + true); + }); + if (not target) { + return; + } + dep_target_exps.emplace_back(ExpressionPtr{*target}); + } + } + else { + dep_target_exps = deps_names->List(); + } + auto anon_pos = anon_positions.find(target_field_name); + auto const& transitions = config_transitions[target_field_name]->List(); + for (const auto& transition : transitions) { + auto transitioned_config = key.config.Update(transition); + for (const auto& dep : dep_target_exps) { + if (anon_pos != anon_positions.end()) { + anon_pos->second.emplace_back(dependency_keys.size()); + } + + dependency_keys.emplace_back( + BuildMaps::Target::ConfiguredTarget{dep->Name(), + transitioned_config}); + transition_keys.emplace_back( + BuildMaps::Target::ConfiguredTarget{ + dep->Name(), Configuration{transition}}); + } + } + params.emplace(target_field_name, + ExpressionPtr{std::move(dep_target_exps)}); + } + for (auto const& [implicit_field_name, implicit_target] : + rule->ImplicitTargets()) { + auto anon_pos = anon_positions.find(implicit_field_name); + auto transitions = config_transitions[implicit_field_name]->List(); + for (const auto& transition : transitions) { + auto transitioned_config = key.config.Update(transition); + for (const auto& dep : implicit_target) { + if (anon_pos != anon_positions.end()) { + anon_pos->second.emplace_back(dependency_keys.size()); + } + + dependency_keys.emplace_back( + BuildMaps::Target::ConfiguredTarget{dep, + transitioned_config}); + transition_keys.emplace_back( + BuildMaps::Target::ConfiguredTarget{ + dep, Configuration{transition}}); + } + } + } + params.insert(rule->ImplicitTargetExps().begin(), + rule->ImplicitTargetExps().end()); + + (*subcaller)( + dependency_keys, + [transition_keys = std::move(transition_keys), + rule, + data, + key, + params = std::move(params), + setter, + logger, + result_map, + subcaller, + config_transitions = std::move(config_transitions), + anon_positions = + std::move(anon_positions)](auto const& values) mutable { + // Now that all non-anonymous targets have been evaluated we can + // read their provides map to construct and evaluate anonymous + // targets. + std::vector<BuildMaps::Target::ConfiguredTarget> anonymous_keys; + for (auto const& [name, def] : rule->AnonymousDefinitions()) { + Expression::list_t anon_names{}; + for (auto pos : anon_positions.at(def.target)) { + auto const& provider_value = + (*values[pos])->Provides()->Map().Find(def.provider); + if (not provider_value) { + (*logger)( + fmt::format("Provider {} in {} does not exist", + def.provider, + def.target), + true); + return; + } + auto const& exprs = provider_value->get(); + if (not exprs->IsList()) { + (*logger)(fmt::format("Provider {} in {} must be list " + "of target nodes but found: {}", + def.provider, + def.target, + exprs->ToString()), + true); + return; + } + + auto const& list = exprs->List(); + anon_names.reserve(anon_names.size() + list.size()); + for (auto const& node : list) { + if (not node->IsNode()) { + (*logger)( + fmt::format("Entry in provider {} in {} must " + "be target node but found: {}", + def.provider, + def.target, + node->ToString()), + true); + return; + } + anon_names.emplace_back(BuildMaps::Base::EntityName{ + BuildMaps::Base::AnonymousTarget{def.rule_map, + node}}); + } + } + + for (const auto& transition : + config_transitions.at(name)->List()) { + auto transitioned_config = key.config.Update(transition); + for (auto const& anon : anon_names) { + anonymous_keys.emplace_back( + BuildMaps::Target::ConfiguredTarget{ + anon->Name(), transitioned_config}); + + transition_keys.emplace_back( + BuildMaps::Target::ConfiguredTarget{ + anon->Name(), Configuration{transition}}); + } + } + + params.emplace(name, ExpressionPtr{std::move(anon_names)}); + } + (*subcaller)( + anonymous_keys, + [dependency_values = values, + transition_keys = std::move(transition_keys), + rule, + data, + key, + params = std::move(params), + setter, + logger, + result_map](auto const& values) mutable { + // Join dependency values and anonymous values + dependency_values.insert( + dependency_values.end(), values.begin(), values.end()); + withDependencies(transition_keys, + dependency_values, + rule, + data, + key, + params, + setter, + logger, + result_map); + }, + logger); + }, + logger); +} + +void withTargetsFile( + const BuildMaps::Target::ConfiguredTarget& key, + const nlohmann::json& targets_file, + const gsl::not_null<BuildMaps::Base::SourceTargetMap*>& source_target, + const gsl::not_null<BuildMaps::Base::UserRuleMap*>& rule_map, + const gsl::not_null<TaskSystem*>& ts, + const BuildMaps::Target::TargetMap::SubCallerPtr& subcaller, + const BuildMaps::Target::TargetMap::SetterPtr& setter, + const BuildMaps::Target::TargetMap::LoggerPtr& logger, + const gsl::not_null<BuildMaps::Target::ResultTargetMap*> result_map) { + auto desc_it = targets_file.find(key.target.name); + if (desc_it == targets_file.end()) { + // Not a defined taraget, treat as source target + source_target->ConsumeAfterKeysReady( + ts, + {key.target}, + [setter](auto values) { (*setter)(AnalysedTargetPtr{*values[0]}); }, + [logger, target = key.target](auto const& msg, auto fatal) { + (*logger)(fmt::format("While analysing target {} as implicit " + "source target:\n{}", + target.ToString(), + msg), + fatal); + }); + } + else { + nlohmann::json desc = *desc_it; + auto rule_it = desc.find("type"); + if (rule_it == desc.end()) { + (*logger)( + fmt::format("No type specified in the definition of target {}", + key.target.ToString()), + true); + return; + } + // Handle built-in rule, if it is + auto handled_as_builtin = BuildMaps::Target::HandleBuiltin( + *rule_it, desc, key, subcaller, setter, logger, result_map); + if (handled_as_builtin) { + return; + } + + // Not a built-in rule, so has to be a user rule + auto rule_name = BuildMaps::Base::ParseEntityNameFromJson( + *rule_it, + key.target, + [&logger, &rule_it, &key](std::string const& parse_err) { + (*logger)(fmt::format("Parsing rule name {} for target {} " + "failed with:\n{}", + rule_it->dump(), + key.target.ToString(), + parse_err), + true); + }); + if (not rule_name) { + return; + } + auto desc_reader = BuildMaps::Base::FieldReader::CreatePtr( + desc, + key.target, + fmt::format("{} target", rule_name->ToString()), + logger); + if (not desc_reader) { + return; + } + rule_map->ConsumeAfterKeysReady( + ts, + {*rule_name}, + [desc = std::move(desc_reader), + subcaller, + setter, + logger, + key, + result_map, + rn = *rule_name](auto values) { + auto data = TargetData::FromFieldReader(*values[0], desc); + if (not data) { + (*logger)(fmt::format("Failed to read data from target {} " + "with rule {}", + key.target.ToString(), + rn.ToString()), + /*fatal=*/true); + return; + } + withRuleDefinition( + *values[0], + data, + key, + subcaller, + setter, + std::make_shared<AsyncMapConsumerLogger>( + [logger, target = key.target, rn](auto const& msg, + auto fatal) { + (*logger)( + fmt::format("While analysing {} target {}:\n{}", + rn.ToString(), + target.ToString(), + msg), + fatal); + }), + result_map); + }, + [logger, target = key.target](auto const& msg, auto fatal) { + (*logger)(fmt::format("While looking up rule for {}:\n{}", + target.ToString(), + msg), + fatal); + }); + } +} + +void withTargetNode( + const BuildMaps::Target::ConfiguredTarget& key, + const gsl::not_null<BuildMaps::Base::UserRuleMap*>& rule_map, + const gsl::not_null<TaskSystem*>& ts, + const BuildMaps::Target::TargetMap::SubCallerPtr& subcaller, + const BuildMaps::Target::TargetMap::SetterPtr& setter, + const BuildMaps::Target::TargetMap::LoggerPtr& logger, + const gsl::not_null<BuildMaps::Target::ResultTargetMap*> result_map) { + auto const& target_node = key.target.anonymous->target_node->Node(); + auto const& rule_mapping = key.target.anonymous->rule_map->Map(); + if (target_node.IsValue()) { + // fixed value node, create analysed target from result + auto const& val = target_node.GetValue(); + (*setter)(std::make_shared<AnalysedTarget>( + AnalysedTarget{val->Result(), {}, {}, {}, {}, {}})); + } + else { + // abstract target node, lookup rule and instantiate target + auto const& abs = target_node.GetAbstract(); + auto rule_name = rule_mapping.Find(abs.node_type); + if (not rule_name) { + (*logger)(fmt::format("Cannot resolve type of node {} via rule map " + "{}", + target_node.ToString(), + key.target.anonymous->rule_map->ToString()), + /*fatal=*/true); + } + rule_map->ConsumeAfterKeysReady( + ts, + {rule_name->get()->Name()}, + [abs, + subcaller, + setter, + logger, + key, + result_map, + rn = rule_name->get()](auto values) { + auto data = TargetData::FromTargetNode( + *values[0], abs, key.target.anonymous->rule_map, logger); + if (not data) { + (*logger)(fmt::format("Failed to read data from target {} " + "with rule {}", + key.target.ToString(), + rn->ToString()), + /*fatal=*/true); + return; + } + withRuleDefinition(*values[0], + data, + key, + subcaller, + setter, + std::make_shared<AsyncMapConsumerLogger>( + [logger, target = key.target, rn]( + auto const& msg, auto fatal) { + (*logger)( + fmt::format("While analysing {} " + "target {}:\n{}", + rn->ToString(), + target.ToString(), + msg), + fatal); + }), + result_map); + }, + [logger, target = key.target](auto const& msg, auto fatal) { + (*logger)(fmt::format("While looking up rule for {}:\n{}", + target.ToString(), + msg), + fatal); + }); + } +} + +} // namespace + +namespace BuildMaps::Target { +auto CreateTargetMap( + const gsl::not_null<BuildMaps::Base::SourceTargetMap*>& source_target_map, + const gsl::not_null<BuildMaps::Base::TargetsFileMap*>& targets_file_map, + const gsl::not_null<BuildMaps::Base::UserRuleMap*>& rule_map, + const gsl::not_null<ResultTargetMap*>& result_map, + std::size_t jobs) -> TargetMap { + auto target_reader = + [source_target_map, targets_file_map, rule_map, result_map]( + auto ts, auto setter, auto logger, auto subcaller, auto key) { + if (key.target.explicit_file_reference) { + // Not a defined target, treat as source target + source_target_map->ConsumeAfterKeysReady( + ts, + {key.target}, + [setter](auto values) { + (*setter)(AnalysedTargetPtr{*values[0]}); + }, + [logger, target = key.target](auto const& msg, auto fatal) { + (*logger)(fmt::format("While analysing target {} as " + "explicit source target:\n{}", + target.ToString(), + msg), + fatal); + }); + } + else if (key.target.IsAnonymousTarget()) { + withTargetNode( + key, rule_map, ts, subcaller, setter, logger, result_map); + } + else { + targets_file_map->ConsumeAfterKeysReady( + ts, + {key.target.ToModule()}, + [key, + source_target_map, + rule_map, + ts, + subcaller = std::move(subcaller), + setter = std::move(setter), + logger, + result_map](auto values) { + withTargetsFile(key, + *values[0], + source_target_map, + rule_map, + ts, + subcaller, + setter, + logger, + result_map); + }, + [logger, target = key.target](auto const& msg, auto fatal) { + (*logger)(fmt::format("While searching targets " + "description for {}:\n{}", + target.ToString(), + msg), + fatal); + }); + } + }; + return AsyncMapConsumer<ConfiguredTarget, AnalysedTargetPtr>(target_reader, + jobs); +} +} // namespace BuildMaps::Target |