aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build/algorithm.cxx2
-rw-r--r--build/algorithm.ixx4
-rw-r--r--build/b.cxx66
-rw-r--r--build/bin/module.cxx2
-rw-r--r--build/cli/module.cxx2
-rw-r--r--build/config/module.cxx6
-rw-r--r--build/config/operation.cxx14
-rw-r--r--build/context.cxx7
-rw-r--r--build/cxx/compile.cxx4
-rw-r--r--build/cxx/link.cxx4
-rw-r--r--build/cxx/module.cxx2
-rw-r--r--build/dist/module.cxx2
-rw-r--r--build/dist/operation.cxx2
-rw-r--r--build/dist/rule.cxx2
-rw-r--r--build/dump.cxx72
-rw-r--r--build/file15
-rw-r--r--build/file.cxx93
-rw-r--r--build/install/module.cxx2
-rw-r--r--build/operation.cxx10
-rw-r--r--build/parser9
-rw-r--r--build/parser.cxx160
-rw-r--r--build/prerequisite.cxx2
-rw-r--r--build/rule-map6
-rw-r--r--build/scope63
-rw-r--r--build/scope.cxx162
-rw-r--r--build/search.cxx4
-rw-r--r--build/target.cxx2
-rw-r--r--build/test/module.cxx2
-rw-r--r--tests/import/installed/build/bootstrap.build1
-rw-r--r--tests/import/installed/buildfile8
-rw-r--r--tests/scope/amalgamation/build/bootstrap.build3
-rw-r--r--tests/scope/amalgamation/buildfile1
-rw-r--r--tests/scope/amalgamation/l1/build/bootstrap.build16
-rw-r--r--tests/scope/amalgamation/l1/buildfile37
-rw-r--r--tests/scope/amalgamation/l1/l2/build/bootstrap.build2
-rw-r--r--tests/scope/test-1.out15
-rw-r--r--tests/scope/test-2.out15
-rwxr-xr-xtests/scope/test.sh12
-rw-r--r--tests/simple/build/bootstrap.build3
-rw-r--r--tests/simple/buildfile6
-rw-r--r--tests/simple/driver.cxx8
41 files changed, 600 insertions, 248 deletions
diff --git a/build/algorithm.cxx b/build/algorithm.cxx
index f462bb8..9a2f9fb 100644
--- a/build/algorithm.cxx
+++ b/build/algorithm.cxx
@@ -323,7 +323,7 @@ namespace build
if (rs == nullptr) // Could be outside any project.
return;
- const dir_path& out_root (rs->path ());
+ const dir_path& out_root (rs->out_path ());
// If t is a directory (name is empty), say foo/bar/, then
// t is bar and its parent directory is foo/.
diff --git a/build/algorithm.ixx b/build/algorithm.ixx
index ac2b1fc..72a04fe 100644
--- a/build/algorithm.ixx
+++ b/build/algorithm.ixx
@@ -109,7 +109,7 @@ namespace build
t,
a.operation () != clean_id
? dir_path ()
- : t.strong_scope ().path ());
+ : t.strong_scope ().out_path ());
}
inline void
@@ -122,7 +122,7 @@ namespace build
// through groups since the group target should clean eveything
// up. A bit of an optimization.
//
- search_and_match_prerequisites (a, t, t.strong_scope ().path ());
+ search_and_match_prerequisites (a, t, t.strong_scope ().out_path ());
}
target_state
diff --git a/build/b.cxx b/build/b.cxx
index 38a944c..d2d813a 100644
--- a/build/b.cxx
+++ b/build/b.cxx
@@ -401,7 +401,7 @@ main (int argc, char* argv[])
// as a result of one of the preceding target processing.
//
// If we know src_root, set that variable as well. This could
- // be of use to the bootstrap file (other than src-root.build,
+ // be of use to the bootstrap files (other than src-root.build,
// which, BTW, doesn't need to exist if src_root == out_root).
//
scope& rs (create_root (out_root, src_root));
@@ -410,50 +410,48 @@ main (int argc, char* argv[])
// See if the bootstrap process set/changed src_root.
//
- {
- value& v (rs.assign ("src_root"));
+ value& v (rs.assign ("src_root"));
- if (v)
- {
- // If we also have src_root specified by the user, make
- // sure they match.
- //
- const dir_path& p (as<dir_path> (v));
+ if (v)
+ {
+ // If we also have src_root specified by the user, make
+ // sure they match.
+ //
+ const dir_path& p (as<dir_path> (v));
- if (src_root.empty ())
- src_root = p;
- else if (src_root != p)
- fail << "bootstrapped src_root " << p << " does not match "
- << "specified " << src_root;
- }
- else
+ if (src_root.empty ())
+ src_root = p;
+ else if (src_root != p)
+ fail << "bootstrapped src_root " << p << " does not match "
+ << "specified " << src_root;
+ }
+ else
+ {
+ // Neither bootstrap nor the user produced src_root.
+ //
+ if (src_root.empty ())
{
- // Bootstrap didn't produce src_root.
+ // If it also wasn't explicitly specified, see if it is
+ // the same as out_root.
//
- if (src_root.empty ())
+ if (is_src_root (out_root))
+ src_root = out_root;
+ else
{
- // If it also wasn't explicitly specified, see if it is
- // the same as out_root.
+ // If not, then assume we are running from src_base
+ // and calculate src_root based on out_root/out_base.
//
- if (is_src_root (out_root))
- src_root = out_root;
- else
- {
- // If not, then assume we are running from src_base
- // and calculate src_root based on out_root/out_base.
- //
- src_base = work;
- src_root = src_base.directory (out_base.leaf (out_root));
- guessing = true;
- }
+ src_base = work;
+ src_root = src_base.directory (out_base.leaf (out_root));
+ guessing = true;
}
-
- v = src_root;
}
- rs.src_path_ = &as<dir_path> (v);
+ v = src_root;
}
+ setup_root (rs);
+
// At this stage we should have both roots and out_base figured
// out. If src_base is still undetermined, calculate it.
//
diff --git a/build/bin/module.cxx b/build/bin/module.cxx
index 760c90d..ba10181 100644
--- a/build/bin/module.cxx
+++ b/build/bin/module.cxx
@@ -37,7 +37,7 @@ namespace build
bool first)
{
tracer trace ("bin::init");
- level4 ([&]{trace << "for " << b.path ();});
+ level4 ([&]{trace << "for " << b.out_path ();});
// Register target types.
//
diff --git a/build/cli/module.cxx b/build/cli/module.cxx
index 0e81db3..f8d1522 100644
--- a/build/cli/module.cxx
+++ b/build/cli/module.cxx
@@ -36,7 +36,7 @@ namespace build
bool first)
{
tracer trace ("cli::init");
- level4 ([&]{trace << "for " << base.path ();});
+ level4 ([&]{trace << "for " << base.out_path ();});
// Make sure the cxx module has been loaded since we need its
// targets types (?xx{}). Note that we don't try to load it
diff --git a/build/config/module.cxx b/build/config/module.cxx
index 5ecf2d3..74bcec4 100644
--- a/build/config/module.cxx
+++ b/build/config/module.cxx
@@ -42,7 +42,7 @@ namespace build
return;
}
- const dir_path& out_root (r.path ());
+ const dir_path& out_root (r.out_path ());
level4 ([&]{trace << "for " << out_root;});
// Register meta-operations.
@@ -52,7 +52,11 @@ namespace build
// Register alias and fallback rule for the configure meta-operation.
//
+ global_scope->rules.insert<file> (
+ configure_id, 0, "file", file_rule::instance);
+
r.rules.insert<alias> (configure_id, 0, "alias", alias_rule::instance);
+ r.rules.insert<file> (configure_id, 0, "", fallback_rule::instance);
r.rules.insert<target> (configure_id, 0, "", fallback_rule::instance);
// Load config.build if one exists.
diff --git a/build/config/operation.cxx b/build/config/operation.cxx
index b1c6239..424fd46 100644
--- a/build/config/operation.cxx
+++ b/build/config/operation.cxx
@@ -65,7 +65,7 @@ namespace build
static void
save_config (scope& root)
{
- const dir_path& out_root (root.path ());
+ const dir_path& out_root (root.out_path ());
path f (out_root / config_file);
text << (verb ? "config::save_config " : "save ") << f;
@@ -134,7 +134,7 @@ namespace build
{
tracer trace ("configure_project");
- const dir_path& out_root (root.path ());
+ const dir_path& out_root (root.out_path ());
const dir_path& src_root (root.src_path ());
// Make sure the directories exist.
@@ -179,7 +179,7 @@ namespace build
// @@ Strictly speaking we need to check whether the config
// module was loaded for this subproject.
//
- if (nroot.path () != out_nroot) // This subproject was not loaded.
+ if (nroot.out_path () != out_nroot) // This subproject not loaded.
continue;
configure_project (a, nroot);
@@ -273,7 +273,7 @@ namespace build
action_targets& ts)
{
tracer trace ("disfigure_search");
- level5 ([&]{trace << "collecting " << root.path ();});
+ level5 ([&]{trace << "collecting " << root.out_path ();});
ts.push_back (&root);
}
@@ -287,7 +287,7 @@ namespace build
bool m (false); // Keep track of whether we actually did anything.
- const dir_path& out_root (root.path ());
+ const dir_path& out_root (root.out_path ());
const dir_path& src_root (root.src_path ());
// Disfigure subprojects. Since we don't load buildfiles during
@@ -313,7 +313,7 @@ namespace build
if (!val)
val = is_src_root (out_nroot) ? out_nroot : (src_root / pd);
- nroot.src_path_ = &as<dir_path> (val);
+ setup_root (nroot);
bootstrap_src (nroot);
@@ -398,7 +398,7 @@ namespace build
//
target& t (
targets.insert (
- dir::static_type, root.path (), "", nullptr, trace).first);
+ dir::static_type, root.out_path (), "", nullptr, trace).first);
if (!quiet)
info << diag_done (a, t);
diff --git a/build/context.cxx b/build/context.cxx
index aaeb426..fa1ea8f 100644
--- a/build/context.cxx
+++ b/build/context.cxx
@@ -77,7 +77,8 @@ namespace build
// On POSIX, however, this is a real path. See the comment in
// <build/path-map> for details.
//
- global_scope = &scopes[dir_path ("/")];
+ global_scope = scopes.insert (
+ dir_path ("/"), nullptr, true, false)->second;
global_scope->assign ("work") = work;
global_scope->assign ("home") = home;
@@ -188,14 +189,14 @@ namespace build
src_out (const dir_path& out, scope& s)
{
scope& rs (*s.root_scope ());
- return src_out (out, rs.path (), rs.src_path ());
+ return src_out (out, rs.out_path (), rs.src_path ());
}
dir_path
out_src (const dir_path& src, scope& s)
{
scope& rs (*s.root_scope ());
- return out_src (src, rs.path (), rs.src_path ());
+ return out_src (src, rs.out_path (), rs.src_path ());
}
dir_path
diff --git a/build/cxx/compile.cxx b/build/cxx/compile.cxx
index 514c57a..f9351f2 100644
--- a/build/cxx/compile.cxx
+++ b/build/cxx/compile.cxx
@@ -88,7 +88,7 @@ namespace build
const dir_path* amlg (
a.operation () != clean_id
? nullptr
- : &t.strong_scope ().path ());
+ : &t.strong_scope ().out_path ());
link::search_paths_cache lib_paths; // Extract lazily.
@@ -213,7 +213,7 @@ namespace build
return;
const dir_path& out_base (t.dir);
- const dir_path& out_root (rs->path ());
+ const dir_path& out_root (rs->out_path ());
if (auto l = t[var])
{
diff --git a/build/cxx/link.cxx b/build/cxx/link.cxx
index 3b628b5..2554930 100644
--- a/build/cxx/link.cxx
+++ b/build/cxx/link.cxx
@@ -492,7 +492,7 @@ namespace build
const dir_path* amlg (
a.operation () != clean_id
? nullptr
- : &t.strong_scope ().path ());
+ : &t.strong_scope ().out_path ());
for (prerequisite_member p: group_prerequisite_members (a, t))
{
@@ -573,7 +573,7 @@ namespace build
// altogether. So we are going to use the target's project.
//
root = &t.root_scope ();
- out_root = &root->path ();
+ out_root = &root->out_path ();
src_root = &root->src_path ();
}
diff --git a/build/cxx/module.cxx b/build/cxx/module.cxx
index 7171738..6b7da80 100644
--- a/build/cxx/module.cxx
+++ b/build/cxx/module.cxx
@@ -35,7 +35,7 @@ namespace build
bool first)
{
tracer trace ("cxx::init");
- level4 ([&]{trace << "for " << b.path ();});
+ level4 ([&]{trace << "for " << b.out_path ();});
// Initialize the bin module. Only do this if it hasn't already
// been loaded so that we don't overwrite user's bin.* settings.
diff --git a/build/dist/module.cxx b/build/dist/module.cxx
index ab27df7..48c1fd0 100644
--- a/build/dist/module.cxx
+++ b/build/dist/module.cxx
@@ -40,7 +40,7 @@ namespace build
return;
}
- const dir_path& out_root (r.path ());
+ const dir_path& out_root (r.out_path ());
level4 ([&]{trace << "for " << out_root;});
// Register meta-operation.
diff --git a/build/dist/operation.cxx b/build/dist/operation.cxx
index 3c6ec98..c4d022b 100644
--- a/build/dist/operation.cxx
+++ b/build/dist/operation.cxx
@@ -79,7 +79,7 @@ namespace build
if (rs == nullptr)
fail << "out of project target " << t;
- const dir_path& out_root (rs->path ());
+ const dir_path& out_root (rs->out_path ());
const dir_path& src_root (rs->src_path ());
if (out_root == src_root)
diff --git a/build/dist/rule.cxx b/build/dist/rule.cxx
index bda2ef7..6977676 100644
--- a/build/dist/rule.cxx
+++ b/build/dist/rule.cxx
@@ -24,7 +24,7 @@ namespace build
recipe rule::
apply (action a, target& t, const match_result&) const
{
- const dir_path& out_root (t.root_scope ().path ());
+ const dir_path& out_root (t.root_scope ().out_path ());
for (prerequisite_member p: group_prerequisite_members (a, t))
{
diff --git a/build/dump.cxx b/build/dump.cxx
index f3b8c4b..7f4502e 100644
--- a/build/dump.cxx
+++ b/build/dump.cxx
@@ -4,7 +4,6 @@
#include <build/dump>
-#include <set>
#include <string>
#include <cassert>
@@ -142,19 +141,21 @@ namespace build
dump_scope (ostream& os,
string& ind,
action a,
- scope& p,
- scope_map::iterator& i,
- set<const target*>& rts)
+ scope_map::const_iterator& i)
{
+ scope& p (*i->second);
+ const dir_path& d (i->first);
+ ++i;
+
// We don't want the extra notations (e.g., ~/) provided by
// diag_relative() since we want the path to be relative to
- // the global scope.
+ // the outer scope.
//
- os << ind << relative (p.path ()) << ":" << endl
+ os << ind << relative (d) << ":" << endl
<< ind << '{';
const dir_path* orb (relative_base);
- relative_base = &p.path ();
+ relative_base = &d;
ind += " ";
@@ -179,10 +180,25 @@ namespace build
vb = true;
}
- // Nested scopes of which we are a parent.
+ // Nested scopes of which we are an immediate parent.
//
- for (auto e (scopes.end ()); i != e && i->second.parent_scope () == &p; )
+ for (auto e (scopes.end ()); i != e && i->second->parent_scope () == &p;)
{
+ // See what kind of scope entry this is. It can be:
+ //
+ // 1. Out-of-project scope.
+ // 2. In-project out entry.
+ // 3. In-project src entry.
+ //
+ // We want to print #2 and #3 as a single, unified scope.
+ //
+ scope& s (*i->second);
+ if (s.src_path_ != s.out_path_ && s.src_path_ == &i->first)
+ {
+ ++i;
+ continue;
+ }
+
if (vb)
{
os << endl;
@@ -193,9 +209,7 @@ namespace build
os << endl; // Extra newline between scope blocks.
os << endl;
- scope& s (i->second);
- dump_scope (os, ind, a, s, ++i, rts);
-
+ dump_scope (os, ind, a, i);
sb = true;
}
@@ -204,33 +218,8 @@ namespace build
for (const auto& pt: targets)
{
const target& t (*pt);
- const scope* ts (&t.base_scope ());
-
- bool f (false);
- if (ts == &p)
- {
- // If this is the global scope, check that this target hasn't
- // been handled by the src logic below.
- //
- f = (ts != global_scope || rts.find (&t) == rts.end ());
- }
- // If this target is in the global scope and we have a corresponding
- // src directory (i.e., we are a scope inside a project), check
- // whether this target is in our src.
- //
- else if (ts == global_scope && p.src_path_ != nullptr)
- {
- if (t.dir.sub (p.src_path ()))
- {
- // Check that it hasn't already been handled by a more qualified
- // scope.
- //
- f = rts.insert (&t).second;
- }
- }
-
- if (!f)
+ if (&p != &t.base_scope ())
continue;
if (vb || sb)
@@ -254,14 +243,11 @@ namespace build
dump (action a)
{
auto i (scopes.begin ());
- scope& g (i->second); // Global scope.
- assert (&g == global_scope);
+ assert (i->second == global_scope);
string ind;
- set<const target*> rts;
-
ostream& os (*diag_stream);
- dump_scope (os, ind, a, g, ++i, rts);
+ dump_scope (os, ind, a, i);
os << endl;
}
}
diff --git a/build/file b/build/file
index 64d8ad2..c2b2535 100644
--- a/build/file
+++ b/build/file
@@ -9,11 +9,11 @@
#include <string>
#include <build/types>
+#include <build/scope>
#include <build/variable> // list_value
namespace build
{
- class scope;
class target;
class location;
class prerequisite_key;
@@ -68,6 +68,19 @@ namespace build
scope&
create_root (const dir_path& out_root, const dir_path& src_root);
+ // Setup root scope. Note that it assume the src_root variable
+ // has already been set.
+ //
+ void
+ setup_root (scope&);
+
+ // Setup the base scope (set *_base variables, etc).
+ //
+ scope&
+ setup_base (scope_map::iterator,
+ const dir_path& out_base,
+ const dir_path& src_base);
+
// Bootstrap the project's root scope, the out part.
//
void
diff --git a/build/file.cxx b/build/file.cxx
index b303740..dd9b294 100644
--- a/build/file.cxx
+++ b/build/file.cxx
@@ -115,7 +115,16 @@ namespace build
scope&
create_root (const dir_path& out_root, const dir_path& src_root)
{
- scope& rs (scopes.insert (out_root, true).first);
+ auto i (scopes.insert (out_root, nullptr, true, true));
+ scope& rs (*i->second);
+
+ // Set out_path. src_path is set in setup_root() below.
+ //
+ if (rs.out_path_ != &i->first)
+ {
+ assert (rs.out_path_ == nullptr);
+ rs.out_path_ = &i->first;
+ }
// Enter built-in meta-operation and operation names. Loading of
// modules (via the src bootstrap; see below) can result in
@@ -168,9 +177,70 @@ namespace build
}
void
+ setup_root (scope& s)
+ {
+ value& v (s.assign ("src_root"));
+ assert (v);
+
+ // Register and set src_path.
+ //
+ if (s.src_path_ == nullptr)
+ s.src_path_ = &scopes.insert (as<dir_path> (v), &s, false, true)->first;
+ }
+
+ scope&
+ setup_base (scope_map::iterator i,
+ const dir_path& out_base,
+ const dir_path& src_base)
+ {
+ scope& s (*i->second);
+
+ // Set src/out_path. The key (i->first) can be either out_base
+ // or src_base.
+ //
+ if (s.out_path_ == nullptr)
+ {
+ s.out_path_ =
+ i->first == out_base
+ ? &i->first
+ : &scopes.insert (out_base, &s, true, false)->first;
+ }
+
+ if (s.src_path_ == nullptr)
+ {
+ s.src_path_ =
+ i->first == src_base
+ ? &i->first
+ : &scopes.insert (src_base, &s, false, false)->first;
+ }
+
+ // Set src/out_base variables.
+ //
+ {
+ value& v (s.assign ("out_base"));
+
+ if (!v)
+ v = out_base;
+ else
+ assert (as<dir_path> (v) == out_base);
+ }
+
+ {
+ value& v (s.assign ("src_base"));
+
+ if (!v)
+ v = src_base;
+ else
+ assert (as<dir_path> (v) == src_base);
+ }
+
+ return s;
+ }
+
+ void
bootstrap_out (scope& root)
{
- path bf (root.path () / path ("build/bootstrap/src-root.build"));
+ path bf (root.out_path () / path ("build/bootstrap/src-root.build"));
if (!file_exists (bf))
return;
@@ -334,7 +404,7 @@ namespace build
bool r (false);
- const dir_path& out_root (root.path ());
+ const dir_path& out_root (root.out_path ());
const dir_path& src_root (root.src_path ());
path bf (src_root / path ("build/bootstrap.build"));
@@ -370,7 +440,7 @@ namespace build
if (scope* aroot = root.parent_scope ()->root_scope ())
{
- const dir_path& ad (aroot->path ());
+ const dir_path& ad (aroot->out_path ());
dir_path rd (ad.relative (out_root));
// If we already have the amalgamation variable set, verify
@@ -526,7 +596,7 @@ namespace build
return;
const dir_path& d (as<dir_path> (*l));
- dir_path out_root (root.path () / d);
+ dir_path out_root (root.out_path () / d);
out_root.normalize ();
// src_root is a bit more complicated. Here we have three cases:
@@ -557,7 +627,7 @@ namespace build
}
}
- rs.src_path_ = &as<dir_path> (v);
+ setup_root (rs);
bootstrap_src (rs);
create_bootstrap_outer (rs);
@@ -578,7 +648,7 @@ namespace build
if (n.pair != '\0')
continue; // Skip project names.
- dir_path out_root (root.path () / n.dir);
+ dir_path out_root (root.out_path () / n.dir);
if (!out_base.sub (out_root))
continue;
@@ -595,7 +665,7 @@ namespace build
? out_root
: (root.src_path () / n.dir);
- rs.src_path_ = &as<dir_path> (v);
+ setup_root (rs);
bootstrap_src (rs);
@@ -671,7 +741,7 @@ namespace build
if (i != m.end ())
{
const dir_path& d ((*i).second);
- out_root = r->path () / d;
+ out_root = r->out_path () / d;
fallback_src_root = r->src_path () / d;
break;
}
@@ -745,8 +815,6 @@ namespace build
if (!src_root.empty () && p != src_root)
fail (loc) << "bootstrapped src_root " << p << " does not match "
<< "discovered " << src_root;
-
- root.src_path_ = &p;
}
// Otherwise, use fallback if available.
//
@@ -754,12 +822,13 @@ namespace build
{
value& v (root.assign ("src_root"));
v = move (fallback_src_root);
- root.src_path_ = &as<dir_path> (v);
}
else
fail (loc) << "unable to determine src_root for imported " << project <<
info << "consider configuring " << out_root;
+ setup_root (root);
+
bootstrap_src (root);
// Bootstrap outer roots if any. Loading will be done by
diff --git a/build/install/module.cxx b/build/install/module.cxx
index 5bf2876..454ff3e 100644
--- a/build/install/module.cxx
+++ b/build/install/module.cxx
@@ -109,7 +109,7 @@ namespace build
return;
}
- const dir_path& out_root (r.path ());
+ const dir_path& out_root (r.out_path ());
level4 ([&]{trace << "for " << out_root;});
// Register the install operation.
diff --git a/build/operation.cxx b/build/operation.cxx
index 55a926a..9cc8025 100644
--- a/build/operation.cxx
+++ b/build/operation.cxx
@@ -62,13 +62,11 @@ namespace build
load_root_pre (root);
// Create the base scope. Note that its existence doesn't
- // mean it was already processed as a base scope; it can
- // be the same as root.
+ // mean it was already setup as a base scope; it can be the
+ // same as root.
//
- scope& base (scopes[out_base]);
-
- base.assign ("out_base") = out_base;
- base.src_path_ = &as<dir_path> (base.assign ("src_base") = src_base);
+ auto i (scopes.insert (out_base, nullptr, true, false));
+ scope& base (setup_base (i, out_base, src_base));
// Load the buildfile unless it has already been loaded.
//
diff --git a/build/parser b/build/parser
index 3374ac0..c5c33a1 100644
--- a/build/parser
+++ b/build/parser
@@ -100,6 +100,15 @@ namespace build
// Utilities.
//
private:
+
+ // Switch to a new current scope. Note that this function might
+ // also have to switch to a new root scope if the new current
+ // scope is in another project. So both must be saved and
+ // restored.
+ //
+ void
+ switch_scope (const dir_path&);
+
// Switch to new root scope and return the previous one.
//
scope*
diff --git a/build/parser.cxx b/build/parser.cxx
index 9c50d17..470efdb 100644
--- a/build/parser.cxx
+++ b/build/parser.cxx
@@ -101,37 +101,31 @@ namespace build
// @@ Is this the only place where it is valid? Probably also
// in var namespace.
//
- next (t, tt);
print (t, tt);
continue;
}
else if (n == "source")
{
- next (t, tt);
source (t, tt);
continue;
}
else if (n == "include")
{
- next (t, tt);
include (t, tt);
continue;
}
else if (n == "import")
{
- next (t, tt);
import (t, tt);
continue;
}
else if (n == "export")
{
- next (t, tt);
export_ (t, tt);
continue;
}
else if (n == "using")
{
- next (t, tt);
using_ (t, tt);
continue;
}
@@ -198,26 +192,23 @@ namespace build
dir_path p (move (ns[0].dir)); // Steal.
+ // Relative scopes are opened relative to out, not src.
+ //
if (p.relative ())
- p = prev.path () / p;
+ p = scope_->out_path () / p;
p.normalize ();
- scope_ = &scopes[p];
- // If this is a known project root scope, switch the
- // parser state to use it.
- //
- scope* ors (switch_root (scope_->root () ? scope_ : root_));
-
- if (ors != root_)
- level4 ([&]{trace (nloc) << "switching to root scope " << p;});
+ scope* ors (root_);
+ scope* ocs (scope_);
+ switch_scope (p);
// A directory scope can contain anything that a top level can.
//
clause (t, tt);
+ scope_ = ocs;
switch_root (ors);
- scope_ = &prev;
}
else
{
@@ -269,11 +260,11 @@ namespace build
path& d (tn.dir);
if (d.empty ())
- d = scope_->path (); // Already normalized.
+ d = scope_->out_path (); // Already normalized.
else
{
if (d.relative ())
- d = scope_->path () / d;
+ d = scope_->out_path () / d;
d.normalize ();
}
@@ -301,9 +292,6 @@ namespace build
if (n.qualified ())
fail (nloc) << "project name in scope/target " << n;
- target* ot (target_);
- scope* os (scope_);
-
if (n.directory ())
{
// The same code as in directory scope handling code above.
@@ -311,18 +299,29 @@ namespace build
dir_path p (move (n.dir));
if (p.relative ())
- p = scope_->path () / p;
+ p = scope_->out_path () / p;
p.normalize ();
- scope_ = &scopes[move (p)];
+
+ scope* ors (root_);
+ scope* ocs (scope_);
+ switch_scope (p);
+
+ variable (t, tt, move (var), tt);
+
+ scope_ = ocs;
+ switch_root (ors);
+
}
else
+ {
+ target* ot (target_);
target_ = &enter_target (move (n));
- variable (t, tt, move (var), tt);
+ variable (t, tt, move (var), tt);
- scope_ = os;
- target_ = ot;
+ target_ = ot;
+ }
}
// Dependency declaration.
//
@@ -416,6 +415,7 @@ namespace build
// The rest should be a list of buildfiles. Parse them as names
// to get variable expansion and directory prefixes.
//
+ next (t, tt);
const location l (get_location (t, &path_));
names_type ns (tt != type::newline && tt != type::eos
? names (t, tt)
@@ -435,7 +435,7 @@ namespace build
// to the current directory scope.
//
if (src_root_ != nullptr && p.relative ())
- p = src_out (scope_->path (), *out_root_, *src_root_) / p;
+ p = src_out (scope_->out_path (), *out_root_, *src_root_) / p;
p.normalize ();
@@ -504,6 +504,7 @@ namespace build
// The rest should be a list of buildfiles. Parse them as names
// to get variable expansion and directory prefixes.
//
+ next (t, tt);
const location l (get_location (t, &path_));
names_type ns (tt != type::newline && tt != type::eos
? names (t, tt)
@@ -536,7 +537,7 @@ namespace build
if (p.relative ())
{
- out_base = scope_->path () / p.directory ();
+ out_base = scope_->out_path () / p.directory ();
out_base.normalize ();
}
else
@@ -555,28 +556,25 @@ namespace build
: out_src (p.directory (), *out_root_, *src_root_);
}
- // Create and bootstrap root scope(s) of subproject(s) that
- // this out_base belongs to. If any were created, load them
- // and update parser state. Note that we need to do this
- // before figuring out absolute buildfile path since we may
- // switch the project root (i.e., include into a sub-project).
+ // Switch the scope. Note that we need to do this before figuring
+ // out the absolute buildfile path since we may switch the project
+ // root and src_root with it (i.e., include into a sub-project).
//
- scope* ors (switch_root (&create_bootstrap_inner (*root_, out_base)));
-
- if (root_ != ors)
- load_root_pre (*root_); // Loads outer roots recursively.
+ scope* ors (root_);
+ scope* ocs (scope_);
+ switch_scope (out_base);
- // Determine src_base and buildfile, if relative.
+ // Use the new scope's src_base to get absolute buildfile path
+ // if it is relative.
//
- dir_path src_base (src_out (out_base, *out_root_, *src_root_));
-
if (p.relative ())
- p = src_base / p.leaf ();
+ p = scope_->src_path () / p.leaf ();
if (!root_->buildfiles.insert (p).second) // Note: may be "new" root.
{
level4 ([&]{trace (l) << "skipping already included " << p;});
- switch_root (ors); // Restore old root.
+ scope_ = ocs;
+ switch_root (ors);
continue;
}
@@ -599,13 +597,6 @@ namespace build
lexer* ol (lexer_);
lexer_ = &l;
- scope* os (scope_);
- scope_ = &scopes[out_base];
-
- scope_->assign ("out_base") = move (out_base);
- scope_->src_path_ = &as<dir_path> (
- scope_->assign ("src_base") = move (src_base));
-
target* odt (default_target_);
default_target_ = nullptr;
@@ -622,10 +613,10 @@ namespace build
level4 ([&]{trace (t) << "leaving " << p;});
default_target_ = odt;
- scope_ = os;
lexer_ = ol;
path_ = op;
+ scope_ = ocs;
switch_root (ors);
}
@@ -643,6 +634,8 @@ namespace build
if (src_root_ == nullptr)
fail (t) << "import during bootstrap";
+ next (t, tt);
+
// General import format:
//
// import [<var>=](<project>|<project>/<target>])+
@@ -704,12 +697,15 @@ namespace build
// This should be temp_scope.
//
- if (ps == nullptr || ps->path () != scope_->path ())
+ if (ps == nullptr || ps->out_path () != scope_->out_path ())
fail (t) << "export outside export stub";
// The rest is a value. Parse it as names to get variable expansion.
// build::import() will check the names, if required.
//
+ lexer_->mode (lexer_mode::value);
+ next (t, tt);
+
if (tt != type::newline && tt != type::eos)
export_value_ = names (t, tt);
@@ -727,6 +723,7 @@ namespace build
// The rest should be a list of module names. Parse them as names
// to get variable expansion, etc.
//
+ next (t, tt);
const location l (get_location (t, &path_));
names_type ns (tt != type::newline && tt != type::eos
? names (t, tt)
@@ -751,6 +748,13 @@ namespace build
void parser::
print (token& t, token_type& tt)
{
+ // Parse the rest as names to get variable expansion, etc. Switch
+ // to the variable value lexing mode so that we don't treat special
+ // characters (e.g., ':') as the end of the names.
+ //
+ lexer_->mode (lexer_mode::value);
+
+ next (t, tt);
names_type ns (tt != type::newline && tt != type::eos
? names (t, tt)
: names_type ());
@@ -786,6 +790,8 @@ namespace build
if (var.pairs != '\0')
lexer_->mode (lexer_mode::pairs, var.pairs);
+ else
+ lexer_->mode (lexer_mode::value);
next (t, tt);
names_type vns (tt != type::newline && tt != type::eos
@@ -1452,6 +1458,54 @@ namespace build
return bs;
}
+ void parser::
+ switch_scope (const dir_path& p)
+ {
+ tracer trace ("parser::switch_scope", &path_);
+
+ // 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 (scopes.insert (p, nullptr, true, false));
+ scope_ = i->second;
+ scope* rs (scope_->root_scope ());
+
+ if (rs == nullptr)
+ return;
+
+ // Path p can be src_base or out_base. Figure out which one it is.
+ //
+ dir_path out_base (p.sub (rs->out_path ()) ? p : src_out (p, *rs));
+
+ // Create and bootstrap root scope(s) of subproject(s) that this
+ // scope may belong to. If any were created, load them. Note that
+ // we need to do this before figuring out src_base since we may
+ // switch the root project (and src_root with it).
+ //
+ {
+ scope* nrs (&create_bootstrap_inner (*rs, out_base));
+
+ if (rs != nrs)
+ {
+ load_root_pre (*nrs); // Load outer roots recursively.
+ rs = nrs;
+ }
+ }
+
+ // Switch to the new root scope.
+ //
+ if (rs != root_)
+ {
+ level4 ([&]{trace << "switching to root scope " << rs->out_path ();});
+ switch_root (rs);
+ }
+
+ // Now we can figure out src_base and finish setting the scope.
+ //
+ dir_path src_base (src_out (out_base, *rs));
+ setup_base (i, move (out_base), move (src_base));
+ }
+
scope* parser::
switch_root (scope* nr)
{
@@ -1489,7 +1543,7 @@ namespace build
//
if (default_target_ == nullptr || // No targets in this buildfile.
targets.find (dir::static_type, // Explicit current dir target.
- scope_->path (),
+ scope_->out_path (),
"",
nullptr,
trace) != targets.end ())
@@ -1501,7 +1555,7 @@ namespace build
target& ct (
targets.insert (
- dir::static_type, scope_->path (), "", nullptr, trace).first);
+ dir::static_type, scope_->out_path (), "", nullptr, trace).first);
prerequisite& p (
scope_->prerequisites.insert (
diff --git a/build/prerequisite.cxx b/build/prerequisite.cxx
index 2437826..1f9f68b 100644
--- a/build/prerequisite.cxx
+++ b/build/prerequisite.cxx
@@ -29,7 +29,7 @@ namespace build
//
else if (!pk.tk.dir->absolute ())
{
- string s (diag_relative (pk.scope->path (), false));
+ string s (diag_relative (pk.scope->out_path (), false));
if (!s.empty ())
os << s << ':';
diff --git a/build/rule-map b/build/rule-map
index 0ef0036..0ce49e7 100644
--- a/build/rule-map
+++ b/build/rule-map
@@ -52,6 +52,9 @@ namespace build
return map_.size () > oid ? &map_[oid] : nullptr;
}
+ bool
+ empty () const {return map_.empty ();}
+
private:
std::vector<target_type_rule_map> map_;
};
@@ -92,6 +95,9 @@ namespace build
explicit
rule_map (meta_operation_id mid = perform_id): mid_ (mid) {}
+ bool
+ empty () const {return map_.empty () && next_ == nullptr;}
+
private:
meta_operation_id mid_;
operation_rule_map map_;
diff --git a/build/scope b/build/scope
index 50c4f6b..2afc9f4 100644
--- a/build/scope
+++ b/build/scope
@@ -24,13 +24,18 @@ namespace build
class scope
{
public:
+ // Absolute and normalized.
+ //
const dir_path&
- path () const {return *path_;} // Absolute and normalized.
+ out_path () const {return *out_path_;}
const dir_path&
- src_path () const {return *src_path_;} // Corresponding src path.
+ src_path () const {return *src_path_;}
- const dir_path* src_path_ {nullptr}; // Cached src_{root,base} var value.
+ // These are pointers to the keys in scope_map.
+ //
+ const dir_path* out_path_ {nullptr};
+ const dir_path* src_path_ {nullptr};
scope*
parent_scope () const {return parent_;}
@@ -154,6 +159,22 @@ namespace build
public:
loaded_module_map modules; // Only on root scope.
+ public:
+ bool
+ empty () const
+ {
+ return
+ vars.empty () &&
+ target_vars.empty () &&
+ prerequisites.empty () &&
+ meta_operations.empty () &&
+ operations.empty () &&
+ buildfiles.empty () &&
+ target_types.empty () &&
+ rules.empty () &&
+ modules.empty ();
+ }
+
private:
friend class scope_map;
friend class temp_scope;
@@ -165,7 +186,6 @@ namespace build
scope () = default;
- const dir_path* path_; // Pointer to the key in scope_map.
scope* parent_;
scope* root_;
scope* strong_ = nullptr; // Only set on root sopes.
@@ -185,33 +205,35 @@ namespace build
public:
temp_scope (scope& p)
{
- path_ = p.path_;
+ out_path_ = p.out_path_;
+ src_path_ = p.src_path_;
parent_ = &p;
root_ = p.root_;
// No need to copy strong_ since we are never root scope.
}
};
- using scope_map_base = butl::dir_path_map<scope>;
- class scope_map: public scope_map_base
+ class scope_map
{
public:
+ using map_type = butl::dir_path_map<scope*>;
+ using iterator = map_type::iterator;
+ using const_iterator = map_type::const_iterator;
+
// Note that we assume the first insertion into the map is that
- // of the global scope.
+ // of the global scope. If the passed scope pointer is not NULL,
+ // then insert this scope instead of a new one.
//
- std::pair<scope&, bool>
- insert (const dir_path&, bool root);
-
- scope&
- operator[] (const dir_path& p) {return insert (p, false).first;}
+ iterator
+ insert (const dir_path&, scope*, bool parent, bool root);
// Find the most qualified scope that encompasses this path.
//
scope&
- find (const dir_path&);
+ find (const dir_path&) const;
scope&
- find (const path& p)
+ find (const path& p) const
{
// Natural thing to do here would be to call find (p.directory ()).
// However, there could be a situation where the passed path is a
@@ -220,6 +242,17 @@ namespace build
//
return find (dir_path (p.string ()));
}
+
+ const_iterator begin () const {return map_.begin ();}
+ const_iterator end () const {return map_.end ();}
+
+ void
+ clear ();
+
+ ~scope_map () {clear ();}
+
+ private:
+ map_type map_;
};
extern scope_map scopes;
diff --git a/build/scope.cxx b/build/scope.cxx
index e6d165e..53e3a53 100644
--- a/build/scope.cxx
+++ b/build/scope.cxx
@@ -146,93 +146,151 @@ namespace build
scope_map scopes;
scope* global_scope;
- pair<scope&, bool> scope_map::
- insert (const dir_path& k, bool root)
+ auto scope_map::
+ insert (const dir_path& k, scope* ns, bool parent, bool root) -> iterator
{
- auto er (emplace (k, scope ()));
- scope& s (er.first->second);
+ auto er (map_.emplace (k, nullptr));
+ scope*& ps (er.first->second);
if (er.second)
+ ps = ns == nullptr ? new scope : ns;
+ else if (ns != nullptr && ps != ns)
{
- scope* p (nullptr);
+ assert (ps->out_path_ == nullptr || ps->src_path_ == nullptr);
- // Update scopes of which we are a new parent/root (unless this
- // is the global scope).
+ if (!ps->empty ())
+ fail << "attempt to replace non-empty scope " << k;
+
+ // Un-parent ourselves. We will becomes a new parent below,
+ // if requested by the caller.
//
- if (size () > 1)
+ auto r (map_.find_prefix (k)); // The first entry is ourselves.
+ for (++r.first; r.first != r.second; ++r.first)
+ {
+ scope& c (*r.first->second);
+
+ if (c.parent_ == ps) // No intermediate parent.
+ c.parent_ = ps->parent_;
+ }
+
+ delete ps;
+ ps = ns;
+ er.second = true;
+ }
+
+ scope& s (*ps);
+
+ if (parent)
+ {
+ if (er.second)
{
- // The first entry is ourselves.
+ scope* p (nullptr);
+
+ // Update scopes of which we are a new parent/root (unless this
+ // is the global scope). Also find our parent while at it.
//
- auto r (find_prefix (k));
- for (++r.first; r.first != r.second; ++r.first)
+ if (map_.size () > 1)
{
- scope& c (r.first->second);
-
- // The first scope of which we are a parent is the least
- // (shortest) one which means there is no other scope
- // between it and our parent.
+ // The first entry is ourselves.
+ //
+ auto r (map_.find_prefix (k));
+ for (++r.first; r.first != r.second; ++r.first)
+ {
+ scope& c (*r.first->second);
+
+ // The child-parent relationship is based on the out hierarchy,
+ // thus the extra check.
+ //
+ if (c.out_path_ != nullptr && !c.out_path_->sub (k))
+ continue;
+
+ // The first scope of which we are a parent is the least
+ // (shortest) one which means there is no other scope
+ // between it and our parent.
+ //
+ if (p == nullptr)
+ p = c.parent_;
+
+ if (root && c.root_ == p->root_) // No intermediate root.
+ c.root_ = &s;
+
+ if (p == c.parent_) // No intermediate parent.
+ c.parent_ = &s;
+ }
+
+ // We couldn't get the parent from one of its old children
+ // so we have to find it ourselves.
//
if (p == nullptr)
- p = c.parent_;
-
- if (root && c.root_ == p->root_) // No intermediate root.
- c.root_ = &s;
-
- if (p == c.parent_) // No intermediate parent.
- c.parent_ = &s;
+ p = &find (k.directory ());
}
- // We couldn't get the parent from one of its old children
- // so we have to find it ourselves.
- //
- if (p == nullptr)
- p = &find (k.directory ());
+ s.parent_ = p;
+ s.root_ = root ? &s : (p != nullptr ? p->root_ : nullptr);
}
-
- s.path_ = &er.first->first;
- s.parent_ = p;
- s.root_ = root ? &s : (p != nullptr ? p->root_ : nullptr);
- }
- else if (root && !s.root ())
- {
- // Upgrade to root scope.
- //
- auto r (find_prefix (k));
- for (++r.first; r.first != r.second; ++r.first)
+ else if (root && !s.root ())
{
- scope& c (r.first->second);
+ // Upgrade to root scope.
+ //
+ auto r (map_.find_prefix (k));
+ for (++r.first; r.first != r.second; ++r.first)
+ {
+ scope& c (*r.first->second);
- if (c.root_ == s.root_) // No intermediate root.
- c.root_ = &s;
- }
+ if (c.root_ == s.root_) // No intermediate root.
+ c.root_ = &s;
+ }
- s.root_ = &s;
+ s.root_ = &s;
+ }
}
+ else
+ assert (s.parent_ != nullptr);
- return pair<scope&, bool> (s, er.second);
+ return er.first;
}
// Find the most qualified scope that encompasses this path.
//
scope& scope_map::
- find (const dir_path& k)
+ find (const dir_path& k) const
{
// Normally we would have a scope for the full path so try
// that before making any copies.
//
- auto i (scope_map_base::find (k));
+ auto i (map_.find (k)), e (map_.end ());
- if (i != end ())
- return i->second;
+ if (i != e)
+ return *i->second;
for (dir_path d (k.directory ());; d = d.directory ())
{
- auto i (scope_map_base::find (d));
+ auto i (map_.find (d));
- if (i != end ())
- return i->second;
+ if (i != e)
+ return *i->second;
assert (!d.empty ()); // We should have the global scope.
}
}
+
+ void scope_map::
+ clear ()
+ {
+ for (auto& p: map_)
+ {
+ scope* s (p.second);
+
+ if (s->out_path_ == &p.first)
+ s->out_path_ = nullptr;
+
+ if (s->src_path_ == &p.first)
+ s->src_path_ = nullptr;
+
+ if (s->out_path_ == nullptr && s->src_path_ == nullptr)
+ delete s;
+ }
+
+ map_.clear ();
+ }
}
diff --git a/build/search.cxx b/build/search.cxx
index dfb603d..32302d6 100644
--- a/build/search.cxx
+++ b/build/search.cxx
@@ -33,7 +33,7 @@ namespace build
d = *tk.dir; // Already normalized.
else
{
- d = pk.scope->path ();
+ d = pk.scope->out_path ();
if (!tk.dir->empty ())
{
@@ -148,7 +148,7 @@ namespace build
d = *tk.dir; // Already normalized.
else
{
- d = pk.scope->path ();
+ d = pk.scope->out_path ();
if (!tk.dir->empty ())
{
diff --git a/build/target.cxx b/build/target.cxx
index 669ff7a..9e0bea7 100644
--- a/build/target.cxx
+++ b/build/target.cxx
@@ -378,7 +378,7 @@ namespace build
if (pk.tk.dir->relative ())
{
dir_paths sp;
- sp.push_back (src_out (pk.scope->path (), *pk.scope)); // src_base
+ sp.push_back (pk.scope->src_path ()); // src_base
return search_existing_file (pk, sp);
}
else
diff --git a/build/test/module.cxx b/build/test/module.cxx
index 8c0df38..b3378ba 100644
--- a/build/test/module.cxx
+++ b/build/test/module.cxx
@@ -39,7 +39,7 @@ namespace build
return;
}
- const dir_path& out_root (r.path ());
+ const dir_path& out_root (r.out_path ());
level4 ([&]{trace << "for " << out_root;});
// Register the test operation.
diff --git a/tests/import/installed/build/bootstrap.build b/tests/import/installed/build/bootstrap.build
index 97fd11f..2af1e60 100644
--- a/tests/import/installed/build/bootstrap.build
+++ b/tests/import/installed/build/bootstrap.build
@@ -1,3 +1,4 @@
project = import-installed
amalgamation = # Disabled.
using config
+using test
diff --git a/tests/import/installed/buildfile b/tests/import/installed/buildfile
index 276de36..abdc151 100644
--- a/tests/import/installed/buildfile
+++ b/tests/import/installed/buildfile
@@ -5,5 +5,9 @@ cxx.ext = cxx
import libs += lib{z}
-#exe{driver}: cxx{driver} $libs
-lib{driver}: cxx{driver} $libs
+#lib{driver}: cxx{driver} $libs
+
+exe{driver}: cxx{driver} $libs
+exe{driver}: test = true
+
+
diff --git a/tests/scope/amalgamation/build/bootstrap.build b/tests/scope/amalgamation/build/bootstrap.build
new file mode 100644
index 0000000..ee73365
--- /dev/null
+++ b/tests/scope/amalgamation/build/bootstrap.build
@@ -0,0 +1,3 @@
+project = scope-amalgamation
+amalgamation = # Disabled.
+using config
diff --git a/tests/scope/amalgamation/buildfile b/tests/scope/amalgamation/buildfile
new file mode 100644
index 0000000..9f80de9
--- /dev/null
+++ b/tests/scope/amalgamation/buildfile
@@ -0,0 +1 @@
+./:
diff --git a/tests/scope/amalgamation/l1/build/bootstrap.build b/tests/scope/amalgamation/l1/build/bootstrap.build
new file mode 100644
index 0000000..6bde838
--- /dev/null
+++ b/tests/scope/amalgamation/l1/build/bootstrap.build
@@ -0,0 +1,16 @@
+project = scope-amalgamation-l1
+using config
+
+# At this stage we don't know ../ is a project. This
+# tests an out-of-project scope that will later be
+# replaced with an in-project scope. Note that the
+# replacement will only occur if src_root != out_root.
+# If they are the same, then this scope will simply
+# be "upgraded".
+#
+$src_root/../:
+{
+ print 0: $project
+ print 0: $src_base
+ print 0: $out_base
+}
diff --git a/tests/scope/amalgamation/l1/buildfile b/tests/scope/amalgamation/l1/buildfile
new file mode 100644
index 0000000..55d8c64
--- /dev/null
+++ b/tests/scope/amalgamation/l1/buildfile
@@ -0,0 +1,37 @@
+# Out of amalgamation.
+#
+../../:
+{
+ print -1: $project
+ print -1: $src_base
+ print -1: $out_base
+}
+
+# In amalgamation.
+#
+../s/:
+{
+ print 0: $project
+ print 0: $src_base
+ print 0: $out_base
+}
+
+# In project.
+#
+s/:
+{
+ print 1: $project
+ print 1: $src_base
+ print 1: $out_base
+}
+
+# In sub-project.
+#
+l2/s/:
+{
+ print 2: $project
+ print 2: $src_base
+ print 2: $out_base
+}
+
+./:
diff --git a/tests/scope/amalgamation/l1/l2/build/bootstrap.build b/tests/scope/amalgamation/l1/l2/build/bootstrap.build
new file mode 100644
index 0000000..0262763
--- /dev/null
+++ b/tests/scope/amalgamation/l1/l2/build/bootstrap.build
@@ -0,0 +1,2 @@
+project = scope-amalgamation-l2
+using config
diff --git a/tests/scope/test-1.out b/tests/scope/test-1.out
new file mode 100644
index 0000000..0273bf1
--- /dev/null
+++ b/tests/scope/test-1.out
@@ -0,0 +1,15 @@
+0:
+0:
+0:
+-1:
+-1:
+-1:
+0: scope-amalgamation
+0: /home/boris/work/build2/build2/tests/scope/amalgamation/s/
+0: /home/boris/work/build2/build2/tests/scope/amalgamation/s/
+1: scope-amalgamation-l1
+1: /home/boris/work/build2/build2/tests/scope/amalgamation/l1/s/
+1: /home/boris/work/build2/build2/tests/scope/amalgamation/l1/s/
+2: scope-amalgamation-l2
+2: /home/boris/work/build2/build2/tests/scope/amalgamation/l1/l2/s/
+2: /home/boris/work/build2/build2/tests/scope/amalgamation/l1/l2/s/
diff --git a/tests/scope/test-2.out b/tests/scope/test-2.out
new file mode 100644
index 0000000..88f9620
--- /dev/null
+++ b/tests/scope/test-2.out
@@ -0,0 +1,15 @@
+0:
+0:
+0:
+-1:
+-1:
+-1:
+0: scope-amalgamation
+0: /home/boris/work/build2/build2/tests/scope/amalgamation/s/
+0: /home/boris/work/build2/build2/tests/scope/a-out/s/
+1: scope-amalgamation-l1
+1: /home/boris/work/build2/build2/tests/scope/amalgamation/l1/s/
+1: /home/boris/work/build2/build2/tests/scope/a-out/l1/s/
+2: scope-amalgamation-l2
+2: /home/boris/work/build2/build2/tests/scope/amalgamation/l1/l2/s/
+2: /home/boris/work/build2/build2/tests/scope/a-out/l1/l2/s/
diff --git a/tests/scope/test.sh b/tests/scope/test.sh
new file mode 100755
index 0000000..5bbf0d8
--- /dev/null
+++ b/tests/scope/test.sh
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+# In-tree.
+#
+valgrind b amalgamation/l1/ 2>/dev/null | diff -u test-1.out -
+
+# Out-of-tree.
+#
+rm -rf a-out/
+b 'configure(amalgamation/@a-out/)' 2>/dev/null
+valgrind b amalgamation/l1/@a-out/l1/ 2>/dev/null | diff -u test-2.out -
+rm -rf a-out/
diff --git a/tests/simple/build/bootstrap.build b/tests/simple/build/bootstrap.build
new file mode 100644
index 0000000..7ee30cb
--- /dev/null
+++ b/tests/simple/build/bootstrap.build
@@ -0,0 +1,3 @@
+project = simple
+amalgamation = # Disabled.
+using config
diff --git a/tests/simple/buildfile b/tests/simple/buildfile
new file mode 100644
index 0000000..986f391
--- /dev/null
+++ b/tests/simple/buildfile
@@ -0,0 +1,6 @@
+using cxx
+
+hxx.ext = hxx
+cxx.ext = cxx
+
+exe{driver}: cxx{driver}
diff --git a/tests/simple/driver.cxx b/tests/simple/driver.cxx
new file mode 100644
index 0000000..5b076c7
--- /dev/null
+++ b/tests/simple/driver.cxx
@@ -0,0 +1,8 @@
+#include <iostream>
+
+using namespace std;
+
+int
+main ()
+{
+}