aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2022-06-27 13:11:06 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2022-06-28 11:11:55 +0200
commit4f5c9357b7e17b4fb9ecaad36c8740a05cfc1bc6 (patch)
tree1849fdadad55c5efd9ee7e19d21f3915b626c9f7
parentf1c981a22365411794806ed0744b857ef0804e35 (diff)
Add support for rule-specific import phase 2
For example: import! [metadata, rule_hint=cxx.link] lib = libhello%lib{hello}
-rw-r--r--libbuild2/file.cxx90
-rw-r--r--libbuild2/file.hxx29
-rw-r--r--libbuild2/file.ixx24
-rw-r--r--libbuild2/parser.cxx59
-rw-r--r--libbuild2/rule.cxx8
-rw-r--r--libbuild2/rule.hxx15
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 <iomanip> // left, setw()
#include <sstream>
+#include <libbuild2/rule.hxx>
#include <libbuild2/scope.hxx>
#include <libbuild2/target.hxx>
#include <libbuild2/context.hxx>
@@ -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<names, import_kind>
import (scope& base,
name tgt,
- bool ph2,
+ const optional<string>& 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<string>& 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<string>& 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<names> (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.<proj>.<name>[.<type>] 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<names, import_kind>
import (scope& base,
name,
- bool phase2,
+ const optional<string>& 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.<proj>.<name>[.<type>]) 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<target>
import_direct (scope& base,
name,
- bool phase2,
+ const optional<string>& 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<string>& 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 <typename T>
import_result<T>
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<string>& 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<target>
import_direct (scope& base,
name tgt,
- bool ph2, bool opt, bool md,
+ const optional<string>& 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> (string ()) : nullopt,
+ opt,
+ md,
+ loc,
+ w));
return import_result<T> {
r.target != nullptr ? &r.target->as<const T> () : 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> (string ()) : nullopt,
+ opt,
+ md,
+ loc,
+ w));
return import_result<T> {
r.target != nullptr ? &r.target->as<const T> () : 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[?!] [<attrs>] <var> = [<attrs>] (<target>|<project>%<target>])+
//
bool opt (t.value.back () == '?');
- bool ph2 (opt || t.value.back () == '!');
+ optional<string> ph2 (opt || t.value.back () == '!'
+ ? optional<string> (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<string> (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<string>&,
+ 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<string>& 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.