From 4f5c9357b7e17b4fb9ecaad36c8740a05cfc1bc6 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Mon, 27 Jun 2022 13:11:06 +0200 Subject: Add support for rule-specific import phase 2 For example: import! [metadata, rule_hint=cxx.link] lib = libhello%lib{hello} --- libbuild2/file.cxx | 90 ++++++++++++++++++++++++++++++++++++++++++++++------ libbuild2/file.hxx | 29 ++++++++++------- libbuild2/file.ixx | 24 +++++++++++--- libbuild2/parser.cxx | 59 +++++++++++++++++++++++++--------- libbuild2/rule.cxx | 8 +++++ libbuild2/rule.hxx | 15 +++++++++ 6 files changed, 184 insertions(+), 41 deletions(-) diff --git a/libbuild2/file.cxx b/libbuild2/file.cxx index 03f6ccd..3374791 100644 --- a/libbuild2/file.cxx +++ b/libbuild2/file.cxx @@ -7,6 +7,7 @@ #include // left, setw() #include +#include #include #include #include @@ -2004,6 +2005,7 @@ namespace build2 const project_name& pn, const target_type& tt, const string& tn, + bool rule_hint, const char* qual = nullptr) { string pv (pn.variable ()); @@ -2025,6 +2027,10 @@ namespace build2 dr << info << "or use " << v << " configuration variable to specify " << "its " << (qual != nullptr ? qual : "") << "path"; } + + if (rule_hint) + dr << info << "or use rule_hint attribute to specify a rule that can " + << "find this target"; } // Return the processed target name as well as the project directory, if @@ -2295,7 +2301,8 @@ namespace build2 auto df = make_diag_frame ( [&proj, tt, &on] (const diag_record& dr) { - import_suggest (dr, proj, *tt, on, "alternative "); + import_suggest ( + dr, proj, *tt, on, false, "alternative "); }); md = extract_metadata (e->process_path (), @@ -2757,7 +2764,7 @@ namespace build2 pair import (scope& base, name tgt, - bool ph2, + const optional& ph2, bool opt, bool metadata, const location& loc) @@ -2822,6 +2829,7 @@ namespace build2 // if (const target* t = import (ctx, base.find_prerequisite_key (ns, loc), + *ph2, opt && !r.second /* optional */, nullopt /* metadata */, false /* existing */, @@ -2858,6 +2866,7 @@ namespace build2 const target* import (context& ctx, const prerequisite_key& pk, + const string& hint, bool opt, const optional& meta, bool exist, @@ -2865,16 +2874,72 @@ namespace build2 { tracer trace ("import"); - assert (!meta || !exist); + // Neither hint nor metadata can be requested for existing. + // + assert (!exist || (!meta && hint.empty ())); assert (pk.proj); const project_name& proj (*pk.proj); - // Target type-specific search. - // // Note that if this function returns a target, it should have the // extension assigned (like the find/insert_target() functions) so that // as_name() returns a stable name. + + // Rule-specific resolution. + // + if (!hint.empty ()) + { + assert (pk.scope != nullptr); + + // Note: similar to/inspired by match_rule(). + // + // Search scopes outwards, stopping at the project root. + // + for (const scope* s (pk.scope); + s != nullptr; + s = s->root () ? nullptr : s->parent_scope ()) + { + // We only look for rules that are registered for perform(update). + // + if (const operation_rule_map* om = s->rules[perform_id]) + { + if (const target_type_rule_map* ttm = (*om)[update_id]) + { + // Ignore the target type the rules are registered for (this is + // about prerequisite types, not target). + // + // @@ Note that the same rule could be registered for several + // types which means we will keep calling it repeatedly. + // + for (const auto& p: *ttm) + { + const name_rule_map& nm (p.second); + + // Filter against the hint. + // + for (auto p (nm.find_sub (hint)); p.first != p.second; ++p.first) + { + const string& n (p.first->first); + const rule& r (p.first->second); + + auto df = make_diag_frame ( + [&pk, &n](const diag_record& dr) + { + if (verb != 0) + dr << info << "while importing " << pk << " using rule " + << n; + }); + + if (const target* t = r.import (pk, meta, loc)) + return t; + } + } + } + } + } + } + + // Builtin resolution for certain target types. // const target_key& tk (pk.tk); const target_type& tt (*tk.type); @@ -2927,8 +2992,7 @@ namespace build2 auto df = make_diag_frame ( [&proj, &tt, &tk] (const diag_record& dr) { - import_suggest ( - dr, proj, tt, *tk.name, "alternative "); + import_suggest (dr, proj, tt, *tk.name, false, "alternative "); }); if (!(md = extract_metadata (pp, *meta, opt, loc))) @@ -2971,7 +3035,9 @@ namespace build2 dr << info << "consider adding its installation location" << info << "or explicitly specify its project name"; else - import_suggest (dr, proj, tt, *tk.name); + // Use metadata as proxy for immediate import. + // + import_suggest (dr, proj, tt, *tk.name, meta && hint.empty ()); dr << endf; } @@ -2980,7 +3046,7 @@ namespace build2 import_direct (bool& new_value, scope& base, name tgt, - bool ph2, + const optional& ph2, bool opt, bool metadata, const location& loc, @@ -3038,6 +3104,7 @@ namespace build2 // pt = import (ctx, base.find_prerequisite_key (ns, loc), + *ph2, opt && !r.second, meta, false /* existing */, @@ -3094,7 +3161,10 @@ namespace build2 // The export.metadata value should start with the version followed by // the metadata variable prefix. // - lookup l (t.vars[ctx.var_export_metadata]); + // Note: lookup on target, not target::vars since it could come from + // the group (think lib{} metadata). + // + lookup l (t[ctx.var_export_metadata]); if (l && !l->empty ()) { const names& ns (cast (l)); diff --git a/libbuild2/file.hxx b/libbuild2/file.hxx index 091de7a..be7ea81 100644 --- a/libbuild2/file.hxx +++ b/libbuild2/file.hxx @@ -323,10 +323,14 @@ namespace build2 // original; see the config.import..[.] logic for details) // in which case it should still be passed to import phase 2. // - // If phase2 is true then the phase 2 is performed right away (we call it - // immediate import). Note that if optional is true, phase2 must be true as - // well (and thus there is no rule-specific logic for optional imports). In - // case of optional, empty names value is retuned if nothing was found. + // If phase2 is present then the phase 2 is performed right away (we call it + // immediate import). Note that if optional is true, phase2 must be present + // as well (and thus there is no rule-specific logic for optional imports). + // In case of optional, empty names value is returned if nothing was found. + // The value in phase2 is the optional rule hint that, if not empty, will be + // used to lookup a rule that will be asked to resolve the qualified target + // (see rule::import()). If it is empty, then built-in resolution logic will + // be used for some target types (currently only exe{}). // // If metadata is true, then load the target metadata. In this case phase2 // must be true as well. @@ -345,7 +349,7 @@ namespace build2 LIBBUILD2_SYMEXPORT pair import (scope& base, name, - bool phase2, + const optional& phase2, bool optional, bool metadata, const location&); @@ -360,16 +364,16 @@ namespace build2 // for example, used by build system modules to perform an implicit import // of the corresponding tool. // - // If phase2 is false, then the second phase's fallback/default logic is + // If phase2 is absent, then the second phase's fallback/default logic is // only invoked if the import was ad hoc (i.e., a relative path was // specified via config.import..[.]) with NULL returned // otherwise. // - // If phase2 is true and optional is true, then NULL is returned instead of - // failing if phase 2 could not find anything. + // If phase2 is present and optional is true, then NULL is returned instead + // of failing if phase 2 could not find anything. // // If metadata is true, then load the target metadata. In this case phase2 - // must be true as well. + // must be present as well. // // The what argument specifies what triggered the import (for example, // "module load") and is used in diagnostics. @@ -389,7 +393,7 @@ namespace build2 import_result import_direct (scope& base, name, - bool phase2, + const optional& phase2, bool optional, bool metadata, const location&, @@ -404,13 +408,16 @@ namespace build2 import_direct (bool& new_value, scope& base, name, - bool phase2, + const optional& phase2, bool optional, bool metadata, const location&, const char* what = "import"); + // As above but also cast the target and pass phase2 as bool (primarily + // for use in build system modules). + // template import_result import_direct (scope&, diff --git a/libbuild2/file.ixx b/libbuild2/file.ixx index dbd892d..43c46c9 100644 --- a/libbuild2/file.ixx +++ b/libbuild2/file.ixx @@ -24,6 +24,7 @@ namespace build2 LIBBUILD2_SYMEXPORT const target* import (context&, const prerequisite_key&, + const string& hint, bool optional_, const optional& metadata, // False or metadata key. bool existing, @@ -39,13 +40,13 @@ namespace build2 // Looks like the only way to do this is to keep location in name and // then in prerequisite. Perhaps one day... // - return *import (ctx, pk, false, nullopt, false, location ()); + return *import (ctx, pk, string (), false, nullopt, false, location ()); } inline import_result import_direct (scope& base, name tgt, - bool ph2, bool opt, bool md, + const optional& ph2, bool opt, bool md, const location& loc, const char* w) { bool dummy (false); @@ -59,7 +60,13 @@ namespace build2 bool ph2, bool opt, bool md, const location& loc, const char* w) { - auto r (import_direct (base, move (tgt), ph2, opt, md, loc, w)); + auto r (import_direct (base, + move (tgt), + ph2 ? optional (string ()) : nullopt, + opt, + md, + loc, + w)); return import_result { r.target != nullptr ? &r.target->as () : nullptr, move (r.name), @@ -74,7 +81,14 @@ namespace build2 bool ph2, bool opt, bool md, const location& loc, const char* w) { - auto r (import_direct (nv, base, move (tgt), ph2, opt, md, loc, w)); + auto r (import_direct (nv, + base, + move (tgt), + ph2 ? optional (string ()) : nullopt, + opt, + md, + loc, + w)); return import_result { r.target != nullptr ? &r.target->as () : nullptr, move (r.name), @@ -84,6 +98,6 @@ namespace build2 inline const target* import_existing (context& ctx, const prerequisite_key& pk) { - return import (ctx, pk, false, nullopt, true, location ()); + return import (ctx, pk, string (), false, nullopt, true, location ()); } } diff --git a/libbuild2/parser.cxx b/libbuild2/parser.cxx index da20a48..1fa29f6 100644 --- a/libbuild2/parser.cxx +++ b/libbuild2/parser.cxx @@ -3378,7 +3378,9 @@ namespace build2 // import[?!] [] = [] (|%])+ // bool opt (t.value.back () == '?'); - bool ph2 (opt || t.value.back () == '!'); + optional ph2 (opt || t.value.back () == '!' + ? optional (string ()) + : nullopt); // We are now in the normal lexing mode and we let the lexer handle `=`. // @@ -3389,26 +3391,53 @@ namespace build2 // we handle it in an ad hoc manner. // attributes_push (t, tt); - attributes& as (attributes_top ()); bool meta (false); - for (auto i (as.begin ()); i != as.end (); ) { - if (i->name == "metadata") - { - if (!ph2) - fail (as.loc) << "loading metadata requires immediate import" << - info << "consider using the import! directive instead"; + attributes& as (attributes_top ()); + const location& l (as.loc); - meta = true; - } - else + for (auto i (as.begin ()); i != as.end (); ) { - ++i; - continue; - } + const string& n (i->name); + value& v (i->value); - i = as.erase (i); + if (n == "metadata") + { + if (!ph2) + fail (l) << "loading metadata requires immediate import" << + info << "consider using the import! directive instead"; + + meta = true; + } + else if (n == "rule_hint") + { + if (!ph2) + fail (l) << "rule hint can only be used with immediate import" << + info << "consider using the import! directive instead"; + + // Here we only allow a single name. + // + try + { + ph2 = convert (move (v)); + + if (ph2->empty ()) + throw invalid_argument ("empty name"); + } + catch (const invalid_argument& e) + { + fail (l) << "invalid " << n << " attribute value: " << e; + } + } + else + { + ++i; + continue; + } + + i = as.erase (i); + } } if (tt != type::word) diff --git a/libbuild2/rule.cxx b/libbuild2/rule.cxx index acb46e8..6ede2dd 100644 --- a/libbuild2/rule.cxx +++ b/libbuild2/rule.cxx @@ -22,6 +22,14 @@ namespace build2 { } + const target* rule:: + import (const prerequisite_key&, + const optional&, + const location&) const + { + return nullptr; + } + bool rule:: sub_match (const string& n, operation_id o, action a, target& t, match_extra& me) const diff --git a/libbuild2/rule.hxx b/libbuild2/rule.hxx index 77c2d2c..da069ef 100644 --- a/libbuild2/rule.hxx +++ b/libbuild2/rule.hxx @@ -52,6 +52,21 @@ namespace build2 rule (const rule&) = delete; rule& operator= (const rule&) = delete; + // Resolve a project-qualified target in a rule-specific manner. + // + // This is optional functionality that may be provided by some rules to + // facilitate immediate importation of certain target types. See the + // import machinery for details. The default implementation always returns + // NULL. + // + // Note that if this function returns a target, it should have the + // extension assigned so that as_name() returns a stable name. + // + virtual const target* + import (const prerequisite_key&, + const optional& metadata, + const location&) const; + // Sometimes we want to match only if another rule of ours would match // another operation. For example, we would want our install rule to match // only if our update rule also matches. -- cgit v1.1