aboutsummaryrefslogtreecommitdiff
path: root/libbuild2/file.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'libbuild2/file.cxx')
-rw-r--r--libbuild2/file.cxx898
1 files changed, 737 insertions, 161 deletions
diff --git a/libbuild2/file.cxx b/libbuild2/file.cxx
index 4cf6d82..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,37 +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 */
- ""} /* environment_checksum */);
+
+ 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
@@ -621,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&
@@ -842,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 ()));
@@ -913,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");
@@ -943,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.
@@ -985,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;
{
@@ -1005,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
{
@@ -1071,6 +1123,12 @@ namespace build2
// no been configured. In this case falling through is what we want.
}
}
+ else if (v)
+ {
+ 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.
//
@@ -1129,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.
@@ -1285,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);
@@ -1328,7 +1394,7 @@ namespace build2
}
void
- create_bootstrap_outer (scope& root)
+ create_bootstrap_outer (scope& root, bool subp)
{
context& ctx (root.ctx);
@@ -1376,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
@@ -1387,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);
@@ -1475,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 "
@@ -1499,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);
@@ -1530,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,
@@ -1545,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).
//
{
@@ -1556,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:
//
@@ -1579,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)
@@ -1634,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&
@@ -1690,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.
}
@@ -1812,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);
@@ -1872,10 +1995,19 @@ namespace build2
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;
}
@@ -1891,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;
@@ -1916,8 +2059,7 @@ namespace build2
goto fail;
}
- fail:
-
+ fail:
if (opt)
{
metadata_cache.insert (pp.effect_string (), true);
@@ -1946,15 +2088,13 @@ namespace build2
&t);
}
- // Suggest appropriate ways to import the specified target (as type and
- // name) from the specified project.
- //
- static void
+ void
import_suggest (const diag_record& dr,
const project_name& pn,
- const target_type& tt,
+ const target_type* tt,
const string& tn,
- const char* qual = nullptr)
+ bool rule_hint,
+ const char* qual)
{
string pv (pn.variable ());
@@ -1966,15 +2106,19 @@ namespace build2
// Suggest ad hoc import but only if it's a path-based target (doing it
// for lib{} is very confusing).
//
- if (tt.is_a<path_target> ())
+ if (tt != nullptr && tt->is_a<path_target> ())
{
- string v (tt.is_a<exe> () && (pv == tn || pn == tn)
+ string v (tt->is_a<exe> () && (pv == tn || pn == tn)
? "config." + pv
- : "config.import." + pv + '.' + tn + '.' + tt.name);
+ : "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
@@ -1989,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,
@@ -2020,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 ())
@@ -2027,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).
//
@@ -2043,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 ()));
}
@@ -2075,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;
@@ -2245,7 +2399,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 (),
@@ -2368,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
@@ -2430,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));
@@ -2516,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;
@@ -2527,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.
//
@@ -2544,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);
@@ -2559,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
@@ -2574,7 +2839,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;
{
@@ -2585,7 +2851,7 @@ namespace build2
});
parser p (ctx);
- v = p.parse_export_stub (ifs, path_name (es), gs, ts);
+ v = p.parse_export_stub (ifs, path_name (es), *root, gs, ts);
}
// If there were no export directive executed in an export stub,
@@ -2595,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)
{
@@ -2652,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)
@@ -2682,7 +2984,10 @@ namespace build2
import_result<target> r (
import_direct (base, move (tgt), ph2, opt, metadata, loc));
- return make_pair (move (r.name), r.kind);
+ return import_result<scope> {
+ r.target != nullptr ? r.target->base_scope ().root_scope () : nullptr,
+ move (r.name),
+ r.kind};
}
pair<name, optional<dir_path>> r (
@@ -2698,6 +3003,7 @@ namespace build2
if (!r.second || r.second->empty ())
{
names ns;
+ const target* t (nullptr);
if (r.first.empty ())
{
@@ -2713,17 +3019,25 @@ 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 */,
- 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
@@ -2739,40 +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), false /* 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);
@@ -2825,8 +3198,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)))
@@ -2835,6 +3207,9 @@ namespace build2
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;
@@ -2856,6 +3231,8 @@ namespace build2
return *t;
}
+ // NOTE: see similar code in import2() below if changing anything here.
+
if (opt || exist)
return nullptr;
@@ -2866,7 +3243,137 @@ 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;
+ }
+
+ // 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 ())
+ {
+ size_t n;
+ if ((n = ns.size ()) != 0 && n == (ns[0].pair ? 2 : 1))
+ {
+ const name& n (ns.front ());
+
+ 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 ());
+ }
+ }
+ }
+ }
+
+ 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.
+ //
+ bool ext (path_traits::find_extension (n.value) == string::npos &&
+ n.value != std_buildfile_file.string () &&
+ n.value != alt_buildfile_file.string ());
+
+ if (ext)
+ {
+ f += '.';
+ f += std_build_ext;
+ }
+
+ 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;
}
@@ -2875,7 +3382,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,
@@ -2888,11 +3395,13 @@ namespace build2
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);
@@ -2900,6 +3409,12 @@ namespace build2
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,
@@ -2931,12 +3446,13 @@ 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)
@@ -2953,6 +3469,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);
@@ -2962,14 +3480,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<names, const scope&> 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;
@@ -2986,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));
@@ -3007,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)
@@ -3022,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;
@@ -3039,10 +3585,8 @@ 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
@@ -3052,6 +3596,31 @@ namespace build2
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 import_result<exe>& r)
{
@@ -3100,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
{
@@ -3152,8 +3731,7 @@ namespace build2
{
path f (d / std_root_file);
- if (verb >= verbosity)
- text << (verb >= 2 ? "cat >" : "save ") << f;
+ diag (f);
try
{
@@ -3201,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
{
@@ -3227,8 +3804,7 @@ namespace build2
{
path f (d / std_buildfile_file);
- if (verb >= verbosity)
- text << (verb >= 2 ? "cat >" : "save ") << f;
+ diag (f);
try
{