aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2015-08-31 13:45:57 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2015-08-31 13:45:57 +0200
commit5974cab56148a18628bfb423189e016ade2d40f9 (patch)
tree472a7966d0e1c5725f0736c73812cbdeaab827dc
parent2a9d673f298b623db061ee85d397563d644c8268 (diff)
Rework scoping logic
Now the src directory is entered into the scope map and points to the same scope as out. This means that targets that are in src, not out (e.g., source files) will "see" rules, variables, etc. This becomes important when we try, for example, to install a source file (say, a header) from src: we need the rule as well as the install.* variables.
-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 ()
+{
+}