aboutsummaryrefslogtreecommitdiff
path: root/libbuild2/file.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'libbuild2/file.cxx')
-rw-r--r--libbuild2/file.cxx1136
1 files changed, 902 insertions, 234 deletions
diff --git a/libbuild2/file.cxx b/libbuild2/file.cxx
index 02ad71d..c0957ad 100644
--- a/libbuild2/file.cxx
+++ b/libbuild2/file.cxx
@@ -4,9 +4,11 @@
#include <libbuild2/file.hxx>
#include <cerrno>
+#include <cstring> // strlen()
#include <iomanip> // left, setw()
#include <sstream>
+#include <libbuild2/rule.hxx>
#include <libbuild2/scope.hxx>
#include <libbuild2/target.hxx>
#include <libbuild2/context.hxx>
@@ -28,6 +30,8 @@ namespace build2
{
// Standard and alternative build file/directory naming schemes.
//
+ extern const dir_path std_export_dir;
+ extern const dir_path alt_export_dir;
// build:
@@ -35,6 +39,7 @@ namespace build2
const dir_path std_root_dir (dir_path (std_build_dir) /= "root");
const dir_path std_bootstrap_dir (dir_path (std_build_dir) /= "bootstrap");
const dir_path std_build_build_dir (dir_path (std_build_dir) /= "build");
+ const dir_path std_export_dir (dir_path (std_build_dir) /= "export");
const path std_root_file (std_build_dir / "root.build");
const path std_bootstrap_file (std_build_dir / "bootstrap.build");
@@ -52,6 +57,7 @@ namespace build2
const dir_path alt_root_dir (dir_path (alt_build_dir) /= "root");
const dir_path alt_bootstrap_dir (dir_path (alt_build_dir) /= "bootstrap");
const dir_path alt_build_build_dir (dir_path (alt_build_dir) /= "build");
+ const dir_path alt_export_dir (dir_path (alt_build_dir) /= "export");
const path alt_root_file (alt_build_dir / "root.build2");
const path alt_bootstrap_file (alt_build_dir / "bootstrap.build2");
@@ -218,7 +224,7 @@ namespace build2
// Checking for plausability feels expensive since we have to recursively
// traverse the directory tree. Note, however, that if the answer is
// positive, then shortly after we will be traversing this tree anyway and
- // presumably this time getting the data from the cash (we don't really
+ // presumably this time getting the data from the cache (we don't really
// care about the negative answer since this is a degenerate case).
//
optional<path> bf;
@@ -306,7 +312,7 @@ namespace build2
{
tracer trace ("source_once");
- if (!once.buildfiles.insert (bf).second)
+ if (!once.root_extra->insert_buildfile (bf))
{
l5 ([&]{trace << "skipping already sourced " << bf;});
return false;
@@ -357,7 +363,7 @@ namespace build2
//
try
{
- for (const dir_entry& de: dir_iterator (d, false /* ignore_dangling */))
+ for (const dir_entry& de: dir_iterator (d, dir_iterator::no_follow))
{
// If this is a link, then type() will try to stat() it. And if the
// link is dangling or points to something inaccessible, it will fail.
@@ -522,10 +528,14 @@ namespace build2
pair<scope&, scope*>
switch_scope (scope& root, const dir_path& out_base, bool proj)
{
+ context& ctx (root.ctx);
+
+ assert (ctx.phase == run_phase::load);
+
// First, enter the scope into the map and see if it is in any project. If
// it is not, then there is nothing else to do.
//
- auto i (root.ctx.scopes.rw (root).insert_out (out_base));
+ auto i (ctx.scopes.rw (root).insert_out (out_base));
scope& base (*i->second.front ());
scope* rs (nullptr);
@@ -546,7 +556,7 @@ namespace build2
// Switch to the new root scope.
//
- if (rs != &root)
+ if (rs != &root && !rs->root_extra->loaded)
load_root (*rs); // Load new root(s) recursively.
// Now we can figure out src_base and finish setting the scope.
@@ -581,36 +591,37 @@ namespace build2
fail << "variable out_root expected as first line in " << f << endf;
}
+ scope::root_extra_type::
+ root_extra_type (scope& root, bool a)
+ : altn (a),
+ loaded (false),
+
+ build_ext (a ? alt_build_ext : std_build_ext),
+ build_dir (a ? alt_build_dir : std_build_dir),
+ buildfile_file (a ? alt_buildfile_file : std_buildfile_file),
+ buildignore_file (a ? alt_buildignore_file : std_buildignore_file),
+ root_dir (a ? alt_root_dir : std_root_dir),
+ bootstrap_dir (a ? alt_bootstrap_dir : std_bootstrap_dir),
+ build_build_dir (a ? alt_build_build_dir : std_build_build_dir),
+ bootstrap_file (a ? alt_bootstrap_file : std_bootstrap_file),
+ root_file (a ? alt_root_file : std_root_file),
+ export_file (a ? alt_export_file : std_export_file),
+ src_root_file (a ? alt_src_root_file : std_src_root_file),
+ out_root_file (a ? alt_out_root_file : std_out_root_file),
+
+ var_pool (&root.ctx, &root.ctx.var_pool.rw (root), nullptr)
+ {
+ root.var_pool_ = &var_pool;
+ }
+
static void
setup_root_extra (scope& root, optional<bool>& altn)
{
assert (altn && root.root_extra == nullptr);
- bool a (*altn);
-
- root.root_extra.reset (
- new scope::root_extra_type {
- nullopt /* project */,
- nullopt /* amalgamation */,
- nullopt /* subprojects */,
- a,
- a ? alt_build_ext : std_build_ext,
- a ? alt_build_dir : std_build_dir,
- a ? alt_buildfile_file : std_buildfile_file,
- a ? alt_buildignore_file : std_buildignore_file,
- a ? alt_root_dir : std_root_dir,
- a ? alt_bootstrap_dir : std_bootstrap_dir,
- a ? alt_build_build_dir : std_build_build_dir,
- a ? alt_bootstrap_file : std_bootstrap_file,
- a ? alt_root_file : std_root_file,
- a ? alt_export_file : std_export_file,
- a ? alt_src_root_file : std_src_root_file,
- a ? alt_out_root_file : std_out_root_file,
- {}, /* meta_operations */
- {}, /* operations */
- {}, /* modules */
- {}, /* override_cache */
- {}, /* target_types */
- {}} /* environment */);
+
+ context& ctx (root.ctx);
+
+ root.root_extra.reset (new scope::root_extra_type (root, *altn));
// Enter built-in meta-operation and operation names. Loading of
// modules (via the src bootstrap; see below) can result in
@@ -620,9 +631,9 @@ namespace build2
root.insert_meta_operation (perform_id, mo_perform);
root.insert_meta_operation (info_id, mo_info);
- root.insert_operation (default_id, op_default);
- root.insert_operation (update_id, op_update);
- root.insert_operation (clean_id, op_clean);
+ root.insert_operation (default_id, op_default, nullptr);
+ root.insert_operation (update_id, op_update, ctx.var_update);
+ root.insert_operation (clean_id, op_clean, ctx.var_clean);
}
value&
@@ -841,10 +852,26 @@ namespace build2
try
{
- for (const dir_entry& de: dir_iterator (d, true /* ignore_dangling */))
+ // It's probably possible that a subproject can be a symlink with the
+ // link target, for example, being in a git submodule. Considering that,
+ // it makes sense to warn about dangling symlinks.
+ //
+ for (const dir_entry& de:
+ dir_iterator (d, dir_iterator::detect_dangling))
{
if (de.type () != entry_type::directory)
+ {
+ if (de.type () == entry_type::unknown)
+ {
+ bool sl (de.ltype () == entry_type::symlink);
+
+ warn << "skipping "
+ << (sl ? "dangling symlink" : "inaccessible entry") << ' '
+ << d / de.path ();
+ }
+
continue;
+ }
dir_path sd (d / path_cast<dir_path> (de.path ()));
@@ -912,7 +939,9 @@ namespace build2
}
void
- bootstrap_src (scope& rs, optional<bool>& altn)
+ bootstrap_src (scope& rs, optional<bool>& altn,
+ optional<dir_path> aovr,
+ bool sovr)
{
tracer trace ("bootstrap_src");
@@ -942,13 +971,24 @@ namespace build2
rs.root_extra->project = nullptr;
rs.root_extra->amalgamation = nullptr;
rs.root_extra->subprojects = nullptr;
+
+ // See GH issue #322.
+ //
+#if 0
+ assert (!aovr || aovr->empty ());
+#else
+ if (!(!aovr || aovr->empty ()))
+ fail << "amalgamation directory " << *aovr << " specified for simple "
+ << "project " << src_root <<
+ info << "see https://github.com/build2/build2/issues/322 for details";
+#endif
}
// We assume that bootstrap out cannot load this file explicitly. It
// feels wrong to allow this since that makes the whole bootstrap
// process hard to reason about. But we may try to bootstrap the same
// root scope multiple time.
//
- else if (rs.buildfiles.insert (bf).second)
+ else if (rs.root_extra->insert_buildfile (bf))
{
// Extract the project name and amalgamation variable value so that
// we can make them available while loading bootstrap.build.
@@ -984,7 +1024,13 @@ namespace build2
const project_name pn (cast<project_name> (move (*pv)));
rs.root_extra->project = &pn;
- if (av && (av->null || av->empty ()))
+ // @@ We will still have original values in the variables during
+ // bootstrap. Not sure what we can do about that. But it seems
+ // harmless.
+ //
+ if (aovr)
+ rs.root_extra->amalgamation = aovr->empty () ? nullptr : &*aovr;
+ else if (av && (av->null || av->empty ()))
rs.root_extra->amalgamation = nullptr;
{
@@ -1004,6 +1050,13 @@ namespace build2
fail << "variable " << *ctx.var_amalgamation << " expected as a "
<< "second line in " << bf;
}
+
+ // Replace the value if overridden.
+ //
+ // Note that root_extra::amalgamation will be re-pointed below.
+ //
+ if (aovr)
+ rs.vars.assign (ctx.var_amalgamation) = move (*aovr);
}
else
{
@@ -1021,6 +1074,11 @@ namespace build2
// which we convert to NULL below). When calculated, the NULL value
// indicates that we are not amalgamated.
//
+ // Before we used to assume that if there is an outer root scope, then
+ // that got to be our amalgamation. But it turns our this is not always
+ // the case (for example, a private host configuration in bpkg) and there
+ // could be an unbootstrapped project between us and an outer root scope.
+ //
// Note: the amalgamation variable value is always a relative directory.
//
if (!simple)
@@ -1031,66 +1089,84 @@ namespace build2
if (v && v.empty ()) // Convert empty to NULL.
v = nullptr;
- if (scope* ars = rs.parent_scope ()->root_scope ())
+ scope* ars (rs.parent_scope ()->root_scope ());
+
+ if (rp.second)
{
- // We must not be amalgamated by a simple project.
+ // If the amalgamation variable hasn't been set, then we need to check
+ // if any of the outer directories is a project's out_root. If so,
+ // then that's (likely) our amalgamation.
//
- if (!ars->root_extra->project || *ars->root_extra->project != nullptr)
- {
- const dir_path& ad (ars->out_path ());
- dir_path rd (ad.relative (out_root));
+ optional<bool> altn;
+ const dir_path& d (find_out_root (out_root.directory (), altn).first);
- // If we already have the amalgamation variable set, verify that
- // aroot matches its value.
+ if (!d.empty ())
+ {
+ // Note that the sub() test is important: during configuration we
+ // may find a project that is outside the outer root scope in which
+ // case we should use the latter instead.
//
- if (!rp.second)
- {
- if (v)
- {
- const dir_path& vd (cast<dir_path> (v));
-
- if (vd != rd)
- {
- fail << "inconsistent amalgamation of " << out_root <<
- info << "specified: " << vd <<
- info << "actual: " << rd << " by " << ad;
- }
- }
- }
- else
+ if (ars == nullptr ||
+ (d != ars->out_path () && d.sub (ars->out_path ())))
{
- // Otherwise, use the outer root as our amalgamation.
- //
+ dir_path rd (d.relative (out_root));
l5 ([&]{trace << out_root << " amalgamated as " << rd;});
v = move (rd);
+ ars = nullptr; // Skip the checks blow.
}
+ // Else fall through.
+ }
+ else
+ {
+ // Note that here ars may be not NULL. This can happen both when ars
+ // is a simple project or if out_root is in out directory that has
+ // no been configured. In this case falling through is what we want.
}
}
- else if (rp.second)
+ else if (v)
{
- // If there is no outer root and the amalgamation variable hasn't been
- // set, then we need to check if any of the outer directories is a
- // project's out_root. If so, then that's our amalgamation.
+ if (cast<dir_path> (v).absolute ())
+ fail << "absolute directory in variable " << *ctx.var_amalgamation
+ << " value";
+ }
+
+ // Do additional checks if the outer root could be our amalgamation.
+ //
+ if (ars != nullptr)
+ {
+ const dir_path& ad (ars->out_path ());
+
+ // If we have the amalgamation variable set by the user, verify that
+ // it's a subdirectory of the outer root scope.
//
- optional<bool> altn;
- const dir_path& ad (find_out_root (out_root.directory (), altn).first);
+ // Note that in this case we allow amalgamation by a simple project
+ // (we rely on this, for example, in our modules sidebuild machinery).
+ //
+ if (!rp.second)
+ {
+ if (v)
+ {
+ const dir_path& vd (cast<dir_path> (v));
+ dir_path d (out_root / vd);
+ d.normalize ();
- if (!ad.empty ())
+ if (!d.sub (ad))
+ fail << "incorrect amalgamation " << vd << " of " << out_root;
+ }
+ }
+ // By default we do not get amalgamated by a simple project.
+ //
+ else if (!(ars->root_extra->project &&
+ *ars->root_extra->project == nullptr))
{
+ // Otherwise, use the outer root as our amalgamation.
+ //
dir_path rd (ad.relative (out_root));
+
l5 ([&]{trace << out_root << " amalgamated as " << rd;});
v = move (rd);
}
- //@@ else: the value will be NULL and amalgamation will be disabled.
- // We could omit setting it in root_extra... But maybe this is
- // correct: we don't want to load half of the project as
- // amalgamated and the other half as not, would we now?
-
}
- // @@ else if (v): shouldn't we try to bootstrap a project in the
- // user-specified directory? Though this case is not
- // used outside of some controlled cases (like module
- // sidebuilds).
rs.root_extra->amalgamation = cast_null<dir_path> (v);
}
@@ -1111,6 +1187,14 @@ namespace build2
auto rp (rs.vars.insert (*ctx.var_subprojects)); // Set NULL by default.
value& v (rp.first);
+ if (!sovr)
+ {
+ if (rp.second)
+ rp.second = false; // Keep NULL.
+ else
+ v = nullptr; // Make NULL.
+ }
+
if (rp.second)
{
// No subprojects set so we need to figure out if there are any.
@@ -1267,9 +1351,9 @@ namespace build2
// Call module's post-boot functions.
//
- for (size_t i (0); i != root.root_extra->modules.size (); ++i)
+ for (size_t i (0); i != root.root_extra->loaded_modules.size (); ++i)
{
- module_state& s (root.root_extra->modules[i]);
+ module_state& s (root.root_extra->loaded_modules[i]);
if (s.boot_post != nullptr)
boot_post_module (root, s);
@@ -1310,7 +1394,7 @@ namespace build2
}
void
- create_bootstrap_outer (scope& root)
+ create_bootstrap_outer (scope& root, bool subp)
{
context& ctx (root.ctx);
@@ -1358,7 +1442,7 @@ namespace build2
setup_root (rs, forwarded (root, out_root, v.as<dir_path> (), altn));
bootstrap_pre (rs, altn);
- bootstrap_src (rs, altn);
+ bootstrap_src (rs, altn, nullopt, subp);
// bootstrap_post() delayed until after create_bootstrap_outer().
}
else
@@ -1369,7 +1453,7 @@ namespace build2
rs.assign (ctx.var_forwarded) = true; // Only upgrade (see main()).
}
- create_bootstrap_outer (rs);
+ create_bootstrap_outer (rs, subp);
if (!bstrapped)
bootstrap_post (rs);
@@ -1457,22 +1541,19 @@ namespace build2
}
void
- load_root (scope& root)
+ load_root (scope& root,
+ const function<void (parser&)>& pre,
+ const function<void (parser&)>& post)
{
tracer trace ("load_root");
- context& ctx (root.ctx);
-
- const dir_path& out_root (root.out_path ());
- const dir_path& src_root (root.src_path ());
-
- // As an optimization, check if we have already loaded root.build. If
- // that's the case, then we have already been called for this project.
- //
- path f (src_root / root.root_extra->root_file);
-
- if (root.buildfiles.find (f) != root.buildfiles.end ())
+ if (root.root_extra->loaded)
+ {
+ assert (pre == nullptr && post == nullptr);
return;
+ }
+
+ context& ctx (root.ctx);
if (ctx.no_external_modules)
fail << "attempt to load project " << root << " after skipped loading "
@@ -1481,18 +1562,19 @@ namespace build2
// First load outer roots, if any.
//
if (scope* rs = root.parent_scope ()->root_scope ())
- load_root (*rs);
+ if (!rs->root_extra->loaded)
+ load_root (*rs);
// Finish off initializing bootstrapped modules (before mode).
//
// Note that init() can load additional modules invalidating iterators.
//
auto init_modules =
- [&root, n = root.root_extra->modules.size ()] (module_boot_init v)
+ [&root, n = root.root_extra->loaded_modules.size ()] (module_boot_init v)
{
for (size_t i (0); i != n; ++i)
{
- module_state& s (root.root_extra->modules[i]);
+ module_state& s (root.root_extra->loaded_modules[i]);
if (s.boot_init && *s.boot_init == v)
init_module (root, root, s.name, s.loc);
@@ -1512,6 +1594,11 @@ namespace build2
// Load hooks and root.build.
//
+ const dir_path& out_root (root.out_path ());
+ const dir_path& src_root (root.src_path ());
+
+ path f (src_root / root.root_extra->root_file);
+
// We can load the pre hooks before finishing off loading the bootstrapped
// modules (which, in case of config would load config.build) or after and
// one can come up with a plausible use-case for either approach. Note,
@@ -1527,10 +1614,22 @@ namespace build2
//
parser p (ctx, load_stage::root);
+ if (pre != nullptr)
+ {
+ pre (p);
+ p.reset ();
+ }
+
if (he) {source_hooks (p, root, hd, true /* pre */); p.reset ();}
if (fe) {source_once (p, root, root, f, root);}
if (he) {p.reset (); source_hooks (p, root, hd, false /* pre */);}
+ if (post != nullptr)
+ {
+ p.reset ();
+ post (p);
+ }
+
// Finish off initializing bootstrapped modules (after mode).
//
{
@@ -1538,12 +1637,19 @@ namespace build2
init_modules (module_boot_init::after);
}
- // Print the project configuration report, similar to how we do it in
+ // Print the project configuration report(s), similar to how we do it in
// build system modules.
//
- if (!p.config_report.empty () && verb >= (p.config_report_new ? 2 : 3))
+ using config_report = parser::config_report;
+
+ const project_name* proj (nullptr); // Resolve lazily.
+ for (const config_report& cr: p.config_reports)
{
- const project_name& proj (named_project (root)); // Can be empty.
+ if (verb < (cr.new_value ? 2 : 3))
+ continue;
+
+ if (proj == nullptr)
+ proj = &named_project (root); // Can be empty.
// @@ TODO/MAYBE:
//
@@ -1561,46 +1667,74 @@ namespace build2
// config @/tmp/tests
// libhello.tests.remote true
//
- string stem (!proj.empty () ? '.' + proj.variable () + '.' : string ());
+ // If the module name is not empty then it means the config variables
+ // are from the imported project and so we use that for <project>.
+ //
+ string stem (!cr.module.empty ()
+ ? '.' + cr.module.variable () + '.'
+ : (!proj->empty ()
+ ? '.' + proj->variable () + '.'
+ : string ()));
- // Calculate max name length.
+ // Return the variable name for printing.
//
- size_t pad (10);
- for (const pair<lookup, string>& lf: p.config_report)
+ auto name = [&stem] (const config_report::value& cv) -> const char*
{
- lookup l (lf.first);
+ lookup l (cv.val);
- size_t n;
if (l.value == nullptr)
{
- n = l.var->name.size ();
+ if (cv.org.empty ())
+ return l.var->name.c_str ();
+
+ // This case may or may not have the prefix.
+ //
+ size_t p, n (
+ !stem.empty ()
+ ? (p = cv.org.find (stem)) != string::npos ? p + stem.size () : 0
+ : cv.org.compare (0, 7, "config.") == 0 ? 7 : 0);
+
+ return cv.org.c_str () + n;
}
else
{
+ assert (cv.org.empty ()); // Sanity check.
+
size_t p (!stem.empty ()
? l.var->name.find (stem) + stem.size ()
: 7); // "config."
- n = l.var->name.size () - p;
+
+ return l.var->name.c_str () + p;
}
+ };
+
+ // Calculate max name length.
+ //
+ size_t pad (10);
+ for (const config_report::value& cv: cr.values)
+ {
+ size_t n (strlen (name (cv)));
if (n > pad)
pad = n;
}
// Use the special `config` module name (which doesn't have its own
- // report) for project configuration.
+ // report) for project's own configuration.
//
diag_record dr (text);
- dr << "config " << proj << '@' << root;
+ dr << (cr.module.empty () ? "config" : cr.module.string ().c_str ())
+ << ' ' << *proj << '@' << root;
names storage;
- for (const pair<lookup, string>& lf: p.config_report)
+ for (const config_report::value& cv: cr.values)
{
- lookup l (lf.first);
- const string& f (lf.second);
+ lookup l (cv.val);
+ const string& f (cv.fmt);
// If the report variable has been overriden, now is the time to
- // lookup its value.
+ // lookup its value. Note: see also the name() lambda above if
+ // changing anything here.
//
string n;
if (l.value == nullptr)
@@ -1616,26 +1750,30 @@ namespace build2
n = string (l.var->name, p);
}
+ const char* pn (name (cv)); // Print name.
+
dr << "\n ";
- if (const value& v = *l)
+ if (l)
{
storage.clear ();
- auto ns (reverse (v, storage));
+ auto ns (reverse (*l, storage, true /* reduce */));
if (f == "multiline")
{
- dr << n;
+ dr << pn;
for (auto& n: ns)
dr << "\n " << n;
}
else
- dr << left << setw (static_cast<int> (pad)) << n << ' ' << ns;
+ dr << left << setw (static_cast<int> (pad)) << pn << ' ' << ns;
}
else
- dr << left << setw (static_cast<int> (pad)) << n << " [null]";
+ dr << left << setw (static_cast<int> (pad)) << pn << " [null]";
}
}
+
+ root.root_extra->loaded = true;
}
scope&
@@ -1672,7 +1810,8 @@ namespace build2
if (load)
{
- load_root (rs);
+ if (!rs.root_extra->loaded)
+ load_root (rs);
setup_base (i, out_root, src_root); // Setup as base.
}
@@ -1727,8 +1866,10 @@ namespace build2
}
// Extract metadata for an executable target by executing it with the
- // --build2-metadata option. In case of an error, issue diagnostics and fail
- // if opt is false and return nullopt if it's true.
+ // --build2-metadata option. Key is the target name (and not necessarily the
+ // same as metadata variable prefix in export.metadata; e.g., openbsd-m4 and
+ // openbsd_m4). In case of an error, issue diagnostics and fail if opt is
+ // false and return nullopt if it's true.
//
// Note that loading of the metadata is split into two steps, extraction and
// parsing, because extraction also serves as validation that the executable
@@ -1792,7 +1933,9 @@ namespace build2
try
{
// Note: not using run_*() functions since need to be able to suppress
- // all errors, including inability to exec.
+ // all errors, including abnormal, inability to exec, etc., in case of
+ // optional import. Also, no need to buffer diagnostics since in the
+ // serial load.
//
if (verb >= 3)
print_process (args);
@@ -1841,14 +1984,29 @@ namespace build2
// Note that we won't be able to add more options since trying them
// will be expensive.
//
- if (r.compare (0,
- 20 + key.size (),
- "# build2 buildfile " + key + '\n') == 0)
+ // Note also that the <key> and variable prefix (as specified in the
+ // export.metadata) are not necessarily the same: <key> is the
+ // target name as imported. Think of it as program's canonical name,
+ // for example, g++ with the actual program being g++-10, etc., and
+ // the variable prefix could be gxx.
+ //
+ string s ("# build2 buildfile " + key);
+ if (r.compare (0, s.size (), s) == 0 && r[s.size ()] == '\n')
return r;
if (!opt)
- error (loc) << "invalid metadata signature in " << args[0]
- << " output";
+ {
+ diag_record dr;
+ dr << error (loc) << "invalid metadata signature in " << args[0]
+ << " output" <<
+ info << "expected '" << s << "'";
+
+ if (verb >= 1 && verb <= 2)
+ {
+ dr << info << "command line: ";
+ print_process (dr, args);
+ }
+ }
goto fail;
}
@@ -1865,16 +2023,27 @@ namespace build2
if (pr.wait ())
{
if (!opt)
- error (loc) << "unable to read metadata from " << args[0];
+ error (loc) << "io error reading metadata from " << args[0];
}
else
{
// The child process presumably issued diagnostics but if it didn't,
- // the result will be very confusing. So let's issue something
- // generic for good measure.
+ // the result will be very confusing. So let's issue something generic
+ // for good measure. But also make it consistent with diagnostics
+ // issued by run_finish().
//
if (!opt)
- error (loc) << "unable to extract metadata from " << args[0];
+ {
+ diag_record dr;
+ dr << error (loc) << "unable to extract metadata from " << args[0] <<
+ info << "process " << args[0] << " " << *pr.exit;
+
+ if (verb >= 1 && verb <= 2)
+ {
+ dr << info << "command line: ";
+ print_process (dr, args);
+ }
+ }
}
goto fail;
@@ -1890,8 +2059,7 @@ namespace build2
goto fail;
}
- fail:
-
+ fail:
if (opt)
{
metadata_cache.insert (pp.effect_string (), true);
@@ -1920,6 +2088,39 @@ namespace build2
&t);
}
+ void
+ import_suggest (const diag_record& dr,
+ const project_name& pn,
+ const target_type* tt,
+ const string& tn,
+ bool rule_hint,
+ const char* qual)
+ {
+ string pv (pn.variable ());
+
+ // Suggest normal import.
+ //
+ dr << info << "use config.import." << pv << " configuration variable to "
+ << "specify its " << (qual != nullptr ? qual : "") << "project out_root";
+
+ // Suggest ad hoc import but only if it's a path-based target (doing it
+ // for lib{} is very confusing).
+ //
+ if (tt != nullptr && tt->is_a<path_target> ())
+ {
+ string v (tt->is_a<exe> () && (pv == tn || pn == tn)
+ ? "config." + pv
+ : "config.import." + pv + '.' + tn + '.' + tt->name);
+
+ 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
// any.
//
@@ -1932,6 +2133,9 @@ namespace build2
// Return empty name if an ad hoc import resulted in a NULL target (only
// allowed if optional is true).
//
+ // Note that this function has a side effect of potentially marking some
+ // config.import.* variables as used.
+ //
pair<name, optional<dir_path>>
import_search (bool& new_value,
scope& ibase,
@@ -1963,6 +2167,9 @@ namespace build2
//
// 4. Normal import.
//
+ // @@ PERF: in quite a few places (local, subproject) we could have
+ // returned the scope and save on bootstrap in import_load().
+ //
if (tgt.unqualified ())
{
if (tgt.directory () && tgt.relative ())
@@ -1970,6 +2177,8 @@ namespace build2
if (tgt.absolute ())
{
+ // Ad hoc import.
+ //
// Actualize the directory to be analogous to the config.import.<proj>
// case (which is of abs_dir_path type).
//
@@ -1986,7 +2195,7 @@ namespace build2
fail (loc) << "project-local importation of target " << tgt
<< " from an unnamed project";
- tgt.proj = pn;
+ tgt.proj = pn; // Reduce to normal import.
return make_pair (move (tgt), optional<dir_path> (iroot.out_path ()));
}
@@ -2018,7 +2227,9 @@ namespace build2
// over anything that we may discover. In particular, we will prefer it
// over any bundled subprojects.
//
- auto& vp (iroot.var_pool ());
+ // Note: go straight for the public variable pool.
+ //
+ auto& vp (iroot.var_pool (true /* public */));
using config::lookup_config;
@@ -2135,6 +2346,8 @@ namespace build2
tgt = name (); // NULL
else
{
+ string on (move (tgt.value)); // Original name as imported.
+
tgt.dir = p->directory ();
tgt.value = p->leaf ().string ();
@@ -2163,15 +2376,15 @@ namespace build2
// natural course?
//
name n (tgt);
- auto r (ibase.find_target_type (n, loc));
+ const target_type* tt (ibase.find_target_type (n, loc).first);
- if (r.first == nullptr)
+ if (tt == nullptr)
fail (loc) << "unknown target type " << n.type << " in " << n;
// Note: not using the extension extracted by find_target_type()
// to be consistent with import phase 2.
//
- target& t (insert_target (trace, ctx, *r.first, *p).first);
+ target& t (insert_target (trace, ctx, *tt, *p).first);
// Load the metadata, similar to import phase 2.
//
@@ -2181,12 +2394,22 @@ namespace build2
{
if (!e->vars[ctx.var_export_metadata].defined ())
{
- parse_metadata (*e,
- *extract_metadata (e->process_path (),
- *meta,
- false /* optional */,
- loc),
- loc);
+ optional<string> md;
+ {
+ auto df = make_diag_frame (
+ [&proj, tt, &on] (const diag_record& dr)
+ {
+ import_suggest (
+ dr, proj, tt, on, false, "alternative ");
+ });
+
+ md = extract_metadata (e->process_path (),
+ *meta,
+ false /* optional */,
+ loc);
+ }
+
+ parse_metadata (*e, move (*md), loc);
}
}
}
@@ -2300,6 +2523,8 @@ namespace build2
{
tracer trace ("import_load");
+ uint64_t metav (meta ? 1 : 0); // Metadata version.
+
// We end up here in two cases: Ad hoc import, in which case name is
// unqualified and absolute and path is a base, not necessarily root. And
// normal import, in which case name must be project-qualified and path is
@@ -2362,14 +2587,51 @@ namespace build2
}
}
+ // First check the cache.
+ //
+ using import_key = context::import_key;
+
+ auto cache_find = [&ctx, &tgt, metav] (dir_path& out_root) ->
+ const pair<names, const scope&>*
+ {
+ import_key k {move (out_root), move (tgt), metav};
+
+ auto i (ctx.import_cache.find (k));
+ if (i != ctx.import_cache.end ())
+ return &i->second;
+
+ out_root = move (k.out_root);
+ tgt = move (k.target);
+
+ return nullptr;
+ };
+
+ if (proj)
+ {
+ if (const auto* r = cache_find (out_root))
+ return *r;
+ }
+
+ dir_path cache_out_root;
+
// Clear current project's environment.
//
auto_project_env penv (nullptr);
+ // Note: this loop does at most two iterations.
+ //
for (const scope* proot (nullptr); ; proot = root)
{
bool top (proot == nullptr);
+ // Check the cache for the subproject.
+ //
+ if (!top && proj)
+ {
+ if (const auto* r = cache_find (out_root))
+ return *r;
+ }
+
root = create_root (ctx, out_root, src_root)->second.front ();
bool bstrapped (bootstrapped (*root));
@@ -2448,6 +2710,8 @@ namespace build2
if (i != ps->end ())
{
+ cache_out_root = move (out_root);
+
const dir_path& d ((*i).second);
altn = nullopt;
out_root = root->out_path () / d;
@@ -2459,9 +2723,72 @@ namespace build2
fail (loc) << out_root << " is not out_root for " << *proj;
}
+ // Buildfile importation is quite different so handle it separately.
+ //
+ // Note that we don't need to load the project in this case.
+ //
+ // @@ For now we don't out-qualify the resulting target to be able to
+ // re-import it ad hoc (there is currently no support for out-qualified
+ // ad hoc import). Feels like this should be harmless since it's just a
+ // glorified path to a static file that nobody is actually going to use
+ // as a target (e.g., to depend upon).
+ //
+ if (tgt.type == "buildfile")
+ {
+ auto add_ext = [&altn] (string& n)
+ {
+ if (path_traits::find_extension (n) == string::npos)
+ {
+ if (n != (*altn ? alt_buildfile_file : std_buildfile_file).string ())
+ {
+ n += ".";
+ n += *altn ? alt_build_ext : std_build_ext;
+ }
+ }
+ };
+
+ if (proj)
+ {
+ name n;
+
+ if (src_root.empty ())
+ src_root = root->src_path ();
+
+ n.dir = move (src_root);
+ n.dir /= *altn ? alt_export_dir : std_export_dir;
+ if (!tgt.dir.empty ())
+ {
+ n.dir /= tgt.dir;
+ n.dir.normalize ();
+ }
+
+ n.type = tgt.type;
+ n.value = tgt.value;
+ add_ext (n.value);
+
+ pair<names, const scope&> r (names {move (n)}, *root);
+
+ // Cache.
+ //
+ if (cache_out_root.empty ())
+ cache_out_root = move (out_root);
+
+ ctx.import_cache.emplace (
+ import_key {move (cache_out_root), move (tgt), metav}, r);
+
+ return r;
+ }
+ else
+ {
+ add_ext (tgt.value);
+ return pair<names, const scope&> (names {move (tgt)}, *root);
+ }
+ }
+
// Load the imported root scope.
//
- load_root (*root);
+ if (!root->root_extra->loaded)
+ load_root (*root);
// If this is a normal import, then we go through the export stub.
//
@@ -2476,6 +2803,12 @@ namespace build2
// "Pass" the imported project's roots to the stub.
//
+ if (cache_out_root.empty ())
+ cache_out_root = out_root;
+
+ if (src_root.empty ())
+ src_root = root->src_path ();
+
ts.assign (ctx.var_out_root) = move (out_root);
ts.assign (ctx.var_src_root) = move (src_root);
@@ -2491,7 +2824,7 @@ namespace build2
// Pass the metadata compatibility version in import.metadata.
//
if (meta)
- ts.assign (ctx.var_import_metadata) = uint64_t (1);
+ ts.assign (ctx.var_import_metadata) = metav;
// Load the export stub. Note that it is loaded in the context of the
// importing project, not the imported one. The export stub will
@@ -2506,10 +2839,20 @@ 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?
//
- parser p (ctx);
- names v (p.parse_export_stub (ifs, path_name (es), gs, ts));
+ names v;
+ {
+ auto df = make_diag_frame (
+ [&tgt, &loc] (const diag_record& dr)
+ {
+ dr << info (loc) << "while loading export stub for " << tgt;
+ });
+
+ parser p (ctx);
+ v = p.parse_export_stub (ifs, path_name (es), *root, gs, ts);
+ }
// If there were no export directive executed in an export stub,
// assume the target is not exported.
@@ -2518,7 +2861,14 @@ namespace build2
fail (loc) << "target " << tgt << " is not exported by project "
<< *proj;
- return pair<names, const scope&> (move (v), *root);
+ pair<names, const scope&> r (move (v), *root);
+
+ // Cache.
+ //
+ ctx.import_cache.emplace (
+ import_key {move (cache_out_root), move (tgt), metav}, r);
+
+ return r;
}
catch (const io_error& e)
{
@@ -2575,10 +2925,39 @@ namespace build2
}
}
- pair<names, import_kind>
+ 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&);
+
+ static const target*
+ import2 (context&, const scope&, names&,
+ const string&, bool, const optional<string>&, bool,
+ const location&);
+
+ import_result<scope>
import (scope& base,
name tgt,
- bool ph2,
+ const optional<string>& ph2,
bool opt,
bool metadata,
const location& loc)
@@ -2602,25 +2981,29 @@ namespace build2
//
if (metadata)
{
- pair<const target*, import_kind> r (
+ import_result<target> r (
import_direct (base, move (tgt), ph2, opt, metadata, loc));
- return make_pair (r.first != nullptr ? r.first->as_name () : names {},
- r.second);
+ return import_result<scope> {
+ r.target != nullptr ? r.target->base_scope ().root_scope () : nullptr,
+ move (r.name),
+ r.kind};
}
- // Save the original target name as metadata key.
- //
- auto meta (metadata ? optional<string> (tgt.value) : nullopt);
-
pair<name, optional<dir_path>> r (
- import_search (base, move (tgt), opt, meta, true /* subpproj */, loc));
+ import_search (base,
+ move (tgt),
+ opt,
+ nullopt /* metadata */,
+ true /* subpproj */,
+ loc));
// If there is no project, we are either done or go straight to phase 2.
//
if (!r.second || r.second->empty ())
{
names ns;
+ const target* t (nullptr);
if (r.first.empty ())
{
@@ -2636,18 +3019,32 @@ namespace build2
//
if (ns.back ().qualified ())
{
- if (ph2)
+ if (ns.back ().type == "buildfile")
+ {
+ assert (ph2);
+ ns = import2_buildfile (ctx, move (ns), opt && !r.second, loc);
+ }
+ else if (ph2)
{
// This is tricky: we only want the optional semantics for the
// fallback case.
//
- if (const target* t = import (ctx,
- base.find_prerequisite_key (ns, loc),
- opt && !r.second /* optional */,
- meta,
- 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
+ // any problems since the import() call assigns the extension.
+ //
ns = t->as_name ();
+ }
else
ns.clear (); // NULL
}
@@ -2656,35 +3053,99 @@ namespace build2
}
}
- return make_pair (
+ return import_result<scope> {
+ 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), metadata, loc).first,
- k);
+ pair<names, const scope&> p (
+ import_load (base.ctx, move (r), false /* metadata */, loc));
+
+ return import_result<scope> {&p.second, move (p.first), k};
}
const target*
- import (context& ctx,
- const prerequisite_key& pk,
- bool opt,
- const optional<string>& meta,
- bool exist,
- const location& loc)
+ import2 (context& ctx,
+ const prerequisite_key& pk,
+ const string& hint,
+ bool opt,
+ const optional<string>& meta,
+ bool exist,
+ const location& loc)
{
- tracer trace ("import");
+ tracer trace ("import2");
- 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_impl().
+ //
+ // 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);
@@ -2734,12 +3195,21 @@ namespace build2
if (*t != nullptr && (*t)->vars[ctx.var_export_metadata].defined ())
return *t; // We've got all we need.
+ auto df = make_diag_frame (
+ [&proj, &tt, &tk] (const diag_record& dr)
+ {
+ import_suggest (dr, proj, &tt, *tk.name, false, "alternative ");
+ });
+
if (!(md = extract_metadata (pp, *meta, opt, loc)))
break;
}
if (!t || *t == nullptr)
{
+ // Note: we need the lock because process_path() call below is not
+ // MT-safe.
+ //
pair<target&, ulock> r (insert_target (trace, ctx, tt, p));
t = &r.first;
@@ -2761,6 +3231,8 @@ namespace build2
return *t;
}
+ // NOTE: see similar code in import2() below if changing anything here.
+
if (opt || exist)
return nullptr;
@@ -2771,58 +3243,178 @@ namespace build2
dr << info << "consider adding its installation location" <<
info << "or explicitly specify its project name";
else
+ // Use metadata as proxy for immediate import.
+ //
+ import_suggest (dr, proj, &tt, *tk.name, meta && hint.empty ());
+
+ dr << endf;
+ }
+
+ // As above but with scope/ns instead of pk. This version deals with the
+ // unknown target type case.
+ //
+ static const target*
+ import2 (context& ctx,
+ const scope& base, names& ns,
+ const string& hint,
+ bool opt,
+ const optional<string>& meta,
+ bool exist,
+ const location& loc)
+ {
+ // If we have a rule hint, then it's natural to expect this target type is
+ // known to the importing project. Ditto for project-less import.
+ //
+ const target_type* tt (nullptr);
+ if (hint.empty ())
{
- string projv (proj.variable ());
+ size_t n;
+ if ((n = ns.size ()) != 0 && n == (ns[0].pair ? 2 : 1))
+ {
+ const name& n (ns.front ());
- // Suggest normal import.
- //
- dr << info << "use config.import." << projv << " configuration variable "
- << "to specify its project out_root";
+ if (n.typed () && !n.proj->empty ())
+ {
+ tt = base.find_target_type (n.type);
+
+ if (tt == nullptr)
+ {
+ // A subset of code in the above version of import2().
+ //
+ if (opt || exist)
+ return nullptr;
+
+ diag_record dr;
+ dr << fail (loc) << "unable to import target " << ns;
+ import_suggest (dr, *n.proj, nullptr, string (), meta.has_value ());
+ }
+ }
+ }
+ }
- // Suggest ad hoc import but only if it's a path-based target (doing it
- // for lib{} is very confusing).
+ return import2 (ctx,
+ base.find_prerequisite_key (ns, loc, tt),
+ hint,
+ opt,
+ meta,
+ exist,
+ loc);
+ }
+
+ static names
+ import2_buildfile (context&, names&& ns, bool opt, const location& loc)
+ {
+ tracer trace ("import2_buildfile");
+
+ assert (ns.size () == 1);
+ name n (move (ns.front ()));
+
+ // Our approach doesn't work for targets without a project so let's fail
+ // hard, even if optional.
+ //
+ if (!n.proj || n.proj->empty ())
+ fail (loc) << "unable to import target " << n << " without project name";
+
+ while (!build_install_buildfile.empty ()) // Breakout loop.
+ {
+ path f (build_install_buildfile /
+ dir_path (n.proj->string ()) /
+ n.dir /
+ n.value);
+
+ // See if we need to try with extensions.
//
- if (tt.is_a<path_target> ())
+ bool ext (path_traits::find_extension (n.value) == string::npos &&
+ n.value != std_buildfile_file.string () &&
+ n.value != alt_buildfile_file.string ());
+
+ if (ext)
{
- string v (tt.is_a<exe> () && (projv == *tk.name || proj == *tk.name)
- ? "config." + projv
- : "config.import." + projv + '.' + *tk.name + '.' + tt.name);
+ f += '.';
+ f += std_build_ext;
+ }
- dr << info << "or use " << v << " configuration variable to specify "
- << "its path";
+ if (!exists (f))
+ {
+ l6 ([&]{trace << "tried " << f;});
+
+ if (ext)
+ {
+ f.make_base ();
+ f += '.';
+ f += alt_build_ext;
+
+ if (!exists (f))
+ {
+ l6 ([&]{trace << "tried " << f;});
+ break;
+ }
+ }
+ else
+ break;
}
+
+ // Split the path into the target.
+ //
+ ns = {name (f.directory (), move (n.type), f.leaf ().string ())};
+ return move (ns);
}
+ if (opt)
+ return names {};
+
+ diag_record dr;
+ dr << fail (loc) << "unable to import target " << n;
+
+ import_suggest (dr, *n.proj, nullptr /* tt */, n.value, false);
+
+ if (build_install_buildfile.empty ())
+ dr << info << "no exported buildfile installation location is "
+ << "configured in build2";
+ else
+ dr << info << "exported buildfile installation location is "
+ << build_install_buildfile;
+
dr << endf;
}
- pair<const target*, import_kind>
+ import_result<target>
import_direct (bool& new_value,
scope& base,
name tgt,
- bool ph2,
+ const optional<string>& ph2,
bool opt,
bool metadata,
const location& loc,
const char* what)
{
- // This is like normal import() except we return the target rather than
+ // This is like normal import() except we return the target in addition to
// its name.
//
tracer trace ("import_direct");
l5 ([&]{trace << tgt << " from " << base << " for " << what;});
- assert ((!opt || ph2) && (!metadata || ph2));
+ assert ((!opt || ph2) && (!metadata || ph2) && tgt.type != "buildfile");
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<string> (tgt.value) : nullopt);
- names ns;
+ names ns, rns;
import_kind k;
const target* pt (nullptr);
+ const scope* iroot (nullptr); // Imported root scope.
+
+ // Original project/name as imported for diagnostics.
+ //
+ string oname (meta ? tgt.value : string ());
+ project_name oproj (meta && tgt.proj ? *tgt.proj : project_name ());
pair<name, optional<dir_path>> r (
import_search (new_value,
@@ -2843,7 +3435,7 @@ namespace build2
if (r.first.empty ())
{
assert (opt);
- return make_pair (pt, k); // NULL
+ return import_result<target> {nullptr, {}, k}; // NULL
}
else if (r.first.qualified ())
{
@@ -2854,38 +3446,72 @@ namespace build2
// This is tricky: we only want the optional semantics for the
// fallback case.
//
- pt = import (ctx,
- base.find_prerequisite_key (ns, loc),
- opt && !r.second,
- meta,
- false /* existing */,
- loc);
+ pt = import2 (ctx,
+ base, ns,
+ *ph2,
+ opt && !r.second,
+ meta,
+ false /* existing */,
+ loc);
}
if (pt == nullptr)
- return make_pair (pt, k); // NULL
+ return import_result<target> {nullptr, {}, k}; // NULL
- // Otherwise fall through.
+ // Note that here r.first was still project-qualified and we have no
+ // choice but to call as_name() (below). This shouldn't cause any
+ // problems since the import() call assigns the extension.
+
+ // Fall through.
}
else
+ {
+ // 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);
+
ns.push_back (move (r.first)); // And fall through.
+ }
}
else
{
k = r.first.absolute () ? import_kind::adhoc : import_kind::normal;
- ns = import_load (base.ctx, move (r), metadata, loc).first;
+
+ pair<names, const scope&> p (
+ import_load (base.ctx, move (r), metadata, loc));
+
+ rns = ns = move (p.first);
+ iroot = &p.second;
}
if (pt == nullptr)
{
- // Similar logic to perform's search().
+ // Import (more precisely, alias) the target type into this project
+ // if not known.
//
- target_key tk (base.find_target_key (ns, loc));
+ 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, tt));
pt = ctx.targets.find (tk, trace);
if (pt == nullptr)
fail (loc) << "unknown imported target " << tk;
}
+ if (rns.empty ())
+ rns = pt->as_name ();
+
target& t (pt->rw ()); // Load phase.
// Note that if metadata is requested via any of the import*() functions,
@@ -2894,10 +3520,20 @@ namespace build2
//
if (meta)
{
+ auto df = make_diag_frame (
+ [&oproj, &oname, &t] (const diag_record& dr)
+ {
+ if (!oproj.empty ())
+ import_suggest (dr, oproj, &t.type (), oname, false, "alternative ");
+ });
+
// 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));
@@ -2915,7 +3551,7 @@ namespace build2
catch (const invalid_argument& e)
{
fail (loc) << "invalid metadata version in imported target " << t
- << ": " << e;
+ << ": " << e << endf;
}
if (ver != 1)
@@ -2930,13 +3566,15 @@ namespace build2
const string& pfx (ns[1].value);
- auto& vp (ctx.var_pool.rw ()); // Load phase.
-
// See if we have the stable program name in the <var-prefix>.name
// variable. If its missing, set it to the metadata key (i.e., target
// name as imported) by default.
//
{
+ // Note: go straight for the public variable pool.
+ //
+ auto& vp (ctx.var_pool.rw ()); // Load phase.
+
value& nv (t.assign (vp.insert (pfx + ".name")));
if (!nv)
nv = *meta;
@@ -2947,28 +3585,51 @@ namespace build2
//
if (const auto* e = cast_null<strings> (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
fail (loc) << "no metadata for imported target " << t;
}
- return make_pair (pt, k);
+ return import_result<target> {pt, move (rns), k};
+ }
+
+ path
+ import_buildfile (scope& bs, name n, bool opt, const location& loc)
+ {
+ names r (import (bs,
+ move (n),
+ string () /* phase2 */,
+ opt,
+ false /* metadata */,
+ loc).name);
+
+ path p;
+ if (!r.empty ()) // Optional not found.
+ {
+ // Note: see also parse_import().
+ //
+ assert (r.size () == 1); // See import_load() for details.
+ name& n (r.front ());
+ p = n.dir / n.value; // Should already include extension.
+ }
+ else
+ assert (opt);
+
+ return p;
}
ostream&
- operator<< (ostream& o, const pair<const exe*, import_kind>& p)
+ operator<< (ostream& o, const import_result<exe>& r)
{
- assert (p.first != nullptr);
+ assert (r.target != nullptr);
- if (p.second == import_kind::normal)
- o << *p.first;
+ if (r.kind == import_kind::normal)
+ o << *r.target;
else
- o << p.first->process_path ();
+ o << r.target->process_path ();
return o;
}
@@ -3008,13 +3669,23 @@ namespace build2
//
mkdir (d / std_build_dir, verbosity);
+ auto diag = [verbosity] (const path& f)
+ {
+ if (verb >= verbosity)
+ {
+ if (verb >= 2)
+ text << "cat >" << f;
+ else if (verb)
+ print_diag ("save", f);
+ }
+ };
+
// Write build/bootstrap.build.
//
{
path f (d / std_bootstrap_file);
- if (verb >= verbosity)
- text << (verb >= 2 ? "cat >" : "save ") << f;
+ diag (f);
try
{
@@ -3060,8 +3731,7 @@ namespace build2
{
path f (d / std_root_file);
- if (verb >= verbosity)
- text << (verb >= 2 ? "cat >" : "save ") << f;
+ diag (f);
try
{
@@ -3109,8 +3779,7 @@ namespace build2
{
path f (d / std_build_dir / "config.build"); // std_config_file
- if (verb >= verbosity)
- text << (verb >= 2 ? "cat >" : "save ") << f;
+ diag (f);
try
{
@@ -3135,8 +3804,7 @@ namespace build2
{
path f (d / std_buildfile_file);
- if (verb >= verbosity)
- text << (verb >= 2 ? "cat >" : "save ") << f;
+ diag (f);
try
{