From 8a0ebb2b607c92b8c1bdab7e525da2b711b31565 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Mon, 15 Jan 2024 08:49:57 +0200 Subject: Automatically alias unknown target types of imported targets --- libbuild2/file.cxx | 91 ++++++++++++++++++++++++++++++++++++++++------------ libbuild2/file.hxx | 31 ++++++++++++------ libbuild2/parser.cxx | 33 +++++++++++++++---- libbuild2/scope.cxx | 12 ++++--- libbuild2/scope.hxx | 14 +++++--- libbuild2/scope.ixx | 6 ++-- 6 files changed, 140 insertions(+), 47 deletions(-) diff --git a/libbuild2/file.cxx b/libbuild2/file.cxx index 81a9dac..33f380d 100644 --- a/libbuild2/file.cxx +++ b/libbuild2/file.cxx @@ -2808,7 +2808,8 @@ namespace build2 l5 ([&]{trace << "importing " << es;}); // @@ Should we verify these are all unqualified names? Or maybe there - // is a use-case for the export stub to return a qualified name? + // is a use-case for the export stub to return a qualified name? E.g., + // re-export? // names v; { @@ -2893,6 +2894,27 @@ namespace build2 } } + const target_type& + import_target_type (scope& root, + const scope& iroot, const string& n, + const location& l) + { + // NOTE: see similar code in parser::parse_define(). + + const target_type* tt (iroot.find_target_type (n)); + if (tt == nullptr) + fail (l) << "unknown imported target type " << n << " in project " + << iroot; + + auto p (root.root_extra->target_types.insert (*tt)); + + if (!p.second && &p.first.get () != tt) + fail (l) << "imported target type " << n << " already defined in project " + << root; + + return *tt; + } + static names import2_buildfile (context&, names&&, bool, const location&); @@ -2901,7 +2923,7 @@ namespace build2 const string&, bool, const optional&, bool, const location&); - pair + import_result import (scope& base, name tgt, const optional& ph2, @@ -2931,7 +2953,10 @@ namespace build2 import_result r ( import_direct (base, move (tgt), ph2, opt, metadata, loc)); - return make_pair (move (r.name), r.kind); + return import_result { + r.target != nullptr ? r.target->base_scope ().root_scope () : nullptr, + move (r.name), + r.kind}; } pair> r ( @@ -2947,6 +2972,7 @@ namespace build2 if (!r.second || r.second->empty ()) { names ns; + const target* t (nullptr); if (r.first.empty ()) { @@ -2972,13 +2998,15 @@ namespace build2 // This is tricky: we only want the optional semantics for the // fallback case. // - if (const target* t = import2 (ctx, - base, ns, - *ph2, - opt && !r.second /* optional */, - nullopt /* metadata */, - false /* existing */, - loc)) + t = import2 (ctx, + base, ns, + *ph2, + opt && !r.second /* optional */, + nullopt /* metadata */, + false /* existing */, + loc); + + if (t != nullptr) { // Note that here r.first was still project-qualified and we // have no choice but to call as_name(). This shouldn't cause @@ -2994,18 +3022,20 @@ namespace build2 } } - return make_pair ( + return import_result { + t != nullptr ? t->base_scope ().root_scope () : nullptr, move (ns), - r.second.has_value () ? import_kind::adhoc : import_kind::fallback); + r.second.has_value () ? import_kind::adhoc : import_kind::fallback}; } import_kind k (r.first.absolute () ? import_kind::adhoc : import_kind::normal); - return make_pair ( - import_load (base.ctx, move (r), false /* metadata */, loc).first, - k); + pair p ( + import_load (base.ctx, move (r), false /* metadata */, loc)); + + return import_result {&p.second, move (p.first), k}; } const target* @@ -3339,6 +3369,8 @@ namespace build2 context& ctx (base.ctx); assert (ctx.phase == run_phase::load); + scope& root (*base.root_scope ()); + // Use the original target name as metadata key. // auto meta (metadata ? optional (tgt.value) : nullopt); @@ -3346,6 +3378,7 @@ namespace build2 names ns, rns; import_kind k; const target* pt (nullptr); + const scope* iroot (nullptr); // Imported root scope. pair> r ( import_search (new_value, @@ -3400,6 +3433,8 @@ namespace build2 // It's a bit fuzzy in which cases we end up here. So for now we keep // the original if it's absolute and call as_name() otherwise. // + // @@ TODO: resolve iroot or assume target type should be known? + // if (r.first.absolute ()) rns.push_back (r.first); @@ -3409,14 +3444,30 @@ namespace build2 else { k = r.first.absolute () ? import_kind::adhoc : import_kind::normal; - rns = ns = import_load (base.ctx, move (r), metadata, loc).first; + + pair p ( + import_load (base.ctx, move (r), metadata, loc)); + + rns = ns = move (p.first); + iroot = &p.second; } if (pt == nullptr) { + // Import (more precisely, alias) the target type into this project + // if not known. + // + const target_type* tt (nullptr); + if (iroot != nullptr && !ns.empty ()) + { + const name& n (ns.front ()); + if (n.typed ()) + tt = &import_target_type (root, *iroot, n.type, loc); + } + // Similar logic to perform's search(). Note: modifies ns. // - target_key tk (base.find_target_key (ns, loc)); + target_key tk (base.find_target_key (ns, loc, tt)); pt = ctx.targets.find (tk, trace); if (pt == nullptr) fail (loc) << "unknown imported target " << tk; @@ -3491,10 +3542,8 @@ namespace build2 // if (const auto* e = cast_null (t.vars[pfx + ".environment"])) { - scope& rs (*base.root_scope ()); - for (const string& v: *e) - config::save_environment (rs, v); + config::save_environment (root, v); } } else @@ -3512,7 +3561,7 @@ namespace build2 string () /* phase2 */, opt, false /* metadata */, - loc).first); + loc).name); path p; if (!r.empty ()) // Optional not found. diff --git a/libbuild2/file.hxx b/libbuild2/file.hxx index 68c284b..36e4c00 100644 --- a/libbuild2/file.hxx +++ b/libbuild2/file.hxx @@ -371,7 +371,18 @@ namespace build2 // enum class import_kind {adhoc, normal, fallback}; - LIBBUILD2_SYMEXPORT pair + template + struct import_result + { + const T* target; // Note: T can be imported target or imported scope. + names name; + import_kind kind; + }; + + // Note that import_result::target may be NULL even if name is not + // empty (e.g, out of project target imported via phase 2). + // + LIBBUILD2_SYMEXPORT import_result import (scope& base, name, const optional& phase2, @@ -409,14 +420,6 @@ namespace build2 // // Note: cannot be used to import buildfile targets (use import_buildfile() // instead). - // - template - struct import_result - { - const T* target; - names name; - import_kind kind; - }; // Print import_direct() result either as a target for a normal import // or as a process path for ad hoc and fallback imports. Normally used in @@ -518,6 +521,16 @@ namespace build2 bool metadata, const location&); + // Import (more precisely, alias as if using the `define =` syntax) the + // target type from imported project (iroot) into this project (root). If + // the target type with this name is already defined in this project, then + // make sure it is the same as in the imported project. + // + LIBBUILD2_SYMEXPORT const target_type& + import_target_type (scope& root, + const scope& iroot, const string&, + const location&); + // Suggest appropriate ways to import the specified target (as type and // name) from the specified project. // diff --git a/libbuild2/parser.cxx b/libbuild2/parser.cxx index baf404a..ebfb698 100644 --- a/libbuild2/parser.cxx +++ b/libbuild2/parser.cxx @@ -4226,12 +4226,15 @@ namespace build2 // import() will check the name, if required. // - names r (import (*scope_, - move (n), - ph2 ? ph2 : bf ? optional (string ()) : nullopt, - opt, - meta, - loc).first); + import_result ir ( + import (*scope_, + move (n), + ph2 ? ph2 : bf ? optional (string ()) : nullopt, + opt, + meta, + loc)); + + names& r (ir.name); if (val != nullptr) { @@ -4242,6 +4245,19 @@ namespace build2 } else { + // Import (more precisely, alias) the target type into this project + // if not known. + // + // Note that if the result is ignored (val is NULL), then it's fair + // to assume this is not necessary. + // + if (const scope* iroot = ir.target) + { + const name& n (r.front ()); + if (n.typed ()) + import_target_type (*root_, *iroot, n.type, loc); + } + if (atype == type::assign) val->assign (move (r), var); else if (atype == type::prepend) val->prepend (move (r), var); else val->append (move (r), var); @@ -4518,7 +4534,8 @@ namespace build2 << "group-related attribute"; if (!root_->derive_target_type (move (n), *bt, fs).second) - fail (nl) << "target type " << n << " already defined in this project"; + fail (nl) << "target type " << n << " already defined in project " + << *root_; next (t, tt); // Get newline. } @@ -4565,6 +4582,8 @@ namespace build2 // If we got here, then tn->dir is the scope and tn->value is the target // type. // + // NOTE: see similar code in import_target_type(). + // const target_type* tt (nullptr); if (const scope* rs = ctx->scopes.find_out (tn->dir).root_scope ()) { diff --git a/libbuild2/scope.cxx b/libbuild2/scope.cxx index 4c9f301..2b47dd0 100644 --- a/libbuild2/scope.cxx +++ b/libbuild2/scope.cxx @@ -792,9 +792,11 @@ namespace build2 } pair> scope:: - find_target_type (name& n, name& o, const location& loc) const + find_target_type (name& n, name& o, + const location& loc, + const target_type* tt) const { - auto r (find_target_type (n, loc)); + auto r (find_target_type (n, loc, tt)); if (r.first == nullptr) fail (loc) << "unknown target type " << n.type << " in " << n; @@ -878,14 +880,16 @@ namespace build2 } target_key scope:: - find_target_key (names& ns, const location& loc) const + find_target_key (names& ns, + const location& loc, + const target_type* tt) const { if (size_t n = ns.size ()) { if (n == (ns[0].pair ? 2 : 1)) { name dummy; - return find_target_key (ns[0], n == 1 ? dummy : ns[1], loc); + return find_target_key (ns[0], n == 1 ? dummy : ns[1], loc, tt); } } diff --git a/libbuild2/scope.hxx b/libbuild2/scope.hxx index 3f6b0af..968727b 100644 --- a/libbuild2/scope.hxx +++ b/libbuild2/scope.hxx @@ -358,19 +358,25 @@ namespace build2 // the out directory. // pair> - find_target_type (name&, name&, const location&) const; + find_target_type (name&, name&, + const location&, + const target_type* = nullptr) const; // As above, but return the result as a target key (with its members // shallow-pointing to processed parts in the two names). // target_key - find_target_key (name&, name&, const location&) const; + find_target_key (name&, name&, + const location&, + const target_type* = nullptr) const; // As above, but the names are passed as a vector. Issue appropriate // diagnostics if the wrong number of names is passed. // target_key - find_target_key (names&, const location&) const; + find_target_key (names&, + const location&, + const target_type* = nullptr) const; // Similar to the find_target_type() but does not complete relative // directories. @@ -378,7 +384,7 @@ namespace build2 pair> find_prerequisite_type (name&, name&, const location&, - const target_type* tt = nullptr) const; + const target_type* = nullptr) const; // As above, but return a prerequisite key. // diff --git a/libbuild2/scope.ixx b/libbuild2/scope.ixx index 4543f2c..5975c76 100644 --- a/libbuild2/scope.ixx +++ b/libbuild2/scope.ixx @@ -146,9 +146,11 @@ namespace build2 } inline target_key scope:: - find_target_key (name& n, name& o, const location& loc) const + find_target_key (name& n, name& o, + const location& loc, + const target_type* tt) const { - auto p (find_target_type (n, o, loc)); + auto p (find_target_type (n, o, loc, tt)); return target_key { &p.first, &n.dir, -- cgit v1.1