aboutsummaryrefslogtreecommitdiff
path: root/build2
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2016-01-05 11:55:15 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2016-01-05 11:55:15 +0200
commit9fb791e9fad6c63fc1dac49f4d05ae63b8a3db9b (patch)
treed60322d4382ca5f97b676c5abe2e39524f35eab4 /build2
parentf159b1dac68c8714f7ba71ca168e3b695891aad9 (diff)
Rename build directory/namespace to build2
Diffstat (limited to 'build2')
-rw-r--r--build2/.gitignore2
-rw-r--r--build2/algorithm222
-rw-r--r--build2/algorithm.cxx504
-rw-r--r--build2/algorithm.ixx197
-rw-r--r--build2/algorithm.txx58
-rw-r--r--build2/b.cxx855
-rw-r--r--build2/bin/module23
-rw-r--r--build2/bin/module.cxx188
-rw-r--r--build2/bin/rule39
-rw-r--r--build2/bin/rule.cxx145
-rw-r--r--build2/bin/target99
-rw-r--r--build2/bin/target.cxx190
-rw-r--r--build2/buildfile64
-rw-r--r--build2/cli/module23
-rw-r--r--build2/cli/module.cxx244
-rw-r--r--build2/cli/rule32
-rw-r--r--build2/cli/rule.cxx305
-rw-r--r--build2/cli/target62
-rw-r--r--build2/cli/target.cxx77
-rw-r--r--build2/config/module26
-rw-r--r--build2/config/module.cxx90
-rw-r--r--build2/config/operation19
-rw-r--r--build2/config/operation.cxx455
-rw-r--r--build2/config/utility128
-rw-r--r--build2/config/utility.cxx92
-rw-r--r--build2/config/utility.ixx17
-rw-r--r--build2/config/utility.txx45
-rw-r--r--build2/context165
-rw-r--r--build2/context.cxx391
-rw-r--r--build2/context.txx141
-rw-r--r--build2/cxx/compile32
-rw-r--r--build2/cxx/compile.cxx794
-rw-r--r--build2/cxx/install29
-rw-r--r--build2/cxx/install.cxx66
-rw-r--r--build2/cxx/link70
-rw-r--r--build2/cxx/link.cxx875
-rw-r--r--build2/cxx/module23
-rw-r--r--build2/cxx/module.cxx230
-rw-r--r--build2/cxx/target78
-rw-r--r--build2/cxx/target.cxx81
-rw-r--r--build2/cxx/utility37
-rw-r--r--build2/cxx/utility.cxx29
-rw-r--r--build2/cxx/utility.txx35
-rw-r--r--build2/diagnostics402
-rw-r--r--build2/diagnostics.cxx125
-rw-r--r--build2/dist/module26
-rw-r--r--build2/dist/module.cxx142
-rw-r--r--build2/dist/operation18
-rw-r--r--build2/dist/operation.cxx459
-rw-r--r--build2/dist/rule29
-rw-r--r--build2/dist/rule.cxx55
-rw-r--r--build2/dump18
-rw-r--r--build2/dump.cxx253
-rw-r--r--build2/file144
-rw-r--r--build2/file.cxx980
-rw-r--r--build2/file.ixx12
-rw-r--r--build2/install/module26
-rw-r--r--build2/install/module.cxx188
-rw-r--r--build2/install/operation18
-rw-r--r--build2/install/operation.cxx32
-rw-r--r--build2/install/rule49
-rw-r--r--build2/install/rule.cxx410
-rw-r--r--build2/install/utility40
-rw-r--r--build2/lexer138
-rw-r--r--build2/lexer.cxx431
-rw-r--r--build2/module86
-rw-r--r--build2/module.cxx114
-rw-r--r--build2/name113
-rw-r--r--build2/name.cxx63
-rw-r--r--build2/operation359
-rw-r--r--build2/operation.cxx232
-rw-r--r--build2/options328
-rw-r--r--build2/options.cli31
-rw-r--r--build2/options.cxx549
-rw-r--r--build2/options.ixx165
-rw-r--r--build2/parser296
-rw-r--r--build2/parser.cxx2206
-rw-r--r--build2/path-io26
-rw-r--r--build2/path-io.cxx39
-rw-r--r--build2/prerequisite129
-rw-r--r--build2/prerequisite.cxx82
-rw-r--r--build2/rule135
-rw-r--r--build2/rule-map115
-rw-r--r--build2/rule.cxx249
-rw-r--r--build2/scope312
-rw-r--r--build2/scope.cxx317
-rw-r--r--build2/search31
-rw-r--r--build2/search.cxx171
-rw-r--r--build2/spec61
-rw-r--r--build2/spec.cxx81
-rw-r--r--build2/target1084
-rw-r--r--build2/target-key55
-rw-r--r--build2/target-type97
-rw-r--r--build2/target.cxx537
-rw-r--r--build2/target.ixx85
-rw-r--r--build2/target.txx58
-rw-r--r--build2/test/module24
-rw-r--r--build2/test/module.cxx88
-rw-r--r--build2/test/operation18
-rw-r--r--build2/test/operation.cxx32
-rw-r--r--build2/test/rule30
-rw-r--r--build2/test/rule.cxx439
-rw-r--r--build2/token73
-rw-r--r--build2/token.cxx35
-rw-r--r--build2/types55
-rw-r--r--build2/utility85
-rw-r--r--build2/utility.cxx94
-rw-r--r--build2/variable831
-rw-r--r--build2/variable.cxx452
-rw-r--r--build2/variable.ixx398
-rw-r--r--build2/variable.txx168
-rw-r--r--build2/version37
112 files changed, 22337 insertions, 0 deletions
diff --git a/build2/.gitignore b/build2/.gitignore
new file mode 100644
index 0000000..c9855e8
--- /dev/null
+++ b/build2/.gitignore
@@ -0,0 +1,2 @@
+b
+b-*
diff --git a/build2/algorithm b/build2/algorithm
new file mode 100644
index 0000000..2cfec62
--- /dev/null
+++ b/build2/algorithm
@@ -0,0 +1,222 @@
+// file : build2/algorithm -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_ALGORITHM
+#define BUILD2_ALGORITHM
+
+#include <string>
+#include <utility> // pair
+
+#include <build2/types>
+#include <build2/target>
+#include <build2/operation>
+
+namespace build2
+{
+ class scope;
+ class prerequisite;
+ class prerequisite_key;
+
+ // The default prerequisite search implementation. It first calls the
+ // target-type-specific search function. If that doesn't yeld anything,
+ // it creates a new target.
+ //
+ target&
+ search (prerequisite&);
+
+ // As above but specify the prerequisite to search as a key.
+ //
+ target&
+ search (const prerequisite_key&);
+
+ // As above but override the target type. Useful for searching for
+ // target group members where we need to search for a different
+ // target type.
+ //
+ target&
+ search (const target_type&, const prerequisite_key&);
+
+ // As above but specify the prerequisite to search as individual
+ // key components.
+ //
+ target&
+ search (const target_type& type,
+ const dir_path& dir,
+ const std::string& name,
+ const std::string* ext,
+ scope*);
+
+ // As above but specify the target type as template argument.
+ //
+ template <typename T>
+ T&
+ search (const dir_path& dir,
+ const std::string& name,
+ const std::string* ext,
+ scope*);
+
+ // Search for a target identified by the name. The semantics
+ // is "as if" we first created a prerequisite based on this
+ // name in exactly the same way as the parser would and then
+ // searched based on this prerequisite.
+ //
+ target&
+ search (name, scope&);
+
+ // Match and apply a rule to the action/target with ambiguity
+ // detection. Increment the target's dependents count, which
+ // means that you should call this function with the intent
+ // to also call execute(). In case of optimizations that would
+ // avoid calling execute(), call unmatch() to indicate this.
+ //
+ void
+ match (action, target&);
+
+ // Note that calling this function only makes sense if the
+ // target itself doesn't have its own dependents.
+ //
+ void
+ unmatch (action, target&);
+
+ // Match (but do not apply) a rule to the action/target with
+ // ambiguity detection. Note that this function does not touch
+ // the dependents count.
+ //
+ void
+ match_only (action, target&);
+
+ // Match a "delegate rule" from withing another rules' apply()
+ // function. Return recipe and recipe action (if any). Note
+ // that unlike match(), this call doesn't increment the
+ // dependents count. See also the companion execute_delegate().
+ //
+ std::pair<recipe, action>
+ match_delegate (action, target&);
+
+ // The standard prerequisite search and match implementations. They call
+ // search_and_match_*() versions below passing non-empty directory for
+ // the clean operation.
+ //
+ void
+ search_and_match_prerequisites (action, target&);
+
+ // If we are cleaning, this function doesn't go into group members,
+ // as an optimization (the group should clean everything up).
+ //
+ void
+ search_and_match_prerequisite_members (action, target&);
+
+ // The actual prerequisite search and match implementations. They call
+ // search() and then match() for each prerequisite in a loop. If this
+ // target is a member of a group, then they first do this to the group's
+ // prerequisites.
+ //
+ // If the directory argument is not empty, then they ignore (do not
+ // match) prerequisites that are not in the same or its subdirectory.
+ //
+ void
+ search_and_match_prerequisites (action, target&, const dir_path&);
+
+ void
+ search_and_match_prerequisite_members (action, target&, const dir_path&);
+
+ // Unless already available, match, and, if necessary, execute the group
+ // in order to obtain its members list. Note that even after that the
+ // member's list might still not be available (e.g., if some wildcard/
+ // fallback rule matched).
+ //
+ group_view
+ resolve_group_members (action, target&);
+
+ // Inject dependency on the parent directory's fsdir{}, unless it is
+ // the project's out_root (or is outside of any project; say, for
+ // example, an install directory). Normally this function is called
+ // from the rule's apply() function.
+ //
+ void
+ inject_parent_fsdir (action, target&);
+
+ // Execute the action on target, assuming a rule has been matched
+ // and the recipe for this action has been set. This is the default
+ // executor implementation. Decrements the dependents count.
+ //
+ target_state
+ execute (action, target&);
+
+ // Execute the recipe obtained with match_delegate(). Note that
+ // the target's state is neither checked nor updated by this
+ // function. In other words, the appropriate usage is to call
+ // this function from another recipe and to factor the obtained
+ // state into the one returned.
+ //
+ target_state
+ execute_delegate (const recipe&, action, target&);
+
+ // A special version of the above that should be used for "direct"
+ // and "now" execution, that is, side-stepping the normal target-
+ // prerequisite relationship (so no dependents count is decremented)
+ // and execution order (so this function will never return postponed
+ // target state).
+ //
+ target_state
+ execute_direct (action, target&);
+
+ // The default prerequisite execute implementation. It calls execute()
+ // on each non-ignored (non-NULL) prerequisite target in a loop. If this
+ // target is a member of a group, then it first does this to the group's
+ // prerequisites. Returns target_state::changed if any of them were
+ // changed and target_state::unchanged otherwise. Note that this
+ // function can be used as a recipe.
+ //
+ target_state
+ execute_prerequisites (action, target&);
+
+ // As above but iterates over the prerequisites in reverse.
+ //
+ target_state
+ reverse_execute_prerequisites (action, target&);
+
+ // A version of the above that also determines whether the action
+ // needs to be executed on the target based on the passed mtime
+ // timestamp.
+ //
+ // Note that because we use mtime, this function should normally
+ // only be used in the perform_update action.
+ //
+ bool
+ execute_prerequisites (action, target&, const timestamp&);
+
+ // Another version of the above that does two extra things for the
+ // caller: it determines whether the action needs to be executed on
+ // the target based on the passed timestamp and, if so, finds a
+ // prerequisite of the specified type (e.g., a source file). If
+ // there are multiple prerequisites of this type, then the last
+ // is returned.
+ //
+ template <typename T>
+ T*
+ execute_prerequisites (action, target&, const timestamp&);
+
+ // Return noop_recipe instead of using this function directly.
+ //
+ target_state
+ noop_action (action, target&);
+
+ // Default action implementation which forwards to the prerequisites.
+ // Use default_recipe instead of using this function directly.
+ //
+ target_state
+ default_action (action, target&);
+
+ // Standard perform(clean) action implementation for the file target
+ // or derived.
+ //
+ target_state
+ perform_clean (action, target&);
+}
+
+#include <build2/algorithm.ixx>
+#include <build2/algorithm.txx>
+
+#endif // BUILD2_ALGORITHM
diff --git a/build2/algorithm.cxx b/build2/algorithm.cxx
new file mode 100644
index 0000000..243519e
--- /dev/null
+++ b/build2/algorithm.cxx
@@ -0,0 +1,504 @@
+// file : build2/algorithm.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/algorithm>
+
+#include <memory> // unique_ptr
+#include <cstddef> // size_t
+#include <utility> // move
+#include <cassert>
+
+#include <butl/utility> // reverse_iterate
+
+#include <build2/scope>
+#include <build2/target>
+#include <build2/prerequisite>
+#include <build2/rule>
+#include <build2/file> // import()
+#include <build2/search>
+#include <build2/context>
+#include <build2/utility>
+#include <build2/diagnostics>
+
+using namespace std;
+using namespace butl;
+
+namespace build2
+{
+ target&
+ search (const prerequisite_key& pk)
+ {
+ // If this is a project-qualified prerequisite, then this
+ // is import's business.
+ //
+ if (pk.proj != nullptr)
+ return import (pk);
+
+ if (target* t = pk.tk.type->search (pk))
+ return *t;
+
+ return create_new_target (pk);
+ }
+
+ target&
+ search (name n, scope& s)
+ {
+ const string* e;
+ const target_type* tt (s.find_target_type (n, e));
+
+ if (tt == nullptr)
+ fail << "unknown target type " << n.type << " in name " << n;
+
+ n.dir.normalize ();
+
+ return search (*tt, move (n.dir), move (n.value), e, &s);
+ }
+
+ pair<const rule*, match_result>
+ match_impl (action a, target& t, bool apply)
+ {
+ pair<const rule*, match_result> r;
+
+ // By default, clear the resolved targets list before calling
+ // match(). The rule is free to modify this list in match()
+ // (provided that it matches) in order to, for example, prepare
+ // it for apply().
+ //
+ t.reset (a);
+
+ // If this is a nested operation, first try the outer operation.
+ // This allows a rule to implement a "precise match", that is,
+ // both inner and outer operations match.
+ //
+ for (operation_id oo (a.outer_operation ()), io (a.operation ()),
+ o (oo != 0 ? oo : io); o != 0; o = (oo != 0 ? io : 0))
+ {
+ // Adjust action for recipe: on the first iteration we want it
+ // {inner, outer} (which is the same as 'a') while on the second
+ // -- {inner, 0}. Note that {inner, 0} is the same or "stronger"
+ // (i.e., overrides; see action::operator<()) than 'a'. This
+ // allows "unconditional inner" to override "inner for outer"
+ // recipes.
+ //
+ action ra (a.meta_operation (), io, o != oo ? 0 : oo);
+
+ scope& bs (t.base_scope ());
+
+ for (auto tt (&t.type ()); tt != nullptr; tt = tt->base)
+ {
+ // Search scopes outwards, stopping at the project root.
+ //
+ for (const scope* s (&bs);
+ s != nullptr;
+ s = s->root () ? global_scope : s->parent_scope ())
+ {
+ const operation_rule_map* om (s->rules[a.meta_operation ()]);
+
+ if (om == nullptr)
+ continue; // No entry for this meta-operation id.
+
+ // First try the map for the actual operation. If that
+ // doesn't yeld anything, try the wildcard map.
+ //
+ for (size_t oi (o), oip (o); oip != 0; oip = oi, oi = 0)
+ {
+ const target_type_rule_map* ttm ((*om)[oi]);
+
+ if (ttm == nullptr)
+ continue; // No entry for this operation id.
+
+ if (ttm->empty ())
+ continue; // Empty map for this operation id.
+
+ auto i (ttm->find (tt));
+
+ if (i == ttm->end () || i->second.empty ())
+ continue; // No rules registered for this target type.
+
+ const auto& rules (i->second); // Hint map.
+
+ // @@ TODO
+ //
+ // Different rules can be used for different operations (update
+ // vs test is a good example). So, at some point, we will probably
+ // have to support a list of hints or even an operation-hint map
+ // (e.g., 'hint=cxx test=foo' if cxx supports the test operation
+ // but we want the foo rule instead). This is also the place where
+ // the '{build clean}=cxx' construct (which we currently do not
+ // support) can come handy.
+ //
+ // Also, ignore the hint (that is most likely ment for a different
+ // operation) if this is a unique match.
+ //
+ string hint;
+ auto rs (rules.size () == 1
+ ? make_pair (rules.begin (), rules.end ())
+ : rules.find_prefix (hint));
+
+ for (auto i (rs.first); i != rs.second; ++i)
+ {
+ const string& n (i->first);
+ const rule& ru (i->second);
+
+ match_result m;
+ {
+ auto g (
+ make_exception_guard (
+ [ra, &t, &n]()
+ {
+ info << "while matching rule " << n << " to "
+ << diag_do (ra, t);
+ }));
+
+ if (!(m = ru.match (ra, t, hint)))
+ continue;
+
+ if (!m.recipe_action.valid ())
+ m.recipe_action = ra; // Default, if not set.
+ }
+
+ // Do the ambiguity test.
+ //
+ bool ambig (false);
+
+ diag_record dr;
+
+ for (++i; i != rs.second; ++i)
+ {
+ const string& n1 (i->first);
+ const rule& ru1 (i->second);
+
+ {
+ auto g (
+ make_exception_guard (
+ [ra, &t, &n1]()
+ {
+ info << "while matching rule " << n1 << " to "
+ << diag_do (ra, t);
+ }));
+
+ if (!ru1.match (ra, t, hint))
+ continue;
+ }
+
+ if (!ambig)
+ {
+ dr << fail << "multiple rules matching "
+ << diag_doing (ra, t)
+ << info << "rule " << n << " matches";
+ ambig = true;
+ }
+
+ dr << info << "rule " << n1 << " also matches";
+ }
+
+ if (!ambig)
+ {
+ ra = m.recipe_action; // Use custom, if set.
+
+ if (apply)
+ {
+ auto g (
+ make_exception_guard (
+ [ra, &t, &n]()
+ {
+ info << "while applying rule " << n << " to "
+ << diag_do (ra, t);
+ }));
+
+ // @@ We could also allow the rule to change the recipe
+ // action in apply(). Could be useful with delegates.
+ //
+ t.recipe (ra, ru.apply (ra, t, m));
+ }
+ else
+ {
+ r.first = &ru;
+ r.second = move (m);
+ }
+
+ return r;
+ }
+ else
+ dr << info << "use rule hint to disambiguate this match";
+ }
+ }
+ }
+ }
+ }
+
+ diag_record dr;
+ dr << fail << "no rule to " << diag_do (a, t);
+
+ if (verb < 4)
+ dr << info << "re-run with --verbose 4 for more information";
+
+ return r;
+ }
+
+ group_view
+ resolve_group_members_impl (action a, target& g)
+ {
+ group_view r;
+
+ // Unless we already have a recipe, try matching the target to
+ // the rule.
+ //
+ if (!g.recipe (a))
+ {
+ auto rp (match_impl (a, g, false));
+
+ r = g.group_members (a);
+ if (r.members != nullptr)
+ return r;
+
+ // That didn't help, so apply the rule and go to the building
+ // phase.
+ //
+ const match_result& mr (rp.second);
+ g.recipe (mr.recipe_action, rp.first->apply (mr.recipe_action, g, mr));
+ }
+
+ // Note that we use execute_direct() rather than execute() here to
+ // sidestep the dependents count logic. In this context, this is by
+ // definition the first attempt to execute this rule (otherwise we
+ // would have already known the members list) and we really do need
+ // to execute it now.
+ //
+ execute_direct (a, g);
+
+ r = g.group_members (a);
+ return r; // Might still be unresolved.
+ }
+
+ void
+ search_and_match_prerequisites (action a, target& t, const dir_path& d)
+ {
+ const bool e (d.empty ());
+
+ for (prerequisite p: group_prerequisites (t))
+ {
+ target& pt (search (p));
+
+ if (e || pt.dir.sub (d))
+ {
+ match (a, pt);
+ t.prerequisite_targets.push_back (&pt);
+ }
+ }
+ }
+
+ void
+ search_and_match_prerequisite_members (action a,
+ target& t,
+ const dir_path& d)
+ {
+ const bool e (d.empty ());
+
+ for (prerequisite_member p: group_prerequisite_members (a, t))
+ {
+ target& pt (p.search ());
+
+ if (e || pt.dir.sub (d))
+ {
+ match (a, pt);
+ t.prerequisite_targets.push_back (&pt);
+ }
+ }
+ }
+
+ void
+ inject_parent_fsdir (action a, target& t)
+ {
+ tracer trace ("inject_parent_fsdir");
+
+ scope& s (t.base_scope ());
+ scope* rs (s.root_scope ());
+
+ if (rs == nullptr) // Could be outside any project.
+ return;
+
+ 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/.
+ //
+ const dir_path& d (t.name.empty () ? t.dir.directory () : t.dir);
+
+ if (!d.sub (out_root) || d == out_root)
+ return;
+
+ level6 ([&]{trace << "for " << t;});
+
+ fsdir& dt (search<fsdir> (d, string (), nullptr, &s));
+ match (a, dt);
+ t.prerequisite_targets.emplace_back (&dt);
+ }
+
+ target_state
+ execute_impl (action a, target& t)
+ {
+ // Implementation with some multi-threading ideas in mind.
+ //
+ switch (t.raw_state)
+ {
+ case target_state::group: // Means group's state is unknown.
+ case target_state::unknown:
+ case target_state::postponed:
+ {
+ auto g (
+ make_exception_guard (
+ [a, &t]()
+ {
+ t.raw_state = target_state::failed;
+ info << "while " << diag_doing (a, t);
+ }));
+
+ target_state ts (t.recipe (a) (a, t));
+ assert (ts != target_state::unknown && ts != target_state::failed);
+
+ // Set the target's state unless it should be the group's state.
+ //
+ if (t.raw_state != target_state::group)
+ t.raw_state = ts;
+
+ return ts;
+ }
+ case target_state::unchanged:
+ case target_state::changed:
+ // Should have been handled by inline execute().
+ assert (false);
+ case target_state::failed:
+ break;
+ }
+
+ throw failed ();
+ }
+
+ target_state
+ execute_prerequisites (action a, target& t)
+ {
+ target_state r (target_state::unchanged);
+
+ for (target* pt: t.prerequisite_targets)
+ {
+ if (pt == nullptr) // Skipped.
+ continue;
+
+ r |= execute (a, *pt);
+ }
+
+ return r;
+ }
+
+ target_state
+ reverse_execute_prerequisites (action a, target& t)
+ {
+ target_state r (target_state::unchanged);
+
+ for (target* pt: reverse_iterate (t.prerequisite_targets))
+ {
+ if (pt == nullptr) // Skipped.
+ continue;
+
+ r |= execute (a, *pt);
+ }
+
+ return r;
+ }
+
+ bool
+ execute_prerequisites (action a, target& t, const timestamp& mt)
+ {
+ bool e (mt == timestamp_nonexistent);
+
+ for (target* pt: t.prerequisite_targets)
+ {
+ if (pt == nullptr) // Skipped.
+ continue;
+
+ target_state ts (execute (a, *pt));
+
+ if (!e)
+ {
+ // If this is an mtime-based target, then compare timestamps.
+ //
+ if (auto mpt = dynamic_cast<const mtime_target*> (pt))
+ {
+ timestamp mp (mpt->mtime ());
+
+ // What do we do if timestamps are equal? This can happen, for
+ // example, on filesystems that don't have subsecond resolution.
+ // There is not much we can do here except detect the case where
+ // the prerequisite was changed in this run which means the
+ // action must be executed on the target as well.
+ //
+ if (mt < mp || (mt == mp && ts == target_state::changed))
+ e = true;
+ }
+ else
+ {
+ // Otherwise we assume the prerequisite is newer if it was changed.
+ //
+ if (ts == target_state::changed)
+ e = true;
+ }
+ }
+ }
+
+ return e;
+ }
+
+ target_state
+ noop_action (action, target&)
+ {
+ assert (false); // We shouldn't be called, see target::recipe().
+ return target_state::unchanged;
+ }
+
+ target_state
+ group_action (action a, target& t)
+ {
+ target_state r (execute (a, *t.group));
+
+ // Indicate to the standard execute() logic that this target's
+ // state comes from the group.
+ //
+ t.raw_state = target_state::group;
+
+ return r;
+ }
+
+ target_state
+ default_action (action a, target& t)
+ {
+ return current_mode == execution_mode::first
+ ? execute_prerequisites (a, t)
+ : reverse_execute_prerequisites (a, t);
+ }
+
+ target_state
+ perform_clean (action a, target& t)
+ {
+ // The reverse order of update: first delete the file, then clean
+ // prerequisites.
+ //
+ file& ft (dynamic_cast<file&> (t));
+
+ target_state r (rmfile (ft.path (), ft)
+ ? target_state::changed
+ : target_state::unchanged);
+
+ // Update timestamp in case there are operations after us that
+ // could use the information.
+ //
+ ft.mtime (timestamp_nonexistent);
+
+ // Clean prerequisites.
+ //
+ r |= reverse_execute_prerequisites (a, t);
+
+ return r;
+ }
+}
diff --git a/build2/algorithm.ixx b/build2/algorithm.ixx
new file mode 100644
index 0000000..92cc66d
--- /dev/null
+++ b/build2/algorithm.ixx
@@ -0,0 +1,197 @@
+// file : build2/algorithm.ixx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <utility> // pair
+
+#include <build2/rule>
+#include <build2/prerequisite>
+#include <build2/context>
+
+namespace build2
+{
+ inline target&
+ search (prerequisite& p)
+ {
+ if (p.target == nullptr)
+ p.target = &search (p.key ());
+
+ return *p.target;
+ }
+
+ inline target&
+ search (const target_type& t, const prerequisite_key& k)
+ {
+ return search (
+ prerequisite_key
+ {k.proj, {&t, k.tk.dir, k.tk.name, k.tk.ext}, k.scope});
+ }
+
+ inline target&
+ search (const target_type& type,
+ const dir_path& dir,
+ const std::string& name,
+ const std::string* ext,
+ scope* scope)
+ {
+ return search (
+ prerequisite_key {nullptr, {&type, &dir, &name, &ext}, scope});
+ }
+
+ template <typename T>
+ inline T&
+ search (const dir_path& dir,
+ const std::string& name,
+ const std::string* ext,
+ scope* scope)
+ {
+ return static_cast<T&> (search (T::static_type, dir, name, ext, scope));
+ }
+
+ std::pair<const rule*, match_result>
+ match_impl (action, target&, bool apply);
+
+ inline void
+ match (action a, target& t)
+ {
+ if (!t.recipe (a))
+ match_impl (a, t, true);
+
+ t.dependents++;
+ dependency_count++;
+
+ // text << "M " << t << ": " << t.dependents << " " << dependency_count;
+ }
+
+ inline void
+ unmatch (action, target& t)
+ {
+ // text << "U " << t << ": " << t.dependents << " " << dependency_count;
+
+ assert (t.dependents != 0 && dependency_count != 0);
+ t.dependents--;
+ dependency_count--;
+ }
+
+ inline void
+ match_only (action a, target& t)
+ {
+ if (!t.recipe (a))
+ match_impl (a, t, false);
+ }
+
+ inline std::pair<recipe, action>
+ match_delegate (action a, target& t)
+ {
+ auto rp (match_impl (a, t, false));
+ const match_result& mr (rp.second);
+ return std::make_pair (rp.first->apply (mr.recipe_action, t, mr),
+ mr.recipe_action);
+ }
+
+ group_view
+ resolve_group_members_impl (action, target&);
+
+ inline group_view
+ resolve_group_members (action a, target& g)
+ {
+ group_view r (g.group_members (a));
+ return r.members != nullptr ? r : resolve_group_members_impl (a, g);
+ }
+
+ inline void
+ search_and_match_prerequisites (action a, target& t)
+ {
+ search_and_match_prerequisites (
+ a,
+ t,
+ a.operation () != clean_id
+ ? dir_path ()
+ : t.strong_scope ().out_path ());
+ }
+
+ inline void
+ search_and_match_prerequisite_members (action a, target& t)
+ {
+ if (a.operation () != clean_id)
+ search_and_match_prerequisite_members (a, t, dir_path ());
+ else
+ // Note that here we don't iterate over members even for see-
+ // through groups since the group target should clean eveything
+ // up. A bit of an optimization.
+ //
+ search_and_match_prerequisites (a, t, t.strong_scope ().out_path ());
+ }
+
+ target_state
+ execute_impl (action, target&);
+
+ inline target_state
+ execute (action a, target& t)
+ {
+ // text << "E " << t << ": " << t.dependents << " " << dependency_count;
+
+ if (dependency_count != 0) // Re-examination of a postponed target?
+ {
+ assert (t.dependents != 0);
+ t.dependents--;
+ dependency_count--;
+ }
+
+ switch (target_state ts = t.state ())
+ {
+ case target_state::unchanged:
+ case target_state::changed:
+ return ts;
+ default:
+ {
+ // Handle the "last" execution mode.
+ //
+ // This gets interesting when we consider interaction with
+ // groups. It seem to make sense to treat group members as
+ // dependents of the group, so, for example, if we try to
+ // clean the group via three of its members, only the last
+ // attempt will actually execute the clean. This means that
+ // when we match a group member, inside we should also match
+ // the group in order to increment the dependents count. This
+ // seems to be a natural requirement: if we are delegating to
+ // the group, we need to find a recipe for it, just like we
+ // would for a prerequisite.
+ //
+ // Note that below we are going to change the group state
+ // to postponed. This is not a mistake: until we execute
+ // the recipe, we want to keep returning postponed. And
+ // once the recipe is executed, it will reset the state
+ // to group (see group_action()). To put it another way,
+ // the execution of this member is postponed, not of the
+ // group.
+ //
+ // One important invariant to keep in mind: the return
+ // value from execute() should always be the same as what
+ // would get returned by a subsequent call to state().
+ //
+ if (current_mode == execution_mode::last && t.dependents != 0)
+ return (t.raw_state = target_state::postponed);
+
+ return execute_impl (a, t);
+ }
+ }
+ }
+
+ inline target_state
+ execute_delegate (const recipe& r, action a, target& t)
+ {
+ return r (a, t);
+ }
+
+ inline target_state
+ execute_direct (action a, target& t)
+ {
+ switch (target_state ts = t.state ())
+ {
+ case target_state::unchanged:
+ case target_state::changed: return ts;
+ default: return execute_impl (a, t);
+ }
+ }
+}
diff --git a/build2/algorithm.txx b/build2/algorithm.txx
new file mode 100644
index 0000000..7b0e498
--- /dev/null
+++ b/build2/algorithm.txx
@@ -0,0 +1,58 @@
+// file : build2/algorithm.txx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+namespace build2
+{
+ template <typename T>
+ T*
+ execute_prerequisites (action a, target& t, const timestamp& mt)
+ {
+ //@@ Can factor the bulk of it into a non-template code. Can
+ // either do a function template that will do dynamic_cast check
+ // or can scan the target type info myself. I think latter.
+ //
+ bool e (mt == timestamp_nonexistent);
+ T* r (nullptr);
+
+ for (target* pt: t.prerequisite_targets)
+ {
+ if (pt == nullptr) // Skip ignored.
+ continue;
+
+ target_state ts (execute (a, *pt));
+
+ if (!e)
+ {
+ // If this is an mtime-based target, then compare timestamps.
+ //
+ if (auto mpt = dynamic_cast<const mtime_target*> (pt))
+ {
+ timestamp mp (mpt->mtime ());
+
+ // What do we do if timestamps are equal? This can happen, for
+ // example, on filesystems that don't have subsecond resolution.
+ // There is not much we can do here except detect the case where
+ // the prerequisite was changed in this run which means the
+ // action must be executed on the target as well.
+ //
+ if (mt < mp || (mt == mp && ts == target_state::changed))
+ e = true;
+ }
+ else
+ {
+ // Otherwise we assume the prerequisite is newer if it was changed.
+ //
+ if (ts == target_state::changed)
+ e = true;
+ }
+ }
+
+ if (T* tmp = dynamic_cast<T*> (pt))
+ r = tmp;
+ }
+
+ assert (r != nullptr);
+ return e ? r : nullptr;
+ }
+}
diff --git a/build2/b.cxx b/build2/b.cxx
new file mode 100644
index 0000000..42c59f7
--- /dev/null
+++ b/build2/b.cxx
@@ -0,0 +1,855 @@
+// file : build2/b.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <time.h> // tzset()
+#include <string.h> // strerror()
+
+#include <stdlib.h> // getenv()
+#include <unistd.h> // getuid()
+#include <sys/types.h> // uid_t
+#include <pwd.h> // struct passwd, getpwuid()
+
+#include <sstream>
+#include <cassert>
+#include <typeinfo>
+#include <iostream>
+#include <system_error>
+
+#include <butl/filesystem>
+
+#include <build2/version>
+
+#include <build2/types>
+#include <build2/spec>
+#include <build2/operation>
+#include <build2/scope>
+#include <build2/target>
+#include <build2/prerequisite>
+#include <build2/rule>
+#include <build2/file>
+#include <build2/module>
+#include <build2/algorithm>
+#include <build2/diagnostics>
+#include <build2/context>
+#include <build2/utility>
+#include <build2/variable>
+
+#include <build2/token>
+#include <build2/lexer>
+#include <build2/parser>
+
+#include <build2/options>
+
+using namespace std;
+
+#include <build2/config/module>
+#include <build2/dist/module>
+#include <build2/bin/module>
+#include <build2/cxx/module>
+#include <build2/cli/module>
+#include <build2/test/module>
+#include <build2/install/module>
+
+using namespace build2;
+
+int
+main (int argc, char* argv[])
+{
+ try
+ {
+ tracer trace ("main");
+
+ cl::argv_scanner scan (argc, argv, true);
+ options ops (scan);
+
+ // Version.
+ //
+ if (ops.version ())
+ {
+ cout << "build2 " << BUILD2_VERSION_STR<< endl
+ << "libbutl " << LIBBUTL_VERSION_STR << endl
+ << "Copyright (c) 2014-2015 Code Synthesis Ltd" << endl
+ << "This is free software released under the MIT license." << endl;
+ return 0;
+ }
+
+ // Help.
+ //
+ if (ops.help ())
+ {
+ ostream& o (cout);
+
+ o << "Usage: " << argv[0] << " [options] [variables] [buildspec]" << endl
+ << "Options:" << endl;
+
+ options::print_usage (o);
+ return 0;
+ }
+
+ // Diagnostics verbosity.
+ //
+ verb = ops.verbose_specified ()
+ ? ops.verbose ()
+ : ops.v () ? 2 : ops.q () ? 0 : 1;
+
+ // Initialize time conversion data that is used by localtime_r().
+ //
+ tzset ();
+
+ // Register builtin modules.
+ //
+ builtin_modules["config"] = module_functions {&config::config_boot,
+ &config::config_init};
+ builtin_modules["dist"] = module_functions {&dist::dist_boot,
+ &dist::dist_init};
+ builtin_modules["test"] = module_functions {&test::test_boot,
+ &test::test_init};
+ builtin_modules["install"] = module_functions {&install::install_boot,
+ &install::install_init};
+
+ builtin_modules["bin"] = module_functions {nullptr, &bin::bin_init};
+ builtin_modules["cxx"] = module_functions {nullptr, &cxx::cxx_init};
+ builtin_modules["cli"] = module_functions {nullptr, &cli::cli_init};
+
+ // Figure out work and home directories.
+ //
+ work = dir_path::current ();
+
+ if (const char* h = getenv ("HOME"))
+ home = dir_path (h);
+ else
+ {
+ struct passwd* pw (getpwuid (getuid ()));
+
+ if (pw == nullptr)
+ {
+ const char* msg (strerror (errno));
+ fail << "unable to determine home directory: " << msg;
+ }
+
+ home = dir_path (pw->pw_dir);
+ }
+
+ if (verb >= 5)
+ {
+ trace << "work dir: " << work;
+ trace << "home dir: " << home;
+ }
+
+ // Initialize the dependency state.
+ //
+ reset ();
+
+ // Parse command line variables. They should come before the
+ // buildspec.
+ //
+ int argi (1);
+ for (; argi != argc; argi++)
+ {
+ const char* s (argv[argi]);
+
+ istringstream is (s);
+ is.exceptions (istringstream::failbit | istringstream::badbit);
+ lexer l (is, "<cmdline>");
+ token t (l.next ());
+
+ if (t.type == token_type::eos)
+ continue; // Whitespace-only argument.
+
+ // Unless this is a name followed by = or +=, assume it is
+ // a start of the buildspec.
+ //
+ if (t.type != token_type::name)
+ break;
+
+ token_type tt (l.next ().type);
+
+ if (tt != token_type::equal &&
+ tt != token_type::equal_plus &&
+ tt != token_type::plus_equal)
+ break;
+
+ parser p;
+ t = p.parse_variable (l, *global_scope, t.value, tt);
+
+ if (t.type != token_type::eos)
+ fail << "unexpected " << t << " in variable " << s;
+ }
+
+ // Parse the buildspec.
+ //
+ buildspec bspec;
+ {
+ // Merge all the individual buildspec arguments into a single
+ // string. Instead, we could also parse them individually (
+ // and merge the result). The benefit of doing it this way
+ // is potentially better diagnostics (i.e., we could have
+ // used <buildspec-1>, <buildspec-2> to give the idea about
+ // which argument is invalid).
+ //
+ string s;
+ for (; argi != argc;)
+ {
+ s += argv[argi];
+ if (++argi != argc)
+ s += ' ';
+ }
+
+ try
+ {
+ istringstream is (s);
+ is.exceptions (istringstream::failbit | istringstream::badbit);
+
+ parser p;
+ bspec = p.parse_buildspec (is, "<buildspec>");
+ }
+ catch (const istringstream::failure&)
+ {
+ fail << "unable to parse buildspec '" << s << "'";
+ }
+ }
+
+ level5 ([&]{trace << "buildspec: " << bspec;});
+
+ if (bspec.empty ())
+ bspec.push_back (metaopspec ()); // Default meta-operation.
+
+ for (metaopspec& ms: bspec)
+ {
+ if (ms.empty ())
+ ms.push_back (opspec ()); // Default operation.
+
+ meta_operation_id mid (0); // Not yet translated.
+ const meta_operation_info* mif (nullptr);
+
+ bool lifted (false); // See below.
+
+ for (opspec& os: ms)
+ {
+ const location l ("<buildspec>", 1, 0); //@@ TODO
+
+ if (os.empty ()) // Default target: dir{}.
+ os.push_back (targetspec (name ("dir", string ())));
+
+ operation_id oid (0); // Not yet translated.
+ const operation_info* oif (nullptr);
+
+ operation_id pre_oid (0);
+ const operation_info* pre_oif (nullptr);
+
+ operation_id post_oid (0);
+ const operation_info* post_oif (nullptr);
+
+ // We do meta-operation and operation batches sequentially (no
+ // parallelism). But multiple targets in an operation batch
+ // can be done in parallel.
+ //
+ action_targets tgs;
+ tgs.reserve (os.size ());
+
+ // If the previous operation was lifted to meta-operation,
+ // end the meta-operation batch.
+ //
+ if (lifted)
+ {
+ if (mif->meta_operation_post != nullptr)
+ mif->meta_operation_post ();
+
+ level5 ([&]{trace << "end meta-operation batch " << mif->name
+ << ", id " << static_cast<uint16_t> (mid);});
+
+ mid = 0;
+ lifted = false;
+ }
+
+ for (targetspec& ts: os)
+ {
+ name& tn (ts.name);
+
+ // First figure out the out_base of this target. The logic
+ // is as follows: if a directory was specified in any form,
+ // then that's the out_base. Otherwise, we check if the name
+ // value has a directory prefix. This has a good balance of
+ // control and the expected result in most cases.
+ //
+ dir_path out_base (tn.dir);
+ if (out_base.empty ())
+ {
+ const string& v (tn.value);
+
+ // Handle a few common cases as special: empty name, '.',
+ // '..', as well as dir{foo/bar} (without trailing '/').
+ // This code must be consistent with find_target_type().
+ //
+ if (v.empty () || v == "." || v == ".." || tn.type == "dir")
+ out_base = dir_path (v);
+ //
+ // Otherwise, if this is a simple name, see if there is a
+ // directory part in value.
+ //
+ else if (tn.untyped ())
+ {
+ // We cannot assume it is a valid filesystem name so we
+ // will have to do the splitting manually.
+ //
+ path::size_type i (path::traits::rfind_separator (v));
+
+ if (i != string::npos)
+ out_base = dir_path (v, i != 0 ? i : 1); // Special case: "/".
+ }
+ }
+
+ if (out_base.relative ())
+ out_base = work / out_base;
+
+ out_base.normalize ();
+
+ // The order in which we determine the roots depends on whether
+ // src_base was specified explicitly. There will also be a few
+ // cases where we are guessing things that can turn out wrong.
+ // Keep track of that so that we can issue more extensive
+ // diagnostics for such cases.
+ //
+ bool guessing (false);
+ dir_path src_root;
+ dir_path out_root;
+
+ dir_path& src_base (ts.src_base); // Update it in buildspec.
+
+ if (!src_base.empty ())
+ {
+ // Make sure it exists. While we will fail further down
+ // if it doesn't, the diagnostics could be confusing (e.g.,
+ // unknown operation because we don't load bootstrap.build).
+ //
+ if (!dir_exists (src_base))
+ fail << "src_base directory " << src_base << " does not exist";
+
+ if (src_base.relative ())
+ src_base = work / src_base;
+
+ src_base.normalize ();
+
+ // If the src_base was explicitly specified, search for src_root.
+ //
+ src_root = find_src_root (src_base);
+
+ // If not found, assume this is a simple project with src_root
+ // being the same as src_base.
+ //
+ if (src_root.empty ())
+ {
+ src_root = src_base;
+ out_root = out_base;
+ }
+ else
+ // Calculate out_root based on src_root/src_base.
+ //
+ try
+ {
+ out_root = out_base.directory (src_base.leaf (src_root));
+ }
+ catch (const invalid_path&)
+ {
+ fail << "out_base suffix does not match src_root" <<
+ info << "src_root: " << src_root <<
+ info << "out_base: " << out_base;
+ }
+ }
+ else
+ {
+ // If no src_base was explicitly specified, search for out_root.
+ //
+ bool src;
+ out_root = find_out_root (out_base, &src);
+
+ // If not found (i.e., we have no idea where the roots are),
+ // then this can mean two things: an in-tree build of a
+ // simple project or a fresh out-of-tree build. To test for
+ // the latter, try to find src_root starting from work. If
+ // we can't, then assume it is the former case.
+ //
+ if (out_root.empty ())
+ {
+ src_root = find_src_root (work);
+
+ if (!src_root.empty ())
+ {
+ src_base = work;
+
+ if (src_root != src_base)
+ {
+ try
+ {
+ out_root = out_base.directory (src_base.leaf (src_root));
+ }
+ catch (const invalid_path&)
+ {
+ fail << "out_base directory suffix does not match src_base"
+ << info << "src_base is " << src_base
+ << info << "src_root is " << src_root
+ << info << "out_base is " << out_base
+ << info << "consider explicitly specifying src_base "
+ << "for " << tn;
+ }
+ }
+ else
+ out_root = out_base;
+ }
+ else
+ src_root = src_base = out_root = out_base;
+
+ guessing = true;
+ }
+ else if (src)
+ src_root = out_root;
+ }
+
+ // Now we know out_root and, if it was explicitly specified
+ // or the same as out_root, src_root. The next step is to
+ // create the root scope and load the out_root bootstrap
+ // files, if any. Note that we might already have done this
+ // 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 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));
+
+ bootstrap_out (rs);
+
+ // See if the bootstrap process set/changed 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 (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 ())
+ {
+ // If it also wasn't explicitly specified, see if it is
+ // the same as out_root.
+ //
+ 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;
+ }
+ }
+
+ 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.
+ //
+ if (src_base.empty ())
+ src_base = src_root / out_base.leaf (out_root);
+
+ // Now that we have src_root, load the src_root bootstrap file,
+ // if there is one.
+ //
+ bool bootstrapped (bootstrap_src (rs));
+
+ // Check that out_root that we have found is the innermost root
+ // for this project. If it is not, then it means we are trying
+ // to load a disfigured sub-project and that we do not support.
+ // Why don't we support it? Because things are already complex
+ // enough here.
+ //
+ if (auto l = rs.vars["subprojects"])
+ {
+ for (const name& n: *l)
+ {
+ if (n.pair != '\0')
+ continue; // Skip project names.
+
+ if (out_base.sub (out_root / n.dir))
+ fail << tn << " is in a subproject of " << out_root <<
+ info << "explicitly specify src_base for this target";
+ }
+ }
+
+ // Create and bootstrap outer roots if any. Loading is done
+ // by load_root_pre() (that would normally be called by the
+ // meta-operation's load() callback below).
+ //
+ create_bootstrap_outer (rs);
+
+ // The src bootstrap should have loaded all the modules that
+ // may add new meta/operations. So at this stage they should
+ // all be known. We store the combined action id in uint8_t;
+ // see <operation> for details.
+ //
+ assert (operation_table.size () <= 128);
+ assert (meta_operation_table.size () <= 128);
+
+ // Since we now know all the names of meta-operations and
+ // operations, "lift" names that we assumed (from buildspec
+ // syntax) were operations but are actually meta-operations.
+ // Also convert empty names (which means they weren't explicitly
+ // specified) to the defaults and verify that all the names are
+ // known.
+ //
+ {
+ const auto& mn (ms.name);
+ const auto& on (os.name);
+
+ meta_operation_id m (0);
+ operation_id o (0);
+
+ if (!on.empty ())
+ {
+ m = meta_operation_table.find (on);
+
+ if (m != 0)
+ {
+ if (!mn.empty ())
+ fail (l) << "nested meta-operation " << mn
+ << '(' << on << ')';
+
+ if (!lifted) // If this is the first target.
+ {
+ // End the previous meta-operation batch if there was one
+ // and start a new one.
+ //
+ if (mid != 0)
+ {
+ assert (oid == 0);
+
+ if (mif->meta_operation_post != nullptr)
+ mif->meta_operation_post ();
+
+ level5 ([&]{trace << "end meta-operation batch "
+ << mif->name << ", id "
+ << static_cast<uint16_t> (mid);});
+
+ mid = 0;
+ }
+
+ lifted = true; // Flag to also end it; see above.
+ }
+ }
+ else
+ {
+ o = operation_table.find (on);
+
+ if (o == 0)
+ {
+ diag_record dr;
+ dr << fail (l) << "unknown operation " << on;
+
+ // If we guessed src_root and didn't load anything during
+ // bootstrap, then this is probably a meta-operation that
+ // would have been added by the module if src_root was
+ // correct.
+ //
+ if (guessing && !bootstrapped)
+ dr << info << "consider explicitly specifying src_base "
+ << "for " << tn;
+ }
+ }
+ }
+
+ if (!mn.empty ())
+ {
+ m = meta_operation_table.find (mn);
+
+ if (m == 0)
+ {
+ diag_record dr;
+ dr << fail (l) << "unknown meta-operation " << mn;
+
+ // Same idea as for the operation case above.
+ //
+ if (guessing && !bootstrapped)
+ dr << info << "consider explicitly specifying src_base "
+ << "for " << tn;
+ }
+ }
+
+ // The default meta-operation is perform. The default
+ // operation is assigned by the meta-operation below.
+ //
+ if (m == 0)
+ m = perform_id;
+
+ // If this is the first target in the meta-operation batch,
+ // then set the batch meta-operation id.
+ //
+ if (mid == 0)
+ {
+ mid = m;
+ mif = rs.meta_operations[m];
+
+ if (mif == nullptr)
+ fail (l) << "target " << tn << " does not support meta-"
+ << "operation " << meta_operation_table[m];
+
+ level5 ([&]{trace << "start meta-operation batch " << mif->name
+ << ", id " << static_cast<uint16_t> (mid);});
+
+ if (mif->meta_operation_pre != nullptr)
+ mif->meta_operation_pre ();
+
+ current_mif = mif;
+ }
+ //
+ // Otherwise, check that all the targets in a meta-operation
+ // batch have the same meta-operation implementation.
+ //
+ else
+ {
+ const meta_operation_info* mi (rs.meta_operations[mid]);
+
+ if (mi == nullptr)
+ fail (l) << "target " << tn << " does not support meta-"
+ << "operation " << meta_operation_table[mid];
+
+ if (mi != mif)
+ fail (l) << "different implementations of meta-operation "
+ << mif->name << " in the same meta-operation batch";
+ }
+
+ // If this is the first target in the operation batch, then set
+ // the batch operation id.
+ //
+ if (oid == 0)
+ {
+ auto lookup =
+ [&rs, &l, &tn] (operation_id o) -> const operation_info*
+ {
+ const operation_info* r (rs.operations[o]);
+
+ if (r == nullptr)
+ fail (l) << "target " << tn << " does not support "
+ << "operation " << operation_table[o];
+ return r;
+ };
+
+ if (o == 0)
+ o = default_id;
+
+ oif = lookup (o);
+
+ level5 ([&]{trace << "start operation batch " << oif->name
+ << ", id " << static_cast<uint16_t> (o);});
+
+ // Allow the meta-operation to translate the operation.
+ //
+ if (mif->operation_pre != nullptr)
+ oid = mif->operation_pre (o);
+ else // Otherwise translate default to update.
+ oid = (o == default_id ? update_id : o);
+
+ if (o != oid)
+ {
+ oif = lookup (oid);
+ level5 ([&]{trace << "operation translated to " << oif->name
+ << ", id " << static_cast<uint16_t> (oid);});
+ }
+
+ // Handle pre/post operations.
+ //
+ if (oif->pre != nullptr && (pre_oid = oif->pre (mid)) != 0)
+ {
+ assert (pre_oid != default_id);
+ pre_oif = lookup (pre_oid);
+ }
+
+ if (oif->post != nullptr && (post_oid = oif->post (mid)) != 0)
+ {
+ assert (post_oid != default_id);
+ post_oif = lookup (post_oid);
+ }
+ }
+ //
+ // Similar to meta-operations, check that all the targets in
+ // an operation batch have the same operation implementation.
+ //
+ else
+ {
+ auto check =
+ [&rs, &l, &tn] (operation_id o, const operation_info* i)
+ {
+ const operation_info* r (rs.operations[o]);
+
+ if (r == nullptr)
+ fail (l) << "target " << tn << " does not support "
+ << "operation " << operation_table[o];
+
+ if (r != i)
+ fail (l) << "different implementations of operation "
+ << i->name << " in the same operation batch";
+ };
+
+ check (oid, oif);
+
+ if (pre_oid != 0)
+ check (pre_oid, pre_oif);
+
+ if (post_oid != 0)
+ check (post_oid, post_oif);
+ }
+ }
+
+ if (verb >= 5)
+ {
+ trace << "target " << tn << ':';
+ trace << " out_base: " << out_base;
+ trace << " src_base: " << src_base;
+ trace << " out_root: " << out_root;
+ trace << " src_root: " << src_root;
+ }
+
+ path bf (src_base / path ("buildfile"));
+
+ // If we were guessing src_base, check that the buildfile
+ // exists and if not, issue more detailed diagnostics.
+ //
+ if (guessing && !file_exists (bf))
+ fail << bf << " does not exist"
+ << info << "consider explicitly specifying src_base "
+ << "for " << tn;
+
+ // Load the buildfile.
+ //
+ mif->load (bf, rs, out_base, src_base, l);
+
+ // Next search and match the targets. We don't want to start
+ // building before we know how to for all the targets in this
+ // operation batch.
+ //
+ {
+ scope& bs (scopes.find (out_base));
+
+ const string* e;
+ const target_type* ti (bs.find_target_type (tn, e));
+
+ if (ti == nullptr)
+ fail (l) << "unknown target type " << tn.type;
+
+ // If the directory is relative, assume it is relative to work
+ // (must be consistent with how we derived out_base above).
+ //
+ dir_path& d (tn.dir);
+
+ if (d.relative ())
+ d = work / d;
+
+ d.normalize ();
+
+ mif->search (rs, target_key {ti, &d, &tn.value, &e}, l, tgs);
+ }
+ }
+
+ if (pre_oid != 0)
+ {
+ level5 ([&]{trace << "start pre-operation batch " << pre_oif->name
+ << ", id " << static_cast<uint16_t> (pre_oid);});
+
+ if (mif->operation_pre != nullptr)
+ mif->operation_pre (pre_oid); // Cannot be translated.
+
+ current_inner_oif = pre_oif;
+ current_outer_oif = oif;
+ current_mode = pre_oif->mode;
+ dependency_count = 0;
+
+ action a (mid, pre_oid, oid);
+
+ mif->match (a, tgs);
+ mif->execute (a, tgs, true); // Run quiet.
+
+ if (mif->operation_post != nullptr)
+ mif->operation_post (pre_oid);
+
+ level5 ([&]{trace << "end pre-operation batch " << pre_oif->name
+ << ", id " << static_cast<uint16_t> (pre_oid);});
+ }
+
+ current_inner_oif = oif;
+ current_outer_oif = nullptr;
+ current_mode = oif->mode;
+ dependency_count = 0;
+
+ action a (mid, oid, 0);
+
+ mif->match (a, tgs);
+ mif->execute (a, tgs, verb == 0);
+
+ if (post_oid != 0)
+ {
+ level5 ([&]{trace << "start post-operation batch " << post_oif->name
+ << ", id " << static_cast<uint16_t> (post_oid);});
+
+ if (mif->operation_pre != nullptr)
+ mif->operation_pre (post_oid); // Cannot be translated.
+
+ current_inner_oif = post_oif;
+ current_outer_oif = oif;
+ current_mode = post_oif->mode;
+ dependency_count = 0;
+
+ action a (mid, post_oid, oid);
+
+ mif->match (a, tgs);
+ mif->execute (a, tgs, true); // Run quiet.
+
+ if (mif->operation_post != nullptr)
+ mif->operation_post (post_oid);
+
+ level5 ([&]{trace << "end post-operation batch " << post_oif->name
+ << ", id " << static_cast<uint16_t> (post_oid);});
+ }
+
+ if (mif->operation_post != nullptr)
+ mif->operation_post (oid);
+
+ level5 ([&]{trace << "end operation batch " << oif->name
+ << ", id " << static_cast<uint16_t> (oid);});
+ }
+
+ if (mif->meta_operation_post != nullptr)
+ mif->meta_operation_post ();
+
+ level5 ([&]{trace << "end meta-operation batch " << mif->name
+ << ", id " << static_cast<uint16_t> (mid);});
+ }
+ }
+ catch (const failed&)
+ {
+ return 1; // Diagnostics has already been issued.
+ }
+ /*
+ catch (const std::exception& e)
+ {
+ error << e.what ();
+ return 1;
+ }
+ */
+}
diff --git a/build2/bin/module b/build2/bin/module
new file mode 100644
index 0000000..85be444
--- /dev/null
+++ b/build2/bin/module
@@ -0,0 +1,23 @@
+// file : build2/bin/module -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_BIN_MODULE
+#define BUILD2_BIN_MODULE
+
+#include <build2/types>
+#include <build2/utility>
+
+#include <build2/module>
+
+namespace build2
+{
+ namespace bin
+ {
+ extern "C" bool
+ bin_init (
+ scope&, scope&, const location&, unique_ptr<module>&, bool, bool);
+ }
+}
+
+#endif // BUILD2_BIN_MODULE
diff --git a/build2/bin/module.cxx b/build2/bin/module.cxx
new file mode 100644
index 0000000..624a8d3
--- /dev/null
+++ b/build2/bin/module.cxx
@@ -0,0 +1,188 @@
+// file : build2/bin/module.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/bin/module>
+
+#include <build2/scope>
+#include <build2/variable>
+#include <build2/diagnostics>
+
+#include <build2/config/utility>
+#include <build2/install/utility>
+
+#include <build2/bin/rule>
+#include <build2/bin/target>
+
+using namespace std;
+
+namespace build2
+{
+ namespace bin
+ {
+ static obj_rule obj_;
+ static lib_rule lib_;
+
+ // Default config.bin.*.lib values.
+ //
+ static const strings exe_lib {"shared", "static"};
+ static const strings liba_lib {"static"};
+ static const strings libso_lib {"shared"};
+
+ extern "C" bool
+ bin_init (scope& r,
+ scope& b,
+ const location&,
+ std::unique_ptr<module>&,
+ bool first,
+ bool)
+ {
+ tracer trace ("bin::init");
+ level5 ([&]{trace << "for " << b.out_path ();});
+
+ // Enter module variables.
+ //
+ if (first)
+ {
+ auto& v (var_pool);
+
+ v.find ("config.bin.lib", string_type);
+ v.find ("config.bin.exe.lib", strings_type);
+ v.find ("config.bin.liba.lib", strings_type);
+ v.find ("config.bin.libso.lib", strings_type);
+ v.find ("config.bin.rpath", strings_type); //@@ VAR paths_type
+
+ v.find ("bin.lib", string_type);
+ v.find ("bin.exe.lib", strings_type);
+ v.find ("bin.liba.lib", strings_type);
+ v.find ("bin.libso.lib", strings_type);
+ v.find ("bin.rpath", strings_type); //@@ VAR paths_type
+
+ v.find ("bin.libprefix", string_type);
+ }
+
+ // Register target types.
+ //
+ {
+ auto& t (b.target_types);
+
+ t.insert<obja> ();
+ t.insert<objso> ();
+ t.insert<obj> ();
+ t.insert<exe> ();
+ t.insert<liba> ();
+ t.insert<libso> ();
+ t.insert<lib> ();
+ }
+
+ // Register rules.
+ //
+ {
+ auto& r (b.rules);
+
+ r.insert<obj> (perform_update_id, "bin.obj", obj_);
+ r.insert<obj> (perform_clean_id, "bin.obj", obj_);
+
+ r.insert<lib> (perform_update_id, "bin.lib", lib_);
+ r.insert<lib> (perform_clean_id, "bin.lib", lib_);
+
+ // Configure member.
+ //
+ r.insert<lib> (configure_update_id, "bin.lib", lib_);
+
+ //@@ Should we check if the install module was loaded
+ // (by checking if install operation is registered
+ // for this project)? If we do that, then install
+ // will have to be loaded before bin. Perhaps we
+ // should enforce loading of all operation-defining
+ // modules before all others?
+ //
+ r.insert<lib> (perform_install_id, "bin.lib", lib_);
+ }
+
+ // Configure.
+ //
+ using config::required;
+
+ // The idea here is as follows: if we already have one of
+ // the bin.* variables set, then we assume this is static
+ // project configuration and don't bother setting the
+ // corresponding config.bin.* variable.
+ //
+ //@@ Need to validate the values. Would be more efficient
+ // to do it once on assignment than every time on query.
+ // Custom var type?
+ //
+
+ // config.bin.lib
+ //
+ {
+ value& v (b.assign ("bin.lib"));
+ if (!v)
+ v = required (r, "config.bin.lib", "both").first;
+ }
+
+ // config.bin.exe.lib
+ //
+ {
+ value& v (b.assign ("bin.exe.lib"));
+ if (!v)
+ v = required (r, "config.bin.exe.lib", exe_lib).first;
+ }
+
+ // config.bin.liba.lib
+ //
+ {
+ value& v (b.assign ("bin.liba.lib"));
+ if (!v)
+ v = required (r, "config.bin.liba.lib", liba_lib).first;
+ }
+
+ // config.bin.libso.lib
+ //
+ {
+ value& v (b.assign ("bin.libso.lib"));
+ if (!v)
+ v = required (r, "config.bin.libso.lib", libso_lib).first;
+ }
+
+ // config.bin.rpath
+ //
+ // This one is optional and we merge it into bin.rpath, if any.
+ // See the cxx module for details on merging.
+ //
+ if (const value& v = config::optional (r, "config.bin.rpath"))
+ b.assign ("bin.rpath") += as<strings> (v);
+
+
+ // Configure "installability" of our target types.
+ //
+ install::path<exe> (b, dir_path ("bin")); // Install into install.bin.
+
+ // Should shared libraries have executable bit? That depends on
+ // who you ask. In Debian, for example, it should not unless, it
+ // really is executable (i.e., has main()). On the other hand, on
+ // some systems, this may be required in order for the dynamic
+ // linker to be able to load the library. So, by default, we will
+ // keep it executable, especially seeing that this is also the
+ // behavior of autotools. At the same time, it is easy to override
+ // this, for example:
+ //
+ // config.install.lib.mode=644
+ //
+ // And a library that wants to override any such overrides (e.g.,
+ // because it does have main()) can do:
+ //
+ // libso{foo}: install.mode=755
+ //
+ // Everyone is happy then?
+ //
+ install::path<libso> (b, dir_path ("lib")); // Install into install.lib.
+
+ install::path<liba> (b, dir_path ("lib")); // Install into install.lib.
+ install::mode<liba> (b, "644");
+
+ return true;
+ }
+ }
+}
diff --git a/build2/bin/rule b/build2/bin/rule
new file mode 100644
index 0000000..ad52396
--- /dev/null
+++ b/build2/bin/rule
@@ -0,0 +1,39 @@
+// file : build2/bin/rule -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_BIN_RULE
+#define BUILD2_BIN_RULE
+
+#include <build2/rule>
+
+namespace build2
+{
+ namespace bin
+ {
+ class obj_rule: public rule
+ {
+ public:
+ virtual match_result
+ match (action, target&, const std::string& hint) const;
+
+ virtual recipe
+ apply (action, target&, const match_result&) const;
+ };
+
+ class lib_rule: public rule
+ {
+ public:
+ virtual match_result
+ match (action, target&, const std::string& hint) const;
+
+ virtual recipe
+ apply (action, target&, const match_result&) const;
+
+ static target_state
+ perform (action, target&);
+ };
+ }
+}
+
+#endif // BUILD2_BIN_RULE
diff --git a/build2/bin/rule.cxx b/build2/bin/rule.cxx
new file mode 100644
index 0000000..9d93b0a
--- /dev/null
+++ b/build2/bin/rule.cxx
@@ -0,0 +1,145 @@
+// file : build2/bin/rule.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/bin/rule>
+
+#include <build2/scope>
+#include <build2/target>
+#include <build2/algorithm>
+#include <build2/diagnostics>
+
+#include <build2/bin/target>
+
+using namespace std;
+
+namespace build2
+{
+ namespace bin
+ {
+ // obj
+ //
+ match_result obj_rule::
+ match (action a, target& t, const std::string&) const
+ {
+ fail << diag_doing (a, t) << " target group" <<
+ info << "explicitly select either obja{} or objso{} member";
+
+ return nullptr;
+ }
+
+ recipe obj_rule::
+ apply (action, target&, const match_result&) const {return empty_recipe;}
+
+ // lib
+ //
+ // The whole logic is pretty much as if we had our two group
+ // members as our prerequisites.
+ //
+ match_result lib_rule::
+ match (action a, target& xt, const std::string&) const
+ {
+ lib& t (static_cast<lib&> (xt));
+
+ // @@ We have to re-query it on each match_only()!
+
+ // Get the library type to build. If not set for a target, this
+ // should be configured at the project scope by init_lib().
+ //
+ const string& type (as<string> (*t["bin.lib"]));
+
+ bool ar (type == "static" || type == "both");
+ bool so (type == "shared" || type == "both");
+
+ if (!ar && !so)
+ fail << "unknown library type: " << type <<
+ info << "'static', 'shared', or 'both' expected";
+
+ // Search and pre-match the members. The pre-match here is part
+ // of the "library meta-information protocol" that could be used
+ // by the module that actually builds the members. The idea is
+ // that pre-matching members may populate our prerequisite_targets
+ // with prerequisite libraries from which others can extract the
+ // meta-information about the library, such as the options to use
+ // when linking it, etc.
+ //
+ if (ar)
+ {
+ if (t.a == nullptr)
+ t.a = &search<liba> (t.dir, t.name, t.ext, nullptr);
+
+ match_only (a, *t.a);
+ }
+
+ if (so)
+ {
+ if (t.so == nullptr)
+ t.so = &search<libso> (t.dir, t.name, t.ext, nullptr);
+
+ match_only (a, *t.so);
+ }
+
+ match_result mr (t, &type);
+
+ // If there is an outer operation, indicate that we match
+ // unconditionally so that we don't override ourselves.
+ //
+ if (a.outer_operation () != 0)
+ mr.recipe_action = action (a.meta_operation (), a.operation ());
+
+ return mr;
+ }
+
+ recipe lib_rule::
+ apply (action a, target& xt, const match_result& mr) const
+ {
+ lib& t (static_cast<lib&> (xt));
+
+ const string& type (*static_cast<const string*> (mr.cpvalue));
+
+ bool ar (type == "static" || type == "both");
+ bool so (type == "shared" || type == "both");
+
+ // Now we do full match.
+ //
+ if (ar)
+ build2::match (a, *t.a);
+
+ if (so)
+ build2::match (a, *t.so);
+
+ return &perform;
+ }
+
+ target_state lib_rule::
+ perform (action a, target& xt)
+ {
+ lib& t (static_cast<lib&> (xt));
+
+ //@@ Not cool we have to do this again. Looks like we need
+ // some kind of a cache vs resolved pointer, like in
+ // prerequisite vs prerequisite_target.
+ //
+ //
+ const string& type (as<string> (*t["bin.lib"]));
+ bool ar (type == "static" || type == "both");
+ bool so (type == "shared" || type == "both");
+
+ target* m1 (ar ? t.a : nullptr);
+ target* m2 (so ? t.so : nullptr);
+
+ if (current_mode == execution_mode::last)
+ swap (m1, m2);
+
+ target_state r (target_state::unchanged);
+
+ if (m1 != nullptr)
+ r |= execute (a, *m1);
+
+ if (m2 != nullptr)
+ r |= execute (a, *m2);
+
+ return r;
+ }
+ }
+}
diff --git a/build2/bin/target b/build2/bin/target
new file mode 100644
index 0000000..e785ac0
--- /dev/null
+++ b/build2/bin/target
@@ -0,0 +1,99 @@
+// file : build2/bin/target -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_BIN_TARGET
+#define BUILD2_BIN_TARGET
+
+#include <build2/target>
+
+namespace build2
+{
+ namespace bin
+ {
+ // The obj{} target group.
+ //
+ class obja: public file
+ {
+ public:
+ using file::file;
+
+ public:
+ static const target_type static_type;
+ virtual const target_type& dynamic_type () const {return static_type;}
+ };
+
+ class objso: public file
+ {
+ public:
+ using file::file;
+
+ public:
+ static const target_type static_type;
+ virtual const target_type& dynamic_type () const {return static_type;}
+ };
+
+ class obj: public target
+ {
+ public:
+ using target::target;
+
+ obja* a {nullptr};
+ objso* so {nullptr};
+
+ public:
+ static const target_type static_type;
+ virtual const target_type& dynamic_type () const {return static_type;}
+ };
+
+ class exe: public file
+ {
+ public:
+ using file::file;
+
+ public:
+ static const target_type static_type;
+ virtual const target_type& dynamic_type () const {return static_type;}
+ };
+
+ // The lib{} target group.
+ //
+ class liba: public file
+ {
+ public:
+ using file::file;
+
+ public:
+ static const target_type static_type;
+ virtual const target_type& dynamic_type () const {return static_type;}
+ };
+
+ class libso: public file
+ {
+ public:
+ using file::file;
+
+ public:
+ static const target_type static_type;
+ virtual const target_type& dynamic_type () const {return static_type;}
+ };
+
+ class lib: public target
+ {
+ public:
+ using target::target;
+
+ liba* a {nullptr};
+ libso* so {nullptr};
+
+ virtual void
+ reset (action_type);
+
+ public:
+ static const target_type static_type;
+ virtual const target_type& dynamic_type () const {return static_type;}
+ };
+ }
+}
+
+#endif // BUILD2_BIN_TARGET
diff --git a/build2/bin/target.cxx b/build2/bin/target.cxx
new file mode 100644
index 0000000..6241a7b
--- /dev/null
+++ b/build2/bin/target.cxx
@@ -0,0 +1,190 @@
+// file : build2/bin/target.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/bin/target>
+
+using namespace std;
+
+namespace build2
+{
+ namespace bin
+ {
+ static target*
+ obja_factory (const target_type&, dir_path d, string n, const string* e)
+ {
+ obj* o (targets.find<obj> (d, n));
+ obja* a (new obja (move (d), move (n), e));
+
+ if ((a->group = o))
+ o->a = a;
+
+ return a;
+ }
+
+ const target_type obja::static_type
+ {
+ "obja",
+ &file::static_type,
+ &obja_factory,
+ nullptr,
+ &search_target, // Note: not _file(); don't look for an existing file.
+ false
+ };
+
+ static target*
+ objso_factory (const target_type&, dir_path d, string n, const string* e)
+ {
+ obj* o (targets.find<obj> (d, n));
+ objso* so (new objso (move (d), move (n), e));
+
+ if ((so->group = o))
+ o->so = so;
+
+ return so;
+ }
+
+ const target_type objso::static_type
+ {
+ "objso",
+ &file::static_type,
+ &objso_factory,
+ nullptr,
+ &search_target, // Note: not _file(); don't look for an existing file.
+ false
+ };
+
+ static target*
+ obj_factory (const target_type&, dir_path d, string n, const string* e)
+ {
+ obja* a (targets.find<obja> (d, n));
+ objso* so (targets.find<objso> (d, n));
+ obj* o (new obj (move (d), move (n), e));
+
+ if ((o->a = a))
+ a->group = o;
+
+ if ((o->so = so))
+ so->group = o;
+
+ return o;
+ }
+
+ const target_type obj::static_type
+ {
+ "obj",
+ &target::static_type,
+ &obj_factory,
+ nullptr,
+ &search_target,
+ false
+ };
+
+ const target_type exe::static_type
+ {
+ "exe",
+ &file::static_type,
+ &target_factory<exe>,
+ nullptr,
+ &search_file,
+ false
+ };
+
+ static target*
+ liba_factory (const target_type& t, dir_path d, string n, const string* e)
+ {
+ // Only link-up to the group if the types match exactly.
+ //
+ lib* l (t == liba::static_type ? targets.find<lib> (d, n) : nullptr);
+ liba* a (new liba (move (d), move (n), e));
+
+ if ((a->group = l))
+ l->a = a;
+
+ return a;
+ }
+
+ // @@
+ //
+ // What extensions should we use? At the outset, this is platform-
+ // dependent. And if we consider cross-compilation, is it build or
+ // host-dependent? Feels like it should be host-dependent so that
+ // we can copy things between cross and native environments. So
+ // these will have to be determined based on what we are building.
+ // As if this is not complicated enough, the bin module doesn't
+ // know anything about building. So perhaps the extension should
+ // come from a variable that is set not by bin but by the module
+ // whose rule matched the target (e.g., cxx::link).
+ //
+ constexpr const char a_ext[] = "a";
+ const target_type liba::static_type
+ {
+ "liba",
+ &file::static_type,
+ &liba_factory,
+ &target_extension_fix<a_ext>,
+ &search_file,
+ false
+ };
+
+ static target*
+ libso_factory (const target_type& t, dir_path d, string n, const string* e)
+ {
+ // Only link-up to the group if the types match exactly.
+ //
+ lib* l (t == libso::static_type ? targets.find<lib> (d, n) : nullptr);
+ libso* so (new libso (move (d), move (n), e));
+
+ if ((so->group = l))
+ l->so = so;
+
+ return so;
+ }
+
+ constexpr const char so_ext[] = "so";
+ const target_type libso::static_type
+ {
+ "libso",
+ &file::static_type,
+ &libso_factory,
+ &target_extension_fix<so_ext>,
+ &search_file,
+ false
+ };
+
+ // lib
+ //
+ void lib::
+ reset (action_type)
+ {
+ // Don't clear prerequisite_targets since it is "given" to our
+ // members to implement "library meta-information protocol".
+ }
+
+ static target*
+ lib_factory (const target_type&, dir_path d, string n, const string* e)
+ {
+ liba* a (targets.find<liba> (d, n));
+ libso* so (targets.find<libso> (d, n));
+ lib* l (new lib (move (d), move (n), e));
+
+ if ((l->a = a))
+ a->group = l;
+
+ if ((l->so = so))
+ so->group = l;
+
+ return l;
+ }
+
+ const target_type lib::static_type
+ {
+ "lib",
+ &target::static_type,
+ &lib_factory,
+ nullptr,
+ &search_target,
+ false
+ };
+ }
+}
diff --git a/build2/buildfile b/build2/buildfile
new file mode 100644
index 0000000..0ba7535
--- /dev/null
+++ b/build2/buildfile
@@ -0,0 +1,64 @@
+# file : build2/buildfile
+# copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+import libs = libbutl%lib{butl}
+
+exe{b}: \
+ {hxx ixx txx cxx}{ algorithm } \
+ { cxx}{ b } \
+ {hxx txx cxx}{ context } \
+ {hxx cxx}{ diagnostics } \
+ {hxx cxx}{ dump } \
+ {hxx ixx cxx}{ file } \
+ {hxx cxx}{ lexer } \
+ {hxx cxx}{ module } \
+ {hxx cxx}{ name } \
+ {hxx cxx}{ operation } \
+ {hxx ixx cxx}{ options } \
+ {hxx cxx}{ parser } \
+ {hxx cxx}{ path-io } \
+ {hxx cxx}{ prerequisite } \
+ {hxx cxx}{ rule } \
+ {hxx }{ rule-map } \
+ {hxx cxx}{ scope } \
+ {hxx cxx}{ search } \
+ {hxx cxx}{ spec } \
+ {hxx ixx txx cxx}{ target } \
+ {hxx }{ target-key } \
+ {hxx }{ target-type } \
+ {hxx cxx}{ token } \
+ {hxx }{ types } \
+ {hxx cxx}{ utility } \
+ {hxx ixx txx cxx}{ variable } \
+ {hxx }{ version } \
+ bin/{hxx cxx}{ module } \
+ bin/{hxx cxx}{ rule } \
+ bin/{hxx cxx}{ target } \
+ cli/{hxx cxx}{ module } \
+ cli/{hxx cxx}{ rule } \
+ cli/{hxx cxx}{ target } \
+ config/{hxx cxx}{ module } \
+ config/{hxx cxx}{ operation } \
+ config/{hxx ixx txx cxx}{ utility } \
+ cxx/{hxx cxx}{ compile } \
+ cxx/{hxx cxx}{ install } \
+ cxx/{hxx cxx}{ link } \
+ cxx/{hxx cxx}{ module } \
+ cxx/{hxx cxx}{ target } \
+ cxx/{hxx txx cxx}{ utility } \
+ dist/{hxx cxx}{ module } \
+ dist/{hxx cxx}{ operation } \
+ dist/{hxx cxx}{ rule } \
+install/{hxx cxx}{ module } \
+install/{hxx cxx}{ operation } \
+install/{hxx cxx}{ rule } \
+install/{hxx }{ utility } \
+ test/{hxx cxx}{ module } \
+ test/{hxx cxx}{ operation } \
+ test/{hxx cxx}{ rule } \
+$libs
+
+#@@ TODO, also remove gen files from the repository/add to .gitignore
+#
+# cli --generate-specifier --cli-namespace cl --include-with-brackets --include-prefix build2 --guard-prefix BUILD2 --hxx-suffix "" options.cli
diff --git a/build2/cli/module b/build2/cli/module
new file mode 100644
index 0000000..f2890c9
--- /dev/null
+++ b/build2/cli/module
@@ -0,0 +1,23 @@
+// file : build2/cli/module -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_CLI_MODULE
+#define BUILD2_CLI_MODULE
+
+#include <build2/types>
+#include <build2/utility>
+
+#include <build2/module>
+
+namespace build2
+{
+ namespace cli
+ {
+ extern "C" bool
+ cli_init (
+ scope&, scope&, const location&, unique_ptr<module>&, bool, bool);
+ }
+}
+
+#endif // BUILD2_CLI_MODULE
diff --git a/build2/cli/module.cxx b/build2/cli/module.cxx
new file mode 100644
index 0000000..eafa4a0
--- /dev/null
+++ b/build2/cli/module.cxx
@@ -0,0 +1,244 @@
+// file : build2/cli/module.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/cli/module>
+
+#include <butl/process>
+#include <butl/fdstream>
+
+#include <build2/scope>
+#include <build2/target>
+#include <build2/variable>
+#include <build2/diagnostics>
+
+#include <build2/cxx/target>
+
+#include <build2/config/utility>
+
+#include <build2/cli/target>
+#include <build2/cli/rule>
+
+using namespace std;
+using namespace butl;
+
+namespace build2
+{
+ namespace cli
+ {
+ static compile compile_;
+
+ extern "C" bool
+ cli_init (scope& root,
+ scope& base,
+ const location& loc,
+ std::unique_ptr<module>&,
+ bool first,
+ bool optional)
+ {
+ tracer trace ("cli::init");
+ level5 ([&]{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
+ // ourselves because of the non-trivial variable merging
+ // semantics. So it is better to let the user load cxx
+ // explicitly.
+ //
+ {
+ auto l (base["cxx.loaded"]);
+
+ if (!l || !as<bool> (*l))
+ fail (loc) << "cxx module must be loaded before cli";
+ }
+
+ // Enter module variables.
+ //
+ if (first)
+ {
+ auto& v (var_pool);
+
+ v.find ("config.cli.configured", bool_type);
+
+ v.find ("config.cli", string_type); //@@ VAR type
+
+ v.find ("config.cli.options", strings_type);
+ v.find ("cli.options", strings_type);
+ }
+
+ // Register target types.
+ //
+ {
+ auto& t (base.target_types);
+
+ t.insert<cli> ();
+ t.insert<cli_cxx> ();
+ }
+
+ // Configure.
+ //
+ // The plan is as follows: try to configure the module. If this fails,
+ // we are using default values, and the module is optional, leave it
+ // unconfigured.
+ //
+
+ // We will only honor optional if the user didn't specify any cli
+ // configuration explicitly.
+ //
+ optional = optional && !config::specified (root, "config.cli");
+
+ // Don't re-run tests if the configuration says we are unconfigured.
+ //
+ if (optional)
+ {
+ auto l (root["config.cli.configured"]);
+
+ if (l && !as<bool> (*l))
+ return false;
+ }
+
+ // config.cli
+ //
+ if (first)
+ {
+ // Return version or empty string if unable to execute (e.g.,
+ // the cli executable is not found).
+ //
+ auto test = [optional] (const char* cli) -> string
+ {
+ const char* args[] = {cli, "--version", nullptr};
+
+ if (verb >= 2)
+ print_process (args);
+ else if (verb)
+ text << "test " << cli;
+
+ string ver;
+ try
+ {
+ process pr (args, 0, -1); // Open pipe to stdout.
+ ifdstream is (pr.in_ofd);
+
+ // The version should be the last word on the first line.
+ //
+ getline (is, ver);
+ auto p (ver.rfind (' '));
+ if (p != string::npos)
+ ver = string (ver, p + 1);
+
+ is.close (); // Don't block the other end.
+
+ if (!pr.wait ())
+ return string (); // Not found.
+
+ if (ver.empty ())
+ fail << "unexpected output from " << cli;
+
+ return ver;
+ }
+ catch (const process_error& e)
+ {
+ // In some cases this is not enough (e.g., the runtime linker
+ // will print scary errors if some shared libraries are not
+ // found. So it would be good to redirect child's STDERR.
+ //
+ if (!optional)
+ error << "unable to execute " << cli << ": " << e.what ();
+
+ if (e.child ())
+ exit (1);
+
+ throw failed ();
+ }
+ };
+
+ string ver;
+ const char* cli ("cli"); // Default.
+
+ if (optional)
+ {
+ // Test the default value before setting any config.cli.* values
+ // so that if we fail to configure, nothing will be written to
+ // config.build.
+ //
+ ver = test (cli);
+
+ if (ver.empty ())
+ {
+ // Note that we are unconfigured so that we don't keep re-testing
+ // this on each run.
+ //
+ root.assign ("config.cli.configured") = false;
+
+ if (verb >= 2)
+ text << cli << " not found, leaving cli module unconfigured";
+
+ return false;
+ }
+ else
+ {
+ auto p (config::required (root, "config.cli", cli));
+ assert (p.second && as<string> (p.first) == cli);
+ }
+ }
+ else
+ {
+ auto p (config::required (root, "config.cli", cli));
+
+ // If we actually set a new value, test it by trying to execute.
+ //
+ if (p.second)
+ {
+ cli = as<string> (p.first).c_str ();
+ ver = test (cli);
+
+ if (ver.empty ())
+ throw failed ();
+ }
+ }
+
+ // Clear the unconfigured flag, if any.
+ //
+ root.assign ("config.cli.configured") = true;
+
+ if (!ver.empty () && verb >= 2)
+ text << cli << " " << ver;
+ }
+
+ // config.cli.options
+ //
+ // This one is optional. We also merge it into the corresponding
+ // cli.* variables. See the cxx module for more information on
+ // this merging semantics and some of its tricky aspects.
+ //
+ if (const value& v = config::optional (root, "config.cli.options"))
+ base.assign ("cli.options") += as<strings> (v);
+
+ // Register our rules.
+ //
+ {
+ auto& r (base.rules);
+
+ r.insert<cli_cxx> (perform_update_id, "cli.compile", compile_);
+ r.insert<cli_cxx> (perform_clean_id, "cli.compile", compile_);
+
+ r.insert<cxx::hxx> (perform_update_id, "cli.compile", compile_);
+ r.insert<cxx::hxx> (perform_clean_id, "cli.compile", compile_);
+
+ r.insert<cxx::cxx> (perform_update_id, "cli.compile", compile_);
+ r.insert<cxx::cxx> (perform_clean_id, "cli.compile", compile_);
+
+ r.insert<cxx::ixx> (perform_update_id, "cli.compile", compile_);
+ r.insert<cxx::ixx> (perform_clean_id, "cli.compile", compile_);
+
+ // Other rules (e.g., cxx::compile) may need to have the group
+ // members resolved. Looks like a general pattern: groups should
+ // resolve on configure(update).
+ //
+ r.insert<cli_cxx> (configure_update_id, "cli.compile", compile_);
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/build2/cli/rule b/build2/cli/rule
new file mode 100644
index 0000000..6c154e2
--- /dev/null
+++ b/build2/cli/rule
@@ -0,0 +1,32 @@
+// file : build2/cli/rule -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_CLI_RULE
+#define BUILD2_CLI_RULE
+
+#include <build2/rule>
+
+namespace build2
+{
+ namespace cli
+ {
+ class compile: public rule
+ {
+ public:
+ virtual match_result
+ match (action, target&, const std::string& hint) const;
+
+ virtual recipe
+ apply (action, target&, const match_result&) const;
+
+ static target_state
+ perform_update (action, target&);
+
+ static target_state
+ perform_clean (action, target&);
+ };
+ }
+}
+
+#endif // BUILD2_CLI_RULE
diff --git a/build2/cli/rule.cxx b/build2/cli/rule.cxx
new file mode 100644
index 0000000..ae1f48a
--- /dev/null
+++ b/build2/cli/rule.cxx
@@ -0,0 +1,305 @@
+// file : build2/cli/rule.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/cli/rule>
+
+#include <butl/process>
+
+#include <build2/types>
+#include <build2/scope>
+#include <build2/target>
+#include <build2/context>
+#include <build2/algorithm>
+#include <build2/diagnostics>
+
+#include <build2/cli/target>
+
+#include <build2/config/utility>
+
+using namespace std;
+using namespace butl;
+
+namespace build2
+{
+ namespace cli
+ {
+ match_result compile::
+ match (action a, target& xt, const std::string&) const
+ {
+ tracer trace ("cli::compile::match");
+
+ if (cli_cxx* pt = xt.is_a<cli_cxx> ())
+ {
+ // The cli.cxx{} group.
+ //
+ cli_cxx& t (*pt);
+
+ // See if we have a .cli source file.
+ //
+ match_result r;
+ for (prerequisite_member p: group_prerequisite_members (a, t))
+ {
+ if (p.is_a<cli> ())
+ {
+ // Check that the stems match.
+ //
+ if (t.name != p.name ())
+ {
+ level4 ([&]{trace << ".cli file stem '" << p.name () << "' "
+ << "doesn't match target " << t;});
+ return r;
+ }
+
+ r = p;
+ break;
+ }
+ }
+
+ if (!r)
+ {
+ level4 ([&]{trace << "no .cli source file for target " << t;});
+ return r;
+ }
+
+ // If we still haven't figured out the member list, we can do
+ // that now. Specifically, at this stage, no further changes to
+ // cli.options are possible and we can determine whether the
+ // --suppress-inline option is present.
+ //
+ if (t.h == nullptr)
+ {
+ t.h = &search<cxx::hxx> (t.dir, t.name, nullptr, nullptr);
+ t.h->group = &t;
+
+ t.c = &search<cxx::cxx> (t.dir, t.name, nullptr, nullptr);
+ t.c->group = &t;
+
+ if (!config::find_option ("--suppress-inline", t, "cli.options"))
+ {
+ t.i = &search<cxx::ixx> (t.dir, t.name, nullptr, nullptr);
+ t.i->group = &t;
+ }
+ }
+
+ return r;
+ }
+ else
+ {
+ // One of the ?xx{} members.
+ //
+ target& t (xt);
+
+ // First see if we are already linked-up to the cli.cxx{} group.
+ // If it is some other group, then we are definitely not a match.
+ //
+ if (t.group != nullptr)
+ return t.group->is_a<cli_cxx> ();
+
+ // Then check if there is a corresponding cli.cxx{} group.
+ //
+ cli_cxx* g (targets.find<cli_cxx> (t.dir, t.name));
+
+ // If not or if it has no prerequisites (happens when we use it to
+ // set cli.options) and this target has a cli{} prerequisite, then
+ // synthesize the group.
+ //
+ if (g == nullptr || !g->has_prerequisites ())
+ {
+ for (prerequisite_member p: group_prerequisite_members (a, t))
+ {
+ if (p.is_a<cli> ())
+ {
+ // Check that the stems match.
+ //
+ if (t.name == p.name ())
+ {
+ if (g == nullptr)
+ g = &targets.insert<cli_cxx> (t.dir, t.name, trace);
+
+ g->prerequisites.emplace_back (p.as_prerequisite (trace));
+ }
+ else
+ level4 ([&]{trace << ".cli file stem '" << p.name () << "' "
+ << "doesn't match target " << t;});
+ break;
+ }
+ }
+ }
+
+ if (g != nullptr)
+ {
+ // Resolve the group's members. This should link us up to
+ // the group.
+ //
+ resolve_group_members (a, *g);
+
+ // For ixx{}, verify it is part of the group.
+ //
+ if (t.is_a<cxx::ixx> () && g->i == nullptr)
+ {
+ level4 ([&]{trace << "generation of inline file " << t
+ << " is disabled with --suppress-inline";});
+ g = nullptr;
+ }
+ }
+
+ assert (t.group == g);
+ return g;
+ }
+ }
+
+ recipe compile::
+ apply (action a, target& xt, const match_result& mr) const
+ {
+ if (cli_cxx* pt = xt.is_a<cli_cxx> ())
+ {
+ cli_cxx& t (*pt);
+
+ // Derive file names for the members.
+ //
+ t.h->derive_path ();
+ t.c->derive_path ();
+ if (t.i != nullptr)
+ t.i->derive_path ();
+
+ // Inject dependency on the output directory.
+ //
+ inject_parent_fsdir (a, t);
+
+ // Search and match prerequisite members.
+ //
+ search_and_match_prerequisite_members (a, t);
+
+ switch (a)
+ {
+ case perform_update_id: return &perform_update;
+ case perform_clean_id: return &perform_clean;
+ default: return noop_recipe; // Configure update.
+ }
+ }
+ else
+ {
+ cli_cxx& g (*static_cast<cli_cxx*> (mr.target));
+ build2::match (a, g);
+ return group_recipe; // Execute the group's recipe.
+ }
+ }
+
+ static void
+ append_extension (cstrings& args,
+ path_target& t,
+ const char* option,
+ const char* default_extension)
+ {
+ assert (t.ext != nullptr); // Should have been figured out in apply().
+
+ if (*t.ext != default_extension)
+ {
+ // CLI needs the extension with the leading dot (unless it is empty)
+ // while we store the extension without. But if there is an extension,
+ // then we can get it (with the dot) from the file name.
+ //
+ args.push_back (option);
+ args.push_back (t.ext->empty ()
+ ? t.ext->c_str ()
+ : t.path ().extension () - 1);
+ }
+ }
+
+ target_state compile::
+ perform_update (action a, target& xt)
+ {
+ cli_cxx& t (static_cast<cli_cxx&> (xt));
+
+ // Execute our prerequsites and check if we are out of date.
+ //
+ cli* s (execute_prerequisites<cli> (a, t, t.mtime ()));
+
+ if (s == nullptr)
+ return target_state::unchanged;
+
+ // Translate paths to relative (to working directory). This
+ // results in easier to read diagnostics.
+ //
+ path relo (relative (t.dir));
+ path rels (relative (s->path ()));
+
+ scope& rs (t.root_scope ());
+ const string& cli (as<string> (*rs["config.cli"]));
+
+ cstrings args {cli.c_str ()};
+
+ // See if we need to pass any --?xx-suffix options.
+ //
+ append_extension (args, *t.h, "--hxx-suffix", "hxx");
+ append_extension (args, *t.c, "--cxx-suffix", "cxx");
+ if (t.i != nullptr)
+ append_extension (args, *t.i, "--ixx-suffix", "ixx");
+
+ config::append_options (args, t, "cli.options");
+
+ if (!relo.empty ())
+ {
+ args.push_back ("-o");
+ args.push_back (relo.string ().c_str ());
+ }
+
+ args.push_back (rels.string ().c_str ());
+ args.push_back (nullptr);
+
+ if (verb >= 2)
+ print_process (args);
+ else if (verb)
+ text << "cli " << *s;
+
+ try
+ {
+ process pr (args.data ());
+
+ if (!pr.wait ())
+ throw failed ();
+
+ t.mtime (system_clock::now ());
+ }
+ catch (const process_error& e)
+ {
+ error << "unable to execute " << args[0] << ": " << e.what ();
+
+ if (e.child ())
+ exit (1);
+
+ throw failed ();
+ }
+
+ return target_state::changed;
+ }
+
+ target_state compile::
+ perform_clean (action a, target& xt)
+ {
+ cli_cxx& t (static_cast<cli_cxx&> (xt));
+
+ // The reverse order of update: first delete the files, then clean
+ // prerequisites. Also update timestamp in case there are operations
+ // after us that could use the information.
+ //
+ bool r (false);
+
+ if (t.i != nullptr)
+ r = rmfile (t.i->path (), *t.i) || r;
+ r = rmfile (t.c->path (), *t.c) || r;
+ r = rmfile (t.h->path (), *t.h) || r;
+
+ t.mtime (timestamp_nonexistent);
+
+ target_state ts (r ? target_state::changed : target_state::unchanged);
+
+ // Clean prerequisites.
+ //
+ ts |= reverse_execute_prerequisites (a, t);
+
+ return ts;
+ }
+ }
+}
diff --git a/build2/cli/target b/build2/cli/target
new file mode 100644
index 0000000..dde20be
--- /dev/null
+++ b/build2/cli/target
@@ -0,0 +1,62 @@
+// file : build2/cli/target -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_CLI_TARGET
+#define BUILD2_CLI_TARGET
+
+#include <build2/target>
+
+#include <build2/cxx/target>
+
+namespace build2
+{
+ namespace cli
+ {
+ class cli: public file
+ {
+ public:
+ using file::file;
+
+ public:
+ static const target_type static_type;
+ virtual const target_type& dynamic_type () const {return static_type;}
+ };
+
+ class cli_cxx: public mtime_target
+ {
+ public:
+ using mtime_target::mtime_target;
+
+ union
+ {
+ // It is theoretically possible that the compiler will add
+ // padding between the members of this struct. This would
+ // mean that the optimal alignment for a pointer is greater
+ // than its size (and that an array of pointers is sub-
+ // optimally aligned). We will deal with such a beast of
+ // an architecture when we see it.
+ //
+ struct
+ {
+ cxx::hxx* h;
+ cxx::cxx* c;
+ cxx::ixx* i;
+ };
+ target* m[3] = {nullptr, nullptr, nullptr};
+ };
+
+ virtual group_view
+ group_members (action_type) const;
+
+ virtual timestamp
+ load_mtime () const;
+
+ public:
+ static const target_type static_type;
+ virtual const target_type& dynamic_type () const {return static_type;}
+ };
+ }
+}
+
+#endif // BUILD2_CLI_TARGET
diff --git a/build2/cli/target.cxx b/build2/cli/target.cxx
new file mode 100644
index 0000000..ef69e88
--- /dev/null
+++ b/build2/cli/target.cxx
@@ -0,0 +1,77 @@
+// file : build2/cli/target.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/cli/target>
+
+#include <butl/filesystem>
+
+using namespace std;
+using namespace butl;
+
+namespace build2
+{
+ namespace cli
+ {
+ // cli
+ //
+ constexpr const char cli_ext_var[] = "extension";
+ constexpr const char cli_ext_def[] = "cli";
+
+ const target_type cli::static_type
+ {
+ "cli",
+ &file::static_type,
+ &target_factory<cli>,
+ &target_extension_var<cli_ext_var, cli_ext_def>,
+ &search_file,
+ false
+ };
+
+ // cli.cxx
+ //
+ group_view cli_cxx::
+ group_members (action_type) const
+ {
+ return h != nullptr
+ ? group_view {m, (i != nullptr ? 3U : 2U)}
+ : group_view {nullptr, 0};
+ }
+
+ timestamp cli_cxx::
+ load_mtime () const
+ {
+ // The rule has been matched which means the members should
+ // be resolved and paths assigned.
+ //
+ return file_mtime (h->path ());
+ }
+
+ static target*
+ cli_cxx_factory (const target_type&, dir_path d, string n, const string* e)
+ {
+ tracer trace ("cli::cli_cxx_factory");
+
+ // Pre-enter (potential) members as targets. The main purpose
+ // of doing this is to avoid searching for existing files in
+ // src_base if the buildfile mentions some of them explicitly
+ // as prerequisites.
+ //
+ targets.insert<cxx::hxx> (d, n, trace);
+ targets.insert<cxx::cxx> (d, n, trace);
+ targets.insert<cxx::ixx> (d, n, trace);
+
+ return new cli_cxx (move (d), move (n), e);
+ }
+
+ const target_type cli_cxx::static_type
+ {
+ "cli.cxx",
+ &mtime_target::static_type,
+ &cli_cxx_factory,
+ nullptr,
+ &search_target,
+ true // "See through" default iteration mode.
+ };
+ }
+}
diff --git a/build2/config/module b/build2/config/module
new file mode 100644
index 0000000..5888376
--- /dev/null
+++ b/build2/config/module
@@ -0,0 +1,26 @@
+// file : build2/config/module -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_CONFIG_MODULE
+#define BUILD2_CONFIG_MODULE
+
+#include <build2/types>
+#include <build2/utility>
+
+#include <build2/module>
+
+namespace build2
+{
+ namespace config
+ {
+ extern "C" void
+ config_boot (scope&, const location&, unique_ptr<module>&);
+
+ extern "C" bool
+ config_init (
+ scope&, scope&, const location&, unique_ptr<module>&, bool, bool);
+ }
+}
+
+#endif // BUILD2_CONFIG_MODULE
diff --git a/build2/config/module.cxx b/build2/config/module.cxx
new file mode 100644
index 0000000..3d03a51
--- /dev/null
+++ b/build2/config/module.cxx
@@ -0,0 +1,90 @@
+// file : build2/config/module.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/config/module>
+
+#include <butl/filesystem>
+
+#include <build2/file>
+#include <build2/rule>
+#include <build2/scope>
+#include <build2/diagnostics>
+
+#include <build2/config/operation>
+
+using namespace std;
+using namespace butl;
+
+namespace build2
+{
+ namespace config
+ {
+ //@@ Same as in operation.cxx
+ //
+ static const path config_file ("build/config.build");
+
+ extern "C" void
+ config_boot (scope& root, const location&, unique_ptr<module>&)
+ {
+ tracer trace ("config::boot");
+
+ const dir_path& out_root (root.out_path ());
+ level5 ([&]{trace << "for " << out_root;});
+
+ // Register meta-operations.
+ //
+ root.meta_operations.insert (configure_id, configure);
+ root.meta_operations.insert (disfigure_id, disfigure);
+
+ // Load config.build if one exists.
+ //
+ // Note that we have to do this during bootstrap since the order in
+ // which the modules will be initialized is unspecified. So it is
+ // possible that some module which needs the configuration will get
+ // called first.
+ //
+ path f (out_root / config_file);
+
+ if (file_exists (f))
+ source (f, root, root);
+ }
+
+ extern "C" bool
+ config_init (scope& root,
+ scope&,
+ const location& l,
+ std::unique_ptr<module>&,
+ bool first,
+ bool)
+ {
+ tracer trace ("config::init");
+
+ if (!first)
+ {
+ warn (l) << "multiple config module initializations";
+ return true;
+ }
+
+ level5 ([&]{trace << "for " << root.out_path ();});
+
+ // Register alias and fallback rule for the configure meta-operation.
+ //
+ {
+ // We need this rule for out-of-any-project dependencies (e.g.,
+ // libraries imported from /usr/lib).
+ //
+ global_scope->rules.insert<file> (
+ configure_id, 0, "config.file", file_rule::instance);
+
+ auto& r (root.rules);
+
+ r.insert<target> (configure_id, 0, "config", fallback_rule::instance);
+ r.insert<file> (configure_id, 0, "config.file", fallback_rule::instance);
+ r.insert<alias> (configure_id, 0, "config.alias", alias_rule::instance);
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/build2/config/operation b/build2/config/operation
new file mode 100644
index 0000000..63eba7e
--- /dev/null
+++ b/build2/config/operation
@@ -0,0 +1,19 @@
+// file : build2/config/operation -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_CONFIG_OPERATION
+#define BUILD2_CONFIG_OPERATION
+
+#include <build2/operation>
+
+namespace build2
+{
+ namespace config
+ {
+ extern meta_operation_info configure;
+ extern meta_operation_info disfigure;
+ }
+}
+
+#endif // BUILD2_CONFIG_OPERATION
diff --git a/build2/config/operation.cxx b/build2/config/operation.cxx
new file mode 100644
index 0000000..96d9be7
--- /dev/null
+++ b/build2/config/operation.cxx
@@ -0,0 +1,455 @@
+// file : build2/config/operation.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/config/operation>
+
+#include <fstream>
+
+#include <butl/filesystem>
+
+#include <build2/file>
+#include <build2/scope>
+#include <build2/target>
+#include <build2/context>
+#include <build2/algorithm>
+#include <build2/diagnostics>
+
+using namespace std;
+using namespace butl;
+
+namespace build2
+{
+ namespace config
+ {
+ static const path config_file ("build/config.build");
+
+ // configure
+ //
+ static operation_id
+ configure_operation_pre (operation_id o)
+ {
+ // Don't translate default to update. In our case unspecified
+ // means configure everything.
+ //
+ return o;
+ }
+
+ static void
+ save_src_root (const dir_path& out_root, const dir_path& src_root)
+ {
+ path f (out_root / src_root_file);
+
+ if (verb)
+ text << (verb >= 2 ? "config::save_src_root " : "save ") << f;
+
+ try
+ {
+ ofstream ofs (f.string ());
+ if (!ofs.is_open ())
+ fail << "unable to open " << f;
+
+ ofs.exceptions (ofstream::failbit | ofstream::badbit);
+
+ //@@ TODO: quote path
+ //
+ ofs << "# Created automatically by the config module." << endl
+ << "#" << endl
+ << "src_root = " << src_root << endl;
+ }
+ catch (const ofstream::failure&)
+ {
+ fail << "unable to write " << f;
+ }
+ }
+
+ static void
+ save_config (scope& root)
+ {
+ const dir_path& out_root (root.out_path ());
+ path f (out_root / config_file);
+
+ if (verb)
+ text << (verb >= 2 ? "config::save_config " : "save ") << f;
+
+ try
+ {
+ ofstream ofs (f.string ());
+ if (!ofs.is_open ())
+ fail << "unable to open " << f;
+
+ ofs.exceptions (ofstream::failbit | ofstream::badbit);
+
+ ofs << "# Created automatically by the config module, but" << endl
+ << "# feel free to edit." << endl
+ << "#" << endl;
+
+ if (auto l = root.vars["amalgamation"])
+ {
+ const dir_path& d (as<dir_path> (*l));
+
+ ofs << "# Base configuration inherited from " << d << endl
+ << "#" << endl;
+ }
+
+ // Save all the variables in the config namespace that are set
+ // on the project's root scope.
+ //
+ for (auto p (root.vars.find_namespace ("config"));
+ p.first != p.second;
+ ++p.first)
+ {
+ const variable& var (p.first->first);
+ const value& val (p.first->second);
+ const string& n (var.name);
+
+ // Skip special variables.
+ //
+ if (n == "config.loaded" ||
+ n == "config.configured")
+ continue;
+
+ // We will only write config.*.configured if it is false
+ // (true is implied by its absence).
+ //
+ if (n.size () > 11 &&
+ n.compare (n.size () - 11, 11, ".configured") == 0)
+ {
+ if (val == nullptr || as<bool> (val))
+ continue;
+ }
+
+ // Warn the user if the value that we are saving differs
+ // from the one they specified on the command line.
+ //
+ auto l ((*global_scope)[var]);
+ if (l.defined () && *l != val)
+ {
+ warn << "variable " << var.name << " configured value "
+ << "differs from command line value" <<
+ info << "reconfigure the project to use command line value";
+ }
+
+ if (val)
+ {
+ ofs << var.name << " = " << val.data_ << endl;
+ //text << var.name << " = " << val.data_;
+ }
+ else
+ {
+ ofs << var.name << " = #[null]" << endl; // @@ TODO: [null]
+ //text << var.name << " = [null]";
+ }
+ }
+ }
+ catch (const ofstream::failure&)
+ {
+ fail << "unable to write " << f;
+ }
+ }
+
+ static void
+ configure_project (action a, scope& root)
+ {
+ tracer trace ("configure_project");
+
+ const dir_path& out_root (root.out_path ());
+ const dir_path& src_root (root.src_path ());
+
+ // Make sure the directories exist.
+ //
+ if (out_root != src_root)
+ {
+ mkdir_p (out_root);
+ mkdir (out_root / build_dir);
+ mkdir (out_root / bootstrap_dir);
+ }
+
+ // We distinguish between a complete configure and operation-
+ // specific.
+ //
+ if (a.operation () == default_id)
+ {
+ level5 ([&]{trace << "completely configuring " << out_root;});
+
+ // Save src-root.build unless out_root is the same as src.
+ //
+ if (out_root != src_root)
+ save_src_root (out_root, src_root);
+
+ // Save config.build.
+ //
+ save_config (root);
+ }
+ else
+ {
+ }
+
+ // Configure subprojects that have been loaded.
+ //
+ if (auto l = root.vars["subprojects"])
+ {
+ for (auto p: as<subprojects> (*l))
+ {
+ const dir_path& pd (p.second);
+ dir_path out_nroot (out_root / pd);
+ scope& nroot (scopes.find (out_nroot));
+
+ // @@ Strictly speaking we need to check whether the config
+ // module was loaded for this subproject.
+ //
+ if (nroot.out_path () != out_nroot) // This subproject not loaded.
+ continue;
+
+ configure_project (a, nroot);
+ }
+ }
+ }
+
+ static void
+ configure_match (action, action_targets&)
+ {
+ // Don't match anything -- see execute ().
+ }
+
+ static void
+ configure_execute (action a, const action_targets& ts, bool)
+ {
+ // Match rules to configure every operation supported by each
+ // project. Note that we are not calling operation_pre/post()
+ // callbacks here since the meta operation is configure and we
+ // know what we are doing.
+ //
+ for (void* v: ts)
+ {
+ target& t (*static_cast<target*> (v));
+ scope* rs (t.base_scope ().root_scope ());
+
+ if (rs == nullptr)
+ fail << "out of project target " << t;
+
+ for (operations::size_type id (default_id + 1); // Skip default_id
+ id < rs->operations.size ();
+ ++id)
+ {
+ const operation_info* oi (rs->operations[id]);
+ if (oi == nullptr)
+ continue;
+
+ current_inner_oif = oi;
+ current_outer_oif = nullptr;
+ current_mode = oi->mode;
+ dependency_count = 0;
+
+ match (action (configure_id, id), t);
+ }
+
+ configure_project (a, *rs);
+ }
+ }
+
+ meta_operation_info configure {
+ "configure",
+ "configure",
+ "configuring",
+ "is configured",
+ nullptr, // meta-operation pre
+ &configure_operation_pre,
+ &load, // normal load
+ &search, // normal search
+ &configure_match,
+ &configure_execute,
+ nullptr, // operation post
+ nullptr // meta-operation post
+ };
+
+ // disfigure
+ //
+ static operation_id
+ disfigure_operation_pre (operation_id o)
+ {
+ // Don't translate default to update. In our case unspecified
+ // means disfigure everything.
+ //
+ return o;
+ }
+
+ static void
+ disfigure_load (const path& bf,
+ scope&,
+ const dir_path&,
+ const dir_path&,
+ const location&)
+ {
+ tracer trace ("disfigure_load");
+ level6 ([&]{trace << "skipping " << bf;});
+ }
+
+ static void
+ disfigure_search (scope& root,
+ const target_key&,
+ const location&,
+ action_targets& ts)
+ {
+ tracer trace ("disfigure_search");
+ level6 ([&]{trace << "collecting " << root.out_path ();});
+ ts.push_back (&root);
+ }
+
+ static void
+ disfigure_match (action, action_targets&) {}
+
+ static bool
+ disfigure_project (action a, scope& root)
+ {
+ tracer trace ("disfigure_project");
+
+ bool m (false); // Keep track of whether we actually did anything.
+
+ 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
+ // disfigure, we do it for all known subprojects.
+ //
+ if (auto l = root.vars["subprojects"])
+ {
+ for (auto p: as<subprojects> (*l))
+ {
+ const dir_path& pd (p.second);
+
+ // Create and bootstrap subproject's root scope.
+ //
+ dir_path out_nroot (out_root / pd);
+
+ // The same logic for src_root as in create_bootstrap_inner().
+ //
+ scope& nroot (create_root (out_nroot, dir_path ()));
+ bootstrap_out (nroot);
+
+ value& val (nroot.assign ("src_root"));
+
+ if (!val)
+ val = is_src_root (out_nroot) ? out_nroot : (src_root / pd);
+
+ setup_root (nroot);
+
+ bootstrap_src (nroot);
+
+ m = disfigure_project (a, nroot) || m;
+
+ // We use mkdir_p() to create the out_root of a subproject
+ // which means there could be empty parent directories left
+ // behind. Clean them up.
+ //
+ if (!pd.simple () && out_root != src_root)
+ {
+ for (dir_path d (pd.directory ());
+ !d.empty ();
+ d = d.directory ())
+ {
+ rmdir_status s (rmdir (out_root / d));
+
+ if (s == rmdir_status::not_empty)
+ break; // No use trying do remove parent ones.
+
+ m = (s == rmdir_status::success) || m;
+ }
+ }
+ }
+ }
+
+ // We distinguish between a complete disfigure and operation-
+ // specific.
+ //
+ if (a.operation () == default_id)
+ {
+ level5 ([&]{trace << "completely disfiguring " << out_root;});
+
+ m = rmfile (out_root / config_file) || m;
+
+ if (out_root != src_root)
+ {
+ m = rmfile (out_root / src_root_file) || m;
+
+ // Clean up the directories.
+ //
+ m = rmdir (out_root / bootstrap_dir) || m;
+ m = rmdir (out_root / build_dir) || m;
+
+ switch (rmdir (out_root))
+ {
+ case rmdir_status::not_empty:
+ {
+ warn << "directory " << out_root << " is "
+ << (out_root == work
+ ? "current working directory"
+ : "not empty") << ", not removing";
+ break;
+ }
+ case rmdir_status::success:
+ m = true;
+ default:
+ break;
+ }
+ }
+ }
+ else
+ {
+ }
+
+ return m;
+ }
+
+ static void
+ disfigure_execute (action a, const action_targets& ts, bool quiet)
+ {
+ tracer trace ("disfigure_execute");
+
+ for (void* v: ts)
+ {
+ scope& root (*static_cast<scope*> (v));
+
+ if (!disfigure_project (a, root))
+ {
+ // Create a dir{$out_root/} target to signify the project's
+ // root in diagnostics. Not very clean but seems harmless.
+ //
+ target& t (
+ targets.insert (
+ dir::static_type, root.out_path (), "", nullptr, trace).first);
+
+ if (!quiet)
+ info << diag_done (a, t);
+ }
+ }
+ }
+
+ static void
+ disfigure_meta_operation_post ()
+ {
+ tracer trace ("disfigure_meta_operation_post");
+
+ // Reset the dependency state since anything that could have been
+ // loaded earlier using a previous configuration is now invalid.
+ //
+ level6 ([&]{trace << "resetting dependency state";});
+ reset ();
+ }
+
+ meta_operation_info disfigure {
+ "disfigure",
+ "disfigure",
+ "disfiguring",
+ "is disfigured",
+ nullptr, // meta-operation pre
+ &disfigure_operation_pre,
+ &disfigure_load,
+ &disfigure_search,
+ &disfigure_match,
+ &disfigure_execute,
+ nullptr, // operation post
+ &disfigure_meta_operation_post
+ };
+ }
+}
diff --git a/build2/config/utility b/build2/config/utility
new file mode 100644
index 0000000..608cf5e
--- /dev/null
+++ b/build2/config/utility
@@ -0,0 +1,128 @@
+// file : build2/config/utility -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_CONFIG_UTILITY
+#define BUILD2_CONFIG_UTILITY
+
+#include <string>
+#include <utility> // pair
+#include <functional> // reference_wrapper
+
+#include <build2/types>
+#include <build2/variable>
+#include <build2/diagnostics>
+
+namespace build2
+{
+ class scope;
+
+ namespace config
+ {
+ // Set, if necessary, a required config.* variable.
+ //
+ // If override is true and the variable doesn't come from this root
+ // scope or from the command line, then its value is "overridden"
+ // for this root scope.
+ //
+ // Return the reference to the value as well as the indication of
+ // whether the variable has actually been set.
+ //
+ template <typename T>
+ std::pair<std::reference_wrapper<const value>, bool>
+ required (scope& root,
+ const variable&,
+ const T& default_value,
+ bool override = false);
+
+ template <typename T>
+ inline std::pair<std::reference_wrapper<const value>, bool>
+ required (scope& root,
+ const std::string& name,
+ const T& default_value,
+ bool override = false)
+ {
+ return required (root, var_pool.find (name), default_value, override);
+ }
+
+ inline std::pair<std::reference_wrapper<const value>, bool>
+ required (scope& root,
+ const std::string& name,
+ const char* default_value,
+ bool override = false)
+ {
+ return required (root, name, std::string (default_value), override);
+ }
+
+ // Set, if necessary, an optional config.* variable. In particular,
+ // an unspecified variable is set to NULL which is used to distinguish
+ // between the "configured as unspecified" and "not yet configured"
+ // cases.
+ //
+ // Return the value, which can be NULL.
+ //
+ const value&
+ optional (scope& root, const variable&);
+
+ inline const value&
+ optional (scope& root, const std::string& var)
+ {
+ return optional (root, var_pool.find (var));
+ }
+
+ // As above but assumes the value is dir_path and makes it
+ // absolute if the value specified on the command line is
+ // relative.
+ //
+ const value&
+ optional_absolute (scope& root, const variable&);
+
+ inline const value&
+ optional_absolute (scope& root, const std::string& var)
+ {
+ return optional_absolute (root, var_pool.find (var));
+ }
+
+ // Check whether there are any variables specified from the config
+ // namespace. The idea is that we can check if there are any, say,
+ // config.install.* values. If there are none, then we can assume
+ // this functionality is not (yet) used and omit writing a whole
+ // bunch of NULL config.install.* values to the config.build file.
+ // We call it omitted/delayed configuration.
+ //
+ // Note that this function detects and ignores the special
+ // config.*.configured variable which may be used by a module to
+ // "remember" that it is unconfigured.
+ //
+ bool
+ specified (scope& root, const std::string& ns);
+
+ // @@ Why are these here?
+ //
+
+ // Add all the values from a variable to the C-string list. T is
+ // either target or scope. The variable is expected to be of type
+ // strings.
+ //
+ template <typename T>
+ void
+ append_options (cstrings& args, T& s, const char* var);
+
+ // As above but from the strings value directly.
+ //
+ void
+ append_options (cstrings& args, const const_strings_value&);
+
+ // Check if a specified option is present in the variable value.
+ // T is either target or scope.
+ //
+ template <typename T>
+ bool
+ find_option (const char* option, T& s, const char* var);
+ }
+}
+
+#include <build2/config/utility.txx>
+#include <build2/config/utility.ixx>
+
+#endif // BUILD2_CONFIG_UTILITY
diff --git a/build2/config/utility.cxx b/build2/config/utility.cxx
new file mode 100644
index 0000000..361bd5e
--- /dev/null
+++ b/build2/config/utility.cxx
@@ -0,0 +1,92 @@
+// file : build2/config/utility.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/config/utility>
+
+#include <build2/context>
+
+using namespace std;
+
+namespace build2
+{
+ namespace config
+ {
+ const value&
+ optional (scope& root, const variable& var)
+ {
+ auto l (root[var]);
+
+ return l.defined ()
+ ? l.belongs (*global_scope) ? (root.assign (var) = *l) : *l
+ : root.assign (var); // NULL
+ }
+
+ const value&
+ optional_absolute (scope& root, const variable& var)
+ {
+ auto l (root[var]);
+
+ if (!l.defined ())
+ return root.assign (var); // NULL
+
+ if (!l.belongs (*global_scope)) // Value from (some) root scope.
+ return *l;
+
+ // Make the command-line value absolute. This is necessary to avoid
+ // a warning issued by the config module about global/root scope
+ // value mismatch.
+ //
+ value& v (const_cast<value&> (*l));
+
+ if (v && !v.empty ())
+ {
+ dir_path& d (as<dir_path> (v));
+
+ if (d.relative ())
+ {
+ d = work / d;
+ d.normalize ();
+ }
+ }
+
+ return root.assign (var) = v;
+ }
+
+ bool
+ specified (scope& r, const string& ns)
+ {
+ // Search all outer scopes for any value in this namespace.
+ //
+ for (scope* s (&r); s != nullptr; s = s->parent_scope ())
+ {
+ for (auto p (s->vars.find_namespace (ns));
+ p.first != p.second;
+ ++p.first)
+ {
+ const variable& var (p.first->first);
+
+ // Ignore config.*.configured.
+ //
+ if (var.name.size () < 11 ||
+ var.name.compare (var.name.size () - 11, 11, ".configured") != 0)
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ void
+ append_options (cstrings& args, const const_strings_value& sv)
+ {
+ if (!sv.empty ())
+ {
+ args.reserve (args.size () + sv.size ());
+
+ for (const string& s: sv)
+ args.push_back (s.c_str ());
+ }
+ }
+ }
+}
diff --git a/build2/config/utility.ixx b/build2/config/utility.ixx
new file mode 100644
index 0000000..b665248
--- /dev/null
+++ b/build2/config/utility.ixx
@@ -0,0 +1,17 @@
+// file : build2/config/utility.ixx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+namespace build2
+{
+ namespace config
+ {
+ template <typename T>
+ inline void
+ append_options (cstrings& args, T& s, const char* var)
+ {
+ if (auto l = s[var])
+ append_options (args, as<strings> (*l));
+ }
+ }
+}
diff --git a/build2/config/utility.txx b/build2/config/utility.txx
new file mode 100644
index 0000000..c88a34f
--- /dev/null
+++ b/build2/config/utility.txx
@@ -0,0 +1,45 @@
+// file : build2/config/utility.txx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/scope>
+
+namespace build2
+{
+ namespace config
+ {
+ template <typename T>
+ std::pair<std::reference_wrapper<const value>, bool>
+ required (scope& root, const variable& var, const T& def_value, bool ovr)
+ {
+ using result = std::pair<std::reference_wrapper<const value>, bool>;
+
+ if (auto l = root[var])
+ {
+ if (l.belongs (*global_scope))
+ return result (root.assign (var) = *l, true);
+
+ if (!ovr || l.belongs (root))
+ return result (*l, false);
+ }
+
+ return result (root.assign (var) = def_value, true);
+ }
+
+ template <typename T>
+ bool
+ find_option (const char* option, T& s, const char* var)
+ {
+ if (auto l = s[var])
+ {
+ for (const std::string& s: as<strings> (*l))
+ {
+ if (s == option)
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/build2/context b/build2/context
new file mode 100644
index 0000000..22d4b52
--- /dev/null
+++ b/build2/context
@@ -0,0 +1,165 @@
+// file : build2/context -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_CONTEXT
+#define BUILD2_CONTEXT
+
+#include <string>
+#include <ostream>
+#include <cstdint> // uint64_t
+
+#include <butl/filesystem>
+
+#include <build2/types>
+#include <build2/utility>
+#include <build2/operation>
+
+namespace build2
+{
+ class scope;
+ class file;
+
+ extern dir_path work;
+ extern dir_path home;
+
+ extern string_pool path_pool;
+ extern string_pool extension_pool;
+ extern string_pool project_name_pool;
+
+ // Current action (meta/operation).
+ //
+ extern const meta_operation_info* current_mif;
+ extern const operation_info* current_inner_oif;
+ extern const operation_info* current_outer_oif;
+
+ extern execution_mode current_mode;
+
+ // Total number of dependency relationships in the current action.
+ // Together with the target::dependents count it is incremented
+ // during the rule search & match phase and is decremented during
+ // execution with the expectation of it reaching 0. Used as a sanity
+ // check.
+ //
+ extern std::uint64_t dependency_count;
+
+ // Reset the dependency state. In particular, this removes all the
+ // targets, scopes, and variable names.
+ //
+ void
+ reset ();
+
+ // The dual interface wrapper for the {mk,rm}{file,dir}() functions
+ // below that allows you to use it as a true/false return or a more
+ // detailed enum from <butl/filesystem>
+ //
+ template <typename T>
+ struct fs_status
+ {
+ T v;
+ fs_status (T v): v (v) {};
+ operator T () const {return v;}
+ explicit operator bool () const {return v == T::success;}
+ };
+
+ // Create the directory and print the standard diagnostics. Note that
+ // this implementation is not suitable if it is expected that the
+ // directory will exist in the majority of case and performance is
+ // important. See the fsdir{} rule for details.
+ //
+ fs_status<butl::mkdir_status>
+ mkdir (const dir_path&);
+
+ fs_status<butl::mkdir_status>
+ mkdir_p (const dir_path&);
+
+ // Remove the file and print the standard diagnostics. The second
+ // argument is only used in diagnostics, to print the target name.
+ // Passing the path for target will result in the relative path
+ // being printed.
+ //
+ template <typename T>
+ fs_status<butl::rmfile_status>
+ rmfile (const path&, const T& target);
+
+ inline fs_status<butl::rmfile_status>
+ rmfile (const path& f) {return rmfile (f, f);}
+
+ // Similar to rmfile() but for directories.
+ //
+ template <typename T>
+ fs_status<butl::rmdir_status>
+ rmdir (const dir_path&, const T& target);
+
+ inline fs_status<butl::rmdir_status>
+ rmdir (const dir_path& d) {return rmdir (d, d);}
+
+ // Note that this function returns not_empty if we try to remove
+ // a working directory.
+ //
+ fs_status<butl::rmdir_status>
+ rmdir_r (const dir_path&);
+
+ // Return the src/out directory corresponding to the given out/src. The
+ // passed directory should be a sub-directory of out/src_root.
+ //
+ dir_path
+ src_out (const dir_path& out, scope&);
+
+ dir_path
+ src_out (const dir_path& out,
+ const dir_path& out_root, const dir_path& src_root);
+
+ dir_path
+ out_src (const dir_path& src, scope&);
+
+ dir_path
+ out_src (const dir_path& src,
+ const dir_path& out_root, const dir_path& src_root);
+
+ // If possible and beneficial, translate an absolute, normalized path
+ // into relative to the relative_base directory, which is normally
+ // work. Note that if the passed path is the same as relative_base,
+ // then this function returns empty path.
+ //
+ template <typename K>
+ basic_path<char, K>
+ relative (const basic_path<char, K>&);
+
+ // By default this points to work. Setting this to something else
+ // should only be done in tightly controlled, non-parallel
+ // situations (see dump). If base is empty, then relative()
+ // returns the original path.
+ //
+ extern const dir_path* relative_base;
+
+ // In addition to calling relative(), this function also uses shorter
+ // notations such as '~/'.
+ //
+ std::string
+ diag_relative (const path&);
+
+ // As above but also adds trailing '/'. If the path is the same as
+ // base, returns "./" if current is true and empty string otherwise.
+ //
+ std::string
+ diag_relative (const dir_path&, bool current = true);
+
+ // Action phrases, e.g., "configure update exe{foo}", "updating exe{foo}",
+ // and "updating exe{foo} is configured".
+ //
+ class target;
+
+ std::string
+ diag_do (const action&, const target&);
+
+ std::string
+ diag_doing (const action&, const target&);
+
+ std::string
+ diag_done (const action&, const target&);
+}
+
+#include <build2/context.txx>
+
+#endif // BUILD2_CONTEXT
diff --git a/build2/context.cxx b/build2/context.cxx
new file mode 100644
index 0000000..34408e9
--- /dev/null
+++ b/build2/context.cxx
@@ -0,0 +1,391 @@
+// file : build2/context.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/context>
+
+#include <ostream>
+#include <sstream>
+#include <cassert>
+#include <system_error>
+
+#include <build2/scope>
+#include <build2/target>
+#include <build2/rule>
+#include <build2/diagnostics>
+
+using namespace std;
+using namespace butl;
+
+namespace build2
+{
+ dir_path work;
+ dir_path home;
+
+ string_pool path_pool;
+ string_pool extension_pool;
+ string_pool project_name_pool;
+
+ const meta_operation_info* current_mif;
+ const operation_info* current_inner_oif;
+ const operation_info* current_outer_oif;
+ execution_mode current_mode;
+ uint64_t dependency_count;
+
+ void
+ reset ()
+ {
+ path_pool.clear ();
+ extension_pool.clear ();
+ project_name_pool.clear ();
+
+ targets.clear ();
+ scopes.clear ();
+ var_pool.clear ();
+
+ // Reset meta/operation tables. Note that the order should match
+ // the id constants in <build2/operation>.
+ //
+ meta_operation_table.clear ();
+ meta_operation_table.insert ("perform");
+ meta_operation_table.insert ("configure");
+ meta_operation_table.insert ("disfigure");
+ meta_operation_table.insert ("dist");
+
+ operation_table.clear ();
+ operation_table.insert ("default");
+ operation_table.insert ("update");
+ operation_table.insert ("clean");
+ operation_table.insert ("test");
+ operation_table.insert ("install");
+
+ // Enter builtin variables.
+ //
+ {
+ auto& v (var_pool);
+
+ v.find ("work", dir_path_type);
+ v.find ("home", dir_path_type);
+
+ v.find ("src_root", dir_path_type);
+ v.find ("out_root", dir_path_type);
+ v.find ("src_base", dir_path_type);
+ v.find ("out_base", dir_path_type);
+
+ v.find ("project", string_type);
+ v.find ("amalgamation", dir_path_type);
+
+ // Shouldn't be typed since the value requires pre-processing.
+ //
+ v.find ("subprojects", nullptr, '=');
+
+ v.find ("extension", string_type);
+ }
+
+ // Create global scope. For Win32 this is not a "real" root path.
+ // On POSIX, however, this is a real path. See the comment in
+ // <build2/path-map> for details.
+ //
+ global_scope = scopes.insert (
+ dir_path ("/"), nullptr, true, false)->second;
+
+ global_scope->assign ("work") = work;
+ global_scope->assign ("home") = home;
+
+ // Register builtin target types.
+ //
+ {
+ target_type_map& t (global_scope->target_types);
+
+ t.insert<file> ();
+ t.insert<alias> ();
+ t.insert<dir> ();
+ t.insert<fsdir> ();
+ t.insert<doc> ();
+ t.insert<man> ();
+ t.insert<man1> ();
+ }
+
+ // Register builtin rules.
+ //
+ {
+ rule_map& r (global_scope->rules);
+
+ r.insert<alias> (perform_id, 0, "alias", alias_rule::instance);
+
+ r.insert<fsdir> (perform_update_id, "fsdir", fsdir_rule::instance);
+ r.insert<fsdir> (perform_clean_id, "fsdir", fsdir_rule::instance);
+
+ r.insert<file> (perform_update_id, "file", file_rule::instance);
+ r.insert<file> (perform_clean_id, "file", file_rule::instance);
+ }
+ }
+
+ fs_status<mkdir_status>
+ mkdir (const dir_path& d)
+ {
+ // We don't want to print the command if the directory already
+ // exists. This makes the below code a bit ugly.
+ //
+ mkdir_status ms;
+
+ try
+ {
+ ms = try_mkdir (d);
+ }
+ catch (const system_error& e)
+ {
+ if (verb)
+ text << "mkdir " << d;
+
+ fail << "unable to create directory " << d << ": " << e.what ();
+ }
+
+ if (ms == mkdir_status::success)
+ {
+ if (verb)
+ text << "mkdir " << d;
+ }
+
+ return ms;
+ }
+
+ fs_status<mkdir_status>
+ mkdir_p (const dir_path& d)
+ {
+ // We don't want to print the command if the directory already
+ // exists. This makes the below code a bit ugly.
+ //
+ mkdir_status ms;
+
+ try
+ {
+ ms = try_mkdir_p (d);
+ }
+ catch (const system_error& e)
+ {
+ if (verb)
+ text << "mkdir -p " << d;
+
+ fail << "unable to create directory " << d << ": " << e.what ();
+ }
+
+ if (ms == mkdir_status::success)
+ {
+ if (verb)
+ text << "mkdir -p " << d;
+ }
+
+ return ms;
+ }
+
+ fs_status<butl::rmdir_status>
+ rmdir_r (const dir_path& d)
+ {
+ using namespace butl;
+
+ if (work.sub (d)) // Don't try to remove working directory.
+ return rmdir_status::not_empty;
+
+ if (!dir_exists (d))
+ return rmdir_status::not_exist;
+
+ if (verb)
+ text << "rmdir -r " << d;
+
+ try
+ {
+ butl::rmdir_r (d);
+ }
+ catch (const std::system_error& e)
+ {
+ fail << "unable to remove directory " << d << ": " << e.what ();
+ }
+
+ return rmdir_status::success;
+ }
+
+ dir_path
+ src_out (const dir_path& out, scope& s)
+ {
+ scope& rs (*s.root_scope ());
+ 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.out_path (), rs.src_path ());
+ }
+
+ dir_path
+ src_out (const dir_path& o,
+ const dir_path& out_root, const dir_path& src_root)
+ {
+ assert (o.sub (out_root));
+ return src_root / o.leaf (out_root);
+ }
+
+ dir_path
+ out_src (const dir_path& s,
+ const dir_path& out_root, const dir_path& src_root)
+ {
+ assert (s.sub (src_root));
+ return out_root / s.leaf (src_root);
+ }
+
+ // relative()
+ //
+ const dir_path* relative_base = &work;
+
+ string
+ diag_relative (const path& p)
+ {
+ const path& b (*relative_base);
+
+ if (p.absolute ())
+ {
+ if (p == b)
+ return ".";
+
+#ifndef _WIN32
+ if (p == home)
+ return "~";
+#endif
+
+ path rb (relative (p));
+
+#ifndef _WIN32
+ if (rb.relative ())
+ {
+ // See if the original path with the ~/ shortcut is better
+ // that the relative to base.
+ //
+ if (p.sub (home))
+ {
+ path rh (p.leaf (home));
+ if (rb.string ().size () > rh.string ().size () + 2) // 2 for '~/'
+ return "~/" + rh.string ();
+ }
+ }
+ else if (rb.sub (home))
+ return "~/" + rb.leaf (home).string ();
+#endif
+
+ return rb.string ();
+ }
+
+ return p.string ();
+ }
+
+ string
+ diag_relative (const dir_path& d, bool cur)
+ {
+ string r (diag_relative (static_cast<const path&> (d)));
+
+ // Translate "." to empty.
+ //
+ if (!cur && d.absolute () && r == ".")
+ r.clear ();
+
+ // Add trailing '/'.
+ //
+ if (!r.empty () && !dir_path::traits::is_separator (r.back ()))
+ r += '/';
+
+ return r;
+ }
+
+ // diag_do(), etc.
+ //
+ string
+ diag_do (const action&, const target& t)
+ {
+ const meta_operation_info& m (*current_mif);
+ const operation_info& io (*current_inner_oif);
+ const operation_info* oo (current_outer_oif);
+
+ ostringstream os;
+
+ // perform(update(x)) -> "update x"
+ // configure(update(x)) -> "configure updating x"
+ //
+ if (m.name_do.empty ())
+ os << io.name_do << ' ';
+ else
+ {
+ os << m.name_do << ' ';
+
+ if (!io.name_doing.empty ())
+ os << io.name_doing << ' ';
+ }
+
+ if (oo != nullptr)
+ os << "(for " << oo->name << ") ";
+
+ os << t;
+ return os.str ();
+ }
+
+ string
+ diag_doing (const action&, const target& t)
+ {
+ const meta_operation_info& m (*current_mif);
+ const operation_info& io (*current_inner_oif);
+ const operation_info* oo (current_outer_oif);
+
+ ostringstream os;
+
+ // perform(update(x)) -> "updating x"
+ // configure(update(x)) -> "configuring updating x"
+ //
+ if (!m.name_doing.empty ())
+ os << m.name_doing << ' ';
+
+ if (!io.name_doing.empty ())
+ os << io.name_doing << ' ';
+
+ if (oo != nullptr)
+ os << "(for " << oo->name << ") ";
+
+ os << t;
+ return os.str ();
+ }
+
+ string
+ diag_done (const action&, const target& t)
+ {
+ const meta_operation_info& m (*current_mif);
+ const operation_info& io (*current_inner_oif);
+ const operation_info* oo (current_outer_oif);
+
+ ostringstream os;
+
+ // perform(update(x)) -> "x is up to date"
+ // configure(update(x)) -> "updating x is configured"
+ //
+ if (m.name_done.empty ())
+ {
+ os << t;
+
+ if (!io.name_done.empty ())
+ os << " " << io.name_done;
+
+ if (oo != nullptr)
+ os << "(for " << oo->name << ") ";
+ }
+ else
+ {
+ if (!io.name_doing.empty ())
+ os << io.name_doing << ' ';
+
+ if (oo != nullptr)
+ os << "(for " << oo->name << ") ";
+
+ os << t << " " << m.name_done;
+ }
+
+ return os.str ();
+ }
+}
diff --git a/build2/context.txx b/build2/context.txx
new file mode 100644
index 0000000..cc37a97
--- /dev/null
+++ b/build2/context.txx
@@ -0,0 +1,141 @@
+// file : build2/context.txx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <system_error>
+
+#include <build2/diagnostics>
+
+namespace build2
+{
+ template <typename T>
+ fs_status<butl::rmfile_status>
+ rmfile (const path& f, const T& t)
+ {
+ using namespace butl;
+
+ // We don't want to print the command if we couldn't remove the
+ // file because it does not exist (just like we don't print the
+ // update command if the file is up to date). This makes the
+ // below code a bit ugly.
+ //
+ rmfile_status rs;
+
+ try
+ {
+ rs = try_rmfile (f);
+ }
+ catch (const std::system_error& e)
+ {
+ if (verb >= 2)
+ text << "rm " << f;
+ else if (verb)
+ text << "rm " << t;
+
+ fail << "unable to remove file " << f << ": " << e.what ();
+ }
+
+ if (rs == rmfile_status::success)
+ {
+ if (verb >= 2)
+ text << "rm " << f;
+ else if (verb)
+ text << "rm " << t;
+ }
+
+ return rs;
+ }
+
+ template <typename T>
+ fs_status<butl::rmdir_status>
+ rmdir (const dir_path& d, const T& t)
+ {
+ using namespace butl;
+
+ bool w (work.sub (d)); // Don't try to remove working directory.
+ rmdir_status rs;
+
+ // We don't want to print the command if we couldn't remove the
+ // directory because it does not exist (just like we don't print
+ // mkdir if it already exists) or if it is not empty. This makes
+ // the below code a bit ugly.
+ //
+ try
+ {
+ rs = !w ? try_rmdir (d) : rmdir_status::not_empty;
+ }
+ catch (const std::system_error& e)
+ {
+ if (verb >= 2)
+ text << "rmdir " << d;
+ else if (verb)
+ text << "rmdir " << t;
+
+ fail << "unable to remove directory " << d << ": " << e.what ();
+ }
+
+ switch (rs)
+ {
+ case rmdir_status::success:
+ {
+ if (verb >= 2)
+ text << "rmdir " << d;
+ else if (verb)
+ text << "rmdir " << t;
+
+ break;
+ }
+ case rmdir_status::not_empty:
+ {
+ if (verb >= 2)
+ text << "directory " << d << " is "
+ << (w ? "current working directory" : "not empty")
+ << ", not removing";
+
+ break;
+ }
+ case rmdir_status::not_exist:
+ break;
+ }
+
+ return rs;
+ }
+
+ template <typename K>
+ basic_path<char, K>
+ relative (const basic_path<char, K>& p)
+ {
+ typedef basic_path<char, K> path;
+
+ const dir_path& b (*relative_base);
+
+ if (b.empty ())
+ return p;
+
+ if (p.sub (b))
+ return p.leaf (b);
+
+ // If base is a sub-path of {src,out}_root and this path is also a
+ // sub-path of it, then use '..' to form a relative path.
+ //
+ // Don't think this is a good heuristic. For example, why shouldn't
+ // we display paths from imported projects as relative if they are
+ // more readable than absolute?
+ //
+ /*
+ if ((work.sub (src_root) && p.sub (src_root)) ||
+ (work.sub (out_root) && p.sub (out_root)))
+ return p.relative (work);
+ */
+
+ if (p.root_directory () == b.root_directory ())
+ {
+ path r (p.relative (b));
+
+ if (r.string ().size () < p.string ().size ())
+ return r;
+ }
+
+ return p;
+ }
+}
diff --git a/build2/cxx/compile b/build2/cxx/compile
new file mode 100644
index 0000000..42e0f2e
--- /dev/null
+++ b/build2/cxx/compile
@@ -0,0 +1,32 @@
+// file : build2/cxx/compile -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_CXX_COMPILE
+#define BUILD2_CXX_COMPILE
+
+#include <build2/types>
+#include <build2/rule>
+
+namespace build2
+{
+ namespace cxx
+ {
+ class compile: public rule
+ {
+ public:
+ virtual match_result
+ match (action, target&, const std::string& hint) const;
+
+ virtual recipe
+ apply (action, target&, const match_result&) const;
+
+ static target_state
+ perform_update (action, target&);
+
+ static compile instance;
+ };
+ }
+}
+
+#endif // BUILD2_CXX_COMPILE
diff --git a/build2/cxx/compile.cxx b/build2/cxx/compile.cxx
new file mode 100644
index 0000000..bc332b4
--- /dev/null
+++ b/build2/cxx/compile.cxx
@@ -0,0 +1,794 @@
+// file : build2/cxx/compile.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/cxx/compile>
+
+#include <map>
+#include <string>
+#include <cstddef> // size_t
+#include <cstdlib> // exit()
+#include <utility> // move()
+
+#include <butl/process>
+#include <butl/utility> // reverse_iterate
+#include <butl/fdstream>
+#include <butl/path-map>
+
+#include <build2/types>
+#include <build2/scope>
+#include <build2/variable>
+#include <build2/algorithm>
+#include <build2/diagnostics>
+#include <build2/context>
+
+#include <build2/bin/target>
+#include <build2/cxx/target>
+
+#include <build2/cxx/utility>
+#include <build2/cxx/link>
+
+using namespace std;
+using namespace butl;
+
+namespace build2
+{
+ namespace cxx
+ {
+ using namespace bin;
+
+ match_result compile::
+ match (action a, target& t, const string&) const
+ {
+ tracer trace ("cxx::compile::match");
+
+ // @@ TODO:
+ //
+ // - check prerequisites: single source file
+ // - if path already assigned, verify extension?
+ //
+
+ // See if we have a C++ source file. Iterate in reverse so that
+ // a source file specified for an obj*{} member overrides the one
+ // specified for the group. Also "see through" groups.
+ //
+ for (prerequisite_member p: reverse_group_prerequisite_members (a, t))
+ {
+ if (p.is_a<cxx> ())
+ return p;
+ }
+
+ level4 ([&]{trace << "no c++ source file for target " << t;});
+ return nullptr;
+ }
+
+ static void
+ inject_prerequisites (action, target&, cxx&, scope&);
+
+ recipe compile::
+ apply (action a, target& xt, const match_result& mr) const
+ {
+ path_target& t (static_cast<path_target&> (xt));
+
+ // Derive file name from target name.
+ //
+ if (t.path ().empty ())
+ t.derive_path ("o", nullptr, (t.is_a<objso> () ? "-so" : nullptr));
+
+ // Inject dependency on the output directory.
+ //
+ inject_parent_fsdir (a, t);
+
+ // Search and match all the existing prerequisites. The injection
+ // code (below) takes care of the ones it is adding.
+ //
+ // When cleaning, ignore prerequisites that are not in the same
+ // or a subdirectory of our strong amalgamation.
+ //
+ const dir_path* amlg (
+ a.operation () != clean_id
+ ? nullptr
+ : &t.strong_scope ().out_path ());
+
+ link::search_paths_cache lib_paths; // Extract lazily.
+
+ for (prerequisite_member p: group_prerequisite_members (a, t))
+ {
+ // A dependency on a library is there so that we can get its
+ // cxx.export.poptions. In particular, making sure it is
+ // executed before us will only restrict parallelism. But we
+ // do need to pre-match it in order to get its
+ // prerequisite_targets populated. This is the "library
+ // meta-information protocol". See also append_lib_options()
+ // above.
+ //
+ if (p.is_a<lib> () || p.is_a<liba> () || p.is_a<libso> ())
+ {
+ if (a.operation () == update_id)
+ {
+ // Handle imported libraries. We know that for such libraries
+ // we don't need to do match() in order to get options (if
+ // any, they would be set by search_library()).
+ //
+ if (p.proj () == nullptr ||
+ link::search_library (lib_paths, p.prerequisite) == nullptr)
+ {
+ match_only (a, p.search ());
+ }
+ }
+
+ continue;
+ }
+
+ target& pt (p.search ());
+
+ if (a.operation () == clean_id && !pt.dir.sub (*amlg))
+ continue;
+
+ build2::match (a, pt);
+ t.prerequisite_targets.push_back (&pt);
+ }
+
+ // Inject additional prerequisites. We only do it when
+ // performing update since chances are we will have to
+ // update some of our prerequisites in the process (auto-
+ // generated source code).
+ //
+ if (a == perform_update_id)
+ {
+ // The cached prerequisite target should be the same as what
+ // is in t.prerequisite_targets since we used standard
+ // search() and match() above.
+ //
+ // @@ Ugly.
+ //
+ cxx& st (
+ dynamic_cast<cxx&> (
+ mr.target != nullptr ? *mr.target : *mr.prerequisite->target));
+ inject_prerequisites (a, t, st, mr.prerequisite->scope);
+ }
+
+ switch (a)
+ {
+ case perform_update_id: return &perform_update;
+ case perform_clean_id: return &perform_clean;
+ default: return noop_recipe; // Configure update.
+ }
+ }
+
+ // Reverse-lookup target type from extension.
+ //
+ static const target_type*
+ map_extension (scope& s, const string& n, const string& e)
+ {
+ // We will just have to try all of the possible ones, in the
+ // "most likely to match" order.
+ //
+ const variable& var (var_pool.find ("extension"));
+
+ auto test = [&s, &n, &e, &var] (const target_type& tt)
+ -> const target_type*
+ {
+ if (auto l = s.lookup (tt, n, var))
+ if (as<string> (*l) == e)
+ return &tt;
+
+ return nullptr;
+ };
+
+ if (auto r = test (hxx::static_type)) return r;
+ if (auto r = test (h::static_type)) return r;
+ if (auto r = test (ixx::static_type)) return r;
+ if (auto r = test (txx::static_type)) return r;
+ if (auto r = test (cxx::static_type)) return r;
+ if (auto r = test (c::static_type)) return r;
+
+ return nullptr;
+ }
+
+ // Mapping of include prefixes (e.g., foo in <foo/bar>) for auto-
+ // generated headers to directories where they will be generated.
+ //
+ // We are using a prefix map of directories (dir_path_map) instead
+ // of just a map in order also cover sub-paths (e.g., <foo/more/bar>
+ // if we continue with the example). Specifically, we need to make
+ // sure we don't treat foobar as a sub-directory of foo.
+ //
+ // @@ The keys should be canonicalized.
+ //
+ using prefix_map = dir_path_map<dir_path>;
+
+ static void
+ append_prefixes (prefix_map& m, target& t, const char* var)
+ {
+ tracer trace ("cxx::append_prefixes");
+
+ // If this target does not belong to any project (e.g, an
+ // "imported as installed" library), then it can't possibly
+ // generate any headers for us.
+ //
+ scope* rs (t.base_scope ().root_scope ());
+ if (rs == nullptr)
+ return;
+
+ const dir_path& out_base (t.dir);
+ const dir_path& out_root (rs->out_path ());
+
+ if (auto l = t[var])
+ {
+ const auto& v (as<strings> (*l));
+
+ for (auto i (v.begin ()), e (v.end ()); i != e; ++i)
+ {
+ // -I can either be in the "-Ifoo" or "-I foo" form.
+ //
+ dir_path d;
+ if (*i == "-I")
+ {
+ if (++i == e)
+ break; // Let the compiler complain.
+
+ d = dir_path (*i);
+ }
+ else if (i->compare (0, 2, "-I") == 0)
+ d = dir_path (*i, 2, string::npos);
+ else
+ continue;
+
+ level6 ([&]{trace << "-I '" << d << "'";});
+
+ // If we are relative or not inside our project root, then
+ // ignore.
+ //
+ if (d.relative () || !d.sub (out_root))
+ continue;
+
+ // If the target directory is a sub-directory of the include
+ // directory, then the prefix is the difference between the
+ // two. Otherwise, leave it empty.
+ //
+ // The idea here is to make this "canonical" setup work auto-
+ // magically:
+ //
+ // 1. We include all files with a prefix, e.g., <foo/bar>.
+ // 2. The library target is in the foo/ sub-directory, e.g.,
+ // /tmp/foo/.
+ // 3. The poptions variable contains -I/tmp.
+ //
+ dir_path p (out_base.sub (d) ? out_base.leaf (d) : dir_path ());
+
+ auto j (m.find (p));
+
+ if (j != m.end ())
+ {
+ if (j->second != d)
+ {
+ // We used to reject duplicates but it seems this can
+ // be reasonably expected to work according to the order
+ // of the -I options.
+ //
+ if (verb >= 4)
+ trace << "overriding dependency prefix '" << p << "'\n"
+ << " old mapping to " << j->second << "\n"
+ << " new mapping to " << d;
+
+ j->second = d;
+ }
+ }
+ else
+ {
+ level6 ([&]{trace << "'" << p << "' = '" << d << "'";});
+ m.emplace (move (p), move (d));
+ }
+ }
+ }
+ }
+
+ // Append library prefixes based on the cxx.export.poptions variables
+ // recursively, prerequisite libraries first.
+ //
+ static void
+ append_lib_prefixes (prefix_map& m, target& l)
+ {
+ for (target* t: l.prerequisite_targets)
+ {
+ if (t == nullptr)
+ continue;
+
+ if (t->is_a<lib> () || t->is_a<liba> () || t->is_a<libso> ())
+ append_lib_prefixes (m, *t);
+ }
+
+ append_prefixes (m, l, "cxx.export.poptions");
+ }
+
+ static prefix_map
+ build_prefix_map (target& t)
+ {
+ prefix_map m;
+
+ // First process the include directories from prerequisite
+ // libraries. Note that here we don't need to see group
+ // members (see apply()).
+ //
+ for (prerequisite& p: group_prerequisites (t))
+ {
+ target& pt (*p.target); // Already searched and matched.
+
+ if (pt.is_a<lib> () || pt.is_a<liba> () || pt.is_a<libso> ())
+ append_lib_prefixes (m, pt);
+ }
+
+ // Then process our own.
+ //
+ append_prefixes (m, t, "cxx.poptions");
+
+ return m;
+ }
+
+ // Return the next make prerequisite starting from the specified
+ // position and update position to point to the start of the
+ // following prerequisite or l.size() if there are none left.
+ //
+ static string
+ next (const string& l, size_t& p)
+ {
+ size_t n (l.size ());
+
+ // Skip leading spaces.
+ //
+ for (; p != n && l[p] == ' '; p++) ;
+
+ // Lines containing multiple prerequisites are 80 characters max.
+ //
+ string r;
+ r.reserve (n);
+
+ // Scan the next prerequisite while watching out for escape sequences.
+ //
+ for (; p != n && l[p] != ' '; p++)
+ {
+ char c (l[p]);
+
+ if (c == '\\')
+ c = l[++p];
+
+ r += c;
+ }
+
+ // Skip trailing spaces.
+ //
+ for (; p != n && l[p] == ' '; p++) ;
+
+ // Skip final '\'.
+ //
+ if (p == n - 1 && l[p] == '\\')
+ p++;
+
+ return r;
+ }
+
+ static void
+ inject_prerequisites (action a, target& t, cxx& s, scope& ds)
+ {
+ tracer trace ("cxx::compile::inject_prerequisites");
+
+ scope& rs (t.root_scope ());
+ const string& cxx (as<string> (*rs["config.cxx"]));
+
+ cstrings args {cxx.c_str ()};
+
+ // Add cxx.export.poptions from prerequisite libraries. Note
+ // that here we don't need to see group members (see apply()).
+ //
+ for (prerequisite& p: group_prerequisites (t))
+ {
+ target& pt (*p.target); // Already searched and matched.
+
+ if (pt.is_a<lib> () || pt.is_a<liba> () || pt.is_a<libso> ())
+ append_lib_options (args, pt, "cxx.export.poptions");
+ }
+
+ append_options (args, t, "cxx.poptions");
+
+ // @@ Some C++ options (e.g., -std, -m) affect the preprocessor.
+ // Or maybe they are not C++ options? Common options?
+ //
+ append_options (args, t, "cxx.coptions");
+
+ string std; // Storage.
+ append_std (args, t, std);
+
+ if (t.is_a<objso> ())
+ args.push_back ("-fPIC");
+
+ args.push_back ("-M"); // Note: -MM -MG skips missing <>-included.
+ args.push_back ("-MG"); // Treat missing headers as generated.
+ args.push_back ("-MQ"); // Quoted target name.
+ args.push_back ("*"); // Old versions can't handle empty target name.
+
+ // We are using absolute source file path in order to get absolute
+ // paths in the result. Any relative paths in the result are non-
+ // existent, potentially auto-generated headers.
+ //
+ // @@ We will also have to use absolute -I paths to guarantee
+ // that. Or just detect relative paths and error out?
+ //
+ args.push_back (s.path ().string ().c_str ());
+ args.push_back (nullptr);
+
+ level6 ([&]{trace << "target: " << t;});
+
+ // Build the prefix map lazily only if we have non-existent files.
+ // Also reuse it over restarts since it doesn't change.
+ //
+ prefix_map pm;
+
+ // If any prerequisites that we have extracted changed, then we
+ // have to redo the whole thing. The reason for this is auto-
+ // generated headers: the updated header may now include a yet-
+ // non-existent header. Unless we discover this and generate it
+ // (which, BTW, will trigger another restart since that header,
+ // in turn, can also include auto-generated headers), we will
+ // end up with an error during compilation proper.
+ //
+ // One complication with this restart logic is that we will see
+ // a "prefix" of prerequisites that we have already processed
+ // (i.e., they are already in our prerequisite_targets list) and
+ // we don't want to keep redoing this over and over again. One
+ // thing to note, however, is that the prefix that we have seen
+ // on the previous run must appear exactly the same in the
+ // subsequent run. The reason for this is that none of the files
+ // that it can possibly be based on have changed and thus it
+ // should be exactly the same. To put it another way, the
+ // presence or absence of a file in the dependency output can
+ // only depend on the previous files (assuming the compiler
+ // outputs them as it encounters them and it is hard to think
+ // of a reason why would someone do otherwise). And we have
+ // already made sure that all those files are up to date. And
+ // here is the way we are going to exploit this: we are going
+ // to keep track of how many prerequisites we have processed so
+ // far and on restart skip right to the next one.
+ //
+ // Also, before we do all that, make sure the source file itself
+ // if up to date.
+ //
+ execute_direct (a, s);
+
+ size_t skip_count (0);
+ for (bool restart (true); restart; )
+ {
+ restart = false;
+
+ if (verb >= 3)
+ print_process (args);
+
+ try
+ {
+ process pr (args.data (), 0, -1); // Open pipe to stdout.
+ ifdstream is (pr.in_ofd);
+
+ size_t skip (skip_count);
+ for (bool first (true), second (true); !(restart || is.eof ()); )
+ {
+ string l;
+ getline (is, l);
+
+ if (is.fail () && !is.eof ())
+ fail << "error reading C++ compiler -M output";
+
+ size_t pos (0);
+
+ if (first)
+ {
+ // Empty output should mean the wait() call below will return
+ // false.
+ //
+ if (l.empty ())
+ break;
+
+ assert (l[0] == '*' && l[1] == ':' && l[2] == ' ');
+
+ first = false;
+
+ // While normally we would have the source file on the
+ // first line, if too long, it will be moved to the next
+ // line and all we will have on this line is "*: \".
+ //
+ if (l.size () == 4 && l[3] == '\\')
+ continue;
+ else
+ pos = 3; // Skip "*: ".
+
+ // Fall through to the 'second' block.
+ }
+
+ if (second)
+ {
+ second = false;
+ next (l, pos); // Skip the source file.
+ }
+
+ // If things go wrong (and they often do in this area), give
+ // the user a bit extra context.
+ //
+ auto g (
+ make_exception_guard (
+ [&s]()
+ {
+ info << "while extracting dependencies from " << s;
+ }));
+
+ while (pos != l.size ())
+ {
+ string fs (next (l, pos));
+
+ // Skip until where we left off.
+ //
+ if (skip != 0)
+ {
+ skip--;
+ continue;
+ }
+
+ path f (move (fs));
+ f.normalize ();
+
+ if (!f.absolute ())
+ {
+ // This is probably as often an error as an auto-generated
+ // file, so trace at level 4.
+ //
+ level4 ([&]{trace << "non-existent header '" << f << "'";});
+
+ // If we already did it and build_prefix_map() returned empty,
+ // then we would have failed below.
+ //
+ if (pm.empty ())
+ pm = build_prefix_map (t);
+
+ // First try the whole file. Then just the directory.
+ //
+ // @@ Has to be a separate map since the prefix can be
+ // the same as the file name.
+ //
+ // auto i (pm.find (f));
+
+ // Find the most qualified prefix of which we are a
+ // sub-path.
+ //
+ auto i (pm.end ());
+
+ if (!pm.empty ())
+ {
+ const dir_path& d (f.directory ());
+ i = pm.upper_bound (d);
+
+ // Get the greatest less than, if any. We might
+ // still not be a sub. Note also that we still
+ // have to check the last element is upper_bound()
+ // returned end().
+ //
+ if (i == pm.begin () || !d.sub ((--i)->first))
+ i = pm.end ();
+ }
+
+ if (i == pm.end ())
+ fail << "unable to map presumably auto-generated header '"
+ << f << "' to a project";
+
+ f = i->second / f;
+ }
+
+ level6 ([&]{trace << "injecting " << f;});
+
+ // Split the name into its directory part, the name part, and
+ // extension. Here we can assume the name part is a valid
+ // filesystem name.
+ //
+ // Note that if the file has no extension, we record an empty
+ // extension rather than NULL (which would signify that the
+ // default extension should be added).
+ //
+ dir_path d (f.directory ());
+ string n (f.leaf ().base ().string ());
+ const char* es (f.extension ());
+ const string* e (&extension_pool.find (es != nullptr ? es : ""));
+
+ // Determine the target type.
+ //
+ const target_type* tt (nullptr);
+
+ // See if this directory is part of any project out_root
+ // hierarchy. Note that this will miss all the headers
+ // that come from src_root (so they will be treated as
+ // generic C headers below). Generally, we don't have
+ // the ability to determine that some file belongs to
+ // src_root of some project. But that's not a problem
+ // for our purposes: it is only important for us to
+ // accurately determine target types for headers that
+ // could be auto-generated.
+ //
+ scope& b (scopes.find (d));
+ if (b.root_scope () != nullptr)
+ tt = map_extension (b, n, *e);
+
+ // If it is outside any project, or the project doesn't have
+ // such an extension, assume it is a plain old C header.
+ //
+ if (tt == nullptr)
+ tt = &h::static_type;
+
+ // Find or insert target.
+ //
+ path_target& pt (
+ static_cast<path_target&> (search (*tt, d, n, e, &ds)));
+
+ // Assign path.
+ //
+ if (pt.path ().empty ())
+ pt.path (move (f));
+
+ // Match to a rule.
+ //
+ build2::match (a, pt);
+
+ // Update it.
+ //
+ // There would normally be a lot of headers for every source
+ // file (think all the system headers) and this can get
+ // expensive. At the same time, most of these headers are
+ // existing files that we will never be updated (again,
+ // system headers, for example) and the rule that will match
+ // them is fallback file_rule. That rule has an optimization
+ // in that it returns noop_recipe (which causes the target
+ // state to be automatically set to unchanged) if the file
+ // is known to be up to date.
+ //
+ if (pt.state () != target_state::unchanged)
+ {
+ // We only want to restart if our call to execute() actually
+ // caused an update. In particular, the target could already
+ // have been in target_state::changed because of a dependency
+ // extraction run for some other source file.
+ //
+ target_state os (pt.state ());
+ target_state ns (execute_direct (a, pt));
+
+ if (ns != os && ns != target_state::unchanged)
+ {
+ level6 ([&]{trace << "updated " << pt << ", restarting";});
+ restart = true;
+ }
+ }
+
+ // Add to our prerequisite target list.
+ //
+ t.prerequisite_targets.push_back (&pt);
+ skip_count++;
+ }
+ }
+
+ // We may not have read all the output (e.g., due to a restart),
+ // so close the file descriptor before waiting to avoid blocking
+ // the other end.
+ //
+ is.close ();
+
+ // We assume the child process issued some diagnostics.
+ //
+ if (!pr.wait ())
+ {
+ // In case of a restarts, we closed our end of the pipe early
+ // which might have caused the other end to fail. So far we
+ // experienced this on Fedora 23 with GCC 5.3.1 and there were
+ // no diagnostics issued, just the non-zero exit status. If we
+ // do get diagnostics, then we will have to read and discard the
+ // output until eof.
+ //
+ if (!restart)
+ throw failed ();
+ }
+ }
+ catch (const process_error& e)
+ {
+ error << "unable to execute " << args[0] << ": " << e.what ();
+
+ // In a multi-threaded program that fork()'ed but did not exec(),
+ // it is unwise to try to do any kind of cleanup (like unwinding
+ // the stack and running destructors).
+ //
+ if (e.child ())
+ exit (1);
+
+ throw failed ();
+ }
+ }
+ }
+
+ target_state compile::
+ perform_update (action a, target& xt)
+ {
+ path_target& t (static_cast<path_target&> (xt));
+ cxx* s (execute_prerequisites<cxx> (a, t, t.mtime ()));
+
+ if (s == nullptr)
+ return target_state::unchanged;
+
+ // Translate paths to relative (to working directory) ones. This
+ // results in easier to read diagnostics.
+ //
+ path relo (relative (t.path ()));
+ path rels (relative (s->path ()));
+
+ scope& rs (t.root_scope ());
+ const string& cxx (as<string> (*rs["config.cxx"]));
+
+ cstrings args {cxx.c_str ()};
+
+ // Add cxx.export.poptions from prerequisite libraries. Note that
+ // here we don't need to see group members (see apply()).
+ //
+ for (prerequisite& p: group_prerequisites (t))
+ {
+ target& pt (*p.target); // Already searched and matched.
+
+ if (pt.is_a<lib> () || pt.is_a<liba> () || pt.is_a<libso> ())
+ append_lib_options (args, pt, "cxx.export.poptions");
+ }
+
+ append_options (args, t, "cxx.poptions");
+ append_options (args, t, "cxx.coptions");
+
+ string std; // Storage.
+ append_std (args, t, std);
+
+ if (t.is_a<objso> ())
+ args.push_back ("-fPIC");
+
+ args.push_back ("-o");
+ args.push_back (relo.string ().c_str ());
+
+ args.push_back ("-c");
+ args.push_back (rels.string ().c_str ());
+
+ args.push_back (nullptr);
+
+ if (verb >= 2)
+ print_process (args);
+ else if (verb)
+ text << "c++ " << *s;
+
+ try
+ {
+ process pr (args.data ());
+
+ if (!pr.wait ())
+ throw failed ();
+
+ // Should we go to the filesystem and get the new mtime? We
+ // know the file has been modified, so instead just use the
+ // current clock time. It has the advantage of having the
+ // subseconds precision.
+ //
+ t.mtime (system_clock::now ());
+ return target_state::changed;
+ }
+ catch (const process_error& e)
+ {
+ error << "unable to execute " << args[0] << ": " << e.what ();
+
+ // In a multi-threaded program that fork()'ed but did not exec(),
+ // it is unwise to try to do any kind of cleanup (like unwinding
+ // the stack and running destructors).
+ //
+ if (e.child ())
+ exit (1);
+
+ throw failed ();
+ }
+ }
+
+ compile compile::instance;
+ }
+}
diff --git a/build2/cxx/install b/build2/cxx/install
new file mode 100644
index 0000000..154a62a
--- /dev/null
+++ b/build2/cxx/install
@@ -0,0 +1,29 @@
+// file : build2/cxx/install -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_CXX_INSTALL
+#define BUILD2_CXX_INSTALL
+
+#include <build2/types>
+#include <build2/install/rule>
+
+namespace build2
+{
+ namespace cxx
+ {
+ class install: public build2::install::file_rule
+ {
+ public:
+ virtual target*
+ filter (action, target&, prerequisite_member) const;
+
+ virtual match_result
+ match (action, target&, const std::string&) const;
+
+ static install instance;
+ };
+ }
+}
+
+#endif // BUILD2_CXX_INSTALL
diff --git a/build2/cxx/install.cxx b/build2/cxx/install.cxx
new file mode 100644
index 0000000..1bde9ec
--- /dev/null
+++ b/build2/cxx/install.cxx
@@ -0,0 +1,66 @@
+// file : build2/cxx/install.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/cxx/install>
+
+#include <build2/bin/target>
+
+#include <build2/cxx/target>
+#include <build2/cxx/link>
+
+using namespace std;
+
+namespace build2
+{
+ namespace cxx
+ {
+ using namespace bin;
+
+ target* install::
+ filter (action a, target& t, prerequisite_member p) const
+ {
+ if (t.is_a<exe> ())
+ {
+ // Don't install executable's prerequisite headers.
+ //
+ if (p.is_a<hxx> () || p.is_a<ixx> () || p.is_a<txx> () || p.is_a<h> ())
+ return nullptr;
+ }
+
+ // If this is a shared library prerequisite, install it as long as it
+ // is in the same amalgamation as we are.
+ //
+ if ((t.is_a<exe> () || t.is_a<libso> ()) &&
+ (p.is_a<lib> () || p.is_a<libso> ()))
+ {
+ target* pt (&p.search ());
+
+ // If this is the lib{} group, pick a member which we would link.
+ //
+ if (lib* l = pt->is_a<lib> ())
+ pt = &link::link_member (*l, link::link_order (t));
+
+ if (pt->is_a<libso> ()) // Can be liba{}.
+ return pt->in (t.weak_scope ()) ? pt : nullptr;
+ }
+
+ return file_rule::filter (a, t, p);
+ }
+
+ match_result install::
+ match (action a, target& t, const std::string& hint) const
+ {
+ // @@ How do we split the hint between the two?
+ //
+
+ // We only want to handle installation if we are also the
+ // ones building this target. So first run link's match().
+ //
+ match_result r (link::instance.match (a, t, hint));
+ return r ? install::file_rule::match (a, t, "") : r;
+ }
+
+ install install::instance;
+ }
+}
diff --git a/build2/cxx/link b/build2/cxx/link
new file mode 100644
index 0000000..c7f7019
--- /dev/null
+++ b/build2/cxx/link
@@ -0,0 +1,70 @@
+// file : build2/cxx/link -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_CXX_LINK
+#define BUILD2_CXX_LINK
+
+#include <vector>
+
+#include <butl/optional>
+
+#include <build2/types>
+#include <build2/rule>
+
+#include <build2/bin/target>
+
+namespace build2
+{
+ namespace cxx
+ {
+ class link: public rule
+ {
+ public:
+ virtual match_result
+ match (action, target&, const std::string& hint) const;
+
+ virtual recipe
+ apply (action, target&, const match_result&) const;
+
+ static target_state
+ perform_update (action, target&);
+
+ static link instance;
+
+ public:
+ enum class type {e, a, so};
+ enum class order {a, so, a_so, so_a};
+
+ static type
+ link_type (target& t)
+ {
+ return t.is_a<bin::exe> ()
+ ? type::e
+ : (t.is_a<bin::liba> () ? type::a : type::so);
+ }
+
+ static order
+ link_order (target&);
+
+ // Determine the library member (liba or libso) to link.
+ //
+ static target&
+ link_member (bin::lib&, order);
+
+ private:
+ friend class compile;
+
+ using search_paths = std::vector<dir_path>;
+ using search_paths_cache = butl::optional<search_paths>;
+
+ static target*
+ search_library (search_paths_cache&, prerequisite&);
+
+ static search_paths
+ extract_library_paths (scope&);
+ };
+ }
+}
+
+#endif // BUILD2_CXX_LINK
diff --git a/build2/cxx/link.cxx b/build2/cxx/link.cxx
new file mode 100644
index 0000000..96584d9
--- /dev/null
+++ b/build2/cxx/link.cxx
@@ -0,0 +1,875 @@
+// file : build2/cxx/link.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/cxx/link>
+
+#include <vector>
+#include <string>
+#include <cstddef> // size_t
+#include <cstdlib> // exit()
+#include <utility> // move()
+
+#include <butl/process>
+#include <butl/utility> // reverse_iterate
+#include <butl/fdstream>
+#include <butl/optional>
+#include <butl/path-map>
+#include <butl/filesystem>
+
+#include <build2/types>
+#include <build2/scope>
+#include <build2/variable>
+#include <build2/algorithm>
+#include <build2/diagnostics>
+#include <build2/context>
+
+#include <build2/bin/target>
+#include <build2/cxx/target>
+
+#include <build2/cxx/utility>
+
+using namespace std;
+using namespace butl;
+
+namespace build2
+{
+ namespace cxx
+ {
+ using namespace bin;
+
+ link::order link::
+ link_order (target& t)
+ {
+ const char* var;
+
+ switch (link_type (t))
+ {
+ case type::e: var = "bin.exe.lib"; break;
+ case type::a: var = "bin.liba.lib"; break;
+ case type::so: var = "bin.libso.lib"; break;
+ }
+
+ const auto& v (as<strings> (*t[var]));
+ return v[0] == "shared"
+ ? v.size () > 1 && v[1] == "static" ? order::so_a : order::so
+ : v.size () > 1 && v[1] == "shared" ? order::a_so : order::a;
+ }
+
+ target& link::
+ link_member (bin::lib& l, order lo)
+ {
+ bool lso (true);
+ const string& at (as<string> (*l["bin.lib"])); // Available types.
+
+ switch (lo)
+ {
+ case order::a:
+ case order::a_so:
+ lso = false; // Fall through.
+ case order::so:
+ case order::so_a:
+ {
+ if (lso ? at == "static" : at == "shared")
+ {
+ if (lo == order::a_so || lo == order::so_a)
+ lso = !lso;
+ else
+ fail << (lso ? "shared" : "static") << " build of " << l
+ << " is not available";
+ }
+ }
+ }
+
+ target* r (lso ? static_cast<target*> (l.so) : l.a);
+
+ if (r == nullptr)
+ r = &search (lso ? libso::static_type : liba::static_type,
+ prerequisite_key {nullptr, l.key (), nullptr});
+
+ return *r;
+ }
+
+ link::search_paths link::
+ extract_library_paths (scope& bs)
+ {
+ search_paths r;
+ scope& rs (*bs.root_scope ());
+
+ // Extract user-supplied search paths (i.e., -L).
+ //
+ if (auto l = bs["cxx.loptions"])
+ {
+ const auto& v (as<strings> (*l));
+
+ for (auto i (v.begin ()), e (v.end ()); i != e; ++i)
+ {
+ // -L can either be in the "-Lfoo" or "-L foo" form.
+ //
+ dir_path d;
+ if (*i == "-L")
+ {
+ if (++i == e)
+ break; // Let the compiler complain.
+
+ d = dir_path (*i);
+ }
+ else if (i->compare (0, 2, "-L") == 0)
+ d = dir_path (*i, 2, string::npos);
+ else
+ continue;
+
+ // Ignore relative paths. Or maybe we should warn?
+ //
+ if (!d.relative ())
+ r.push_back (move (d));
+ }
+ }
+
+ // Extract system search paths.
+ //
+ cstrings args;
+ string std_storage;
+
+ args.push_back (as<string> (*rs["config.cxx"]).c_str ());
+ append_options (args, bs, "cxx.coptions");
+ append_std (args, bs, std_storage);
+ append_options (args, bs, "cxx.loptions");
+ args.push_back ("-print-search-dirs");
+ args.push_back (nullptr);
+
+ if (verb >= 3)
+ print_process (args);
+
+ string l;
+ try
+ {
+ process pr (args.data (), 0, -1); // Open pipe to stdout.
+ ifdstream is (pr.in_ofd);
+
+ while (!is.eof ())
+ {
+ string s;
+ getline (is, s);
+
+ if (is.fail () && !is.eof ())
+ fail << "error reading C++ compiler -print-search-dirs output";
+
+ if (s.compare (0, 12, "libraries: =") == 0)
+ {
+ l.assign (s, 12, string::npos);
+ break;
+ }
+ }
+
+ is.close (); // Don't block.
+
+ if (!pr.wait ())
+ throw failed ();
+ }
+ catch (const process_error& e)
+ {
+ error << "unable to execute " << args[0] << ": " << e.what ();
+
+ if (e.child ())
+ exit (1);
+
+ throw failed ();
+ }
+
+ if (l.empty ())
+ fail << "unable to extract C++ compiler system library paths";
+
+ // Now the fun part: figuring out which delimiter is used.
+ // Normally it is ':' but on Windows it is ';' (or can be;
+ // who knows for sure). Also note that these paths are
+ // absolute (or should be). So here is what we are going
+ // to do: first look for ';'. If found, then that's the
+ // delimiter. If not found, then there are two cases:
+ // it is either a single Windows path or the delimiter
+ // is ':'. To distinguish these two cases we check if
+ // the path starts with a Windows drive.
+ //
+ char d (';');
+ string::size_type e (l.find (d));
+
+ if (e == string::npos &&
+ (l.size () < 2 || l[0] == '/' || l[1] != ':'))
+ {
+ d = ':';
+ e = l.find (d);
+ }
+
+ // Now chop it up. We already have the position of the
+ // first delimiter (if any).
+ //
+ for (string::size_type b (0);; e = l.find (d, (b = e + 1)))
+ {
+ r.emplace_back (l, b, (e != string::npos ? e - b : e));
+ r.back ().normalize ();
+
+ if (e == string::npos)
+ break;
+ }
+
+ return r;
+ }
+
+ target* link::
+ search_library (search_paths_cache& spc, prerequisite& p)
+ {
+ tracer trace ("cxx::link::search_library");
+
+ // First check the cache.
+ //
+ if (p.target != nullptr)
+ return p.target;
+
+ bool l (p.is_a<lib> ());
+ const string* ext (l ? nullptr : p.ext); // Only for liba/libso.
+
+ // Then figure out what we need to search for.
+ //
+
+ // liba
+ //
+ path an;
+ const string* ae;
+
+ if (l || p.is_a<liba> ())
+ {
+ an = path ("lib" + p.name);
+
+ // Note that p.scope should be the same as the target's for
+ // which we are looking for this library. The idea here is
+ // that we have to use the same "extension configuration" as
+ // the target's.
+ //
+ ae = ext == nullptr
+ ? &liba::static_type.extension (p.key ().tk, p.scope)
+ : ext;
+
+ if (!ae->empty ())
+ {
+ an += '.';
+ an += *ae;
+ }
+ }
+
+ // libso
+ //
+ path sn;
+ const string* se;
+
+ if (l || p.is_a<libso> ())
+ {
+ sn = path ("lib" + p.name);
+ se = ext == nullptr
+ ? &libso::static_type.extension (p.key ().tk, p.scope)
+ : ext;
+
+ if (!se->empty ())
+ {
+ sn += '.';
+ sn += *se;
+ }
+ }
+
+ // Now search.
+ //
+ if (!spc)
+ spc = extract_library_paths (p.scope);
+
+ liba* a (nullptr);
+ libso* s (nullptr);
+
+ path f; // Reuse the buffer.
+ const dir_path* pd;
+ for (const dir_path& d: *spc)
+ {
+ timestamp mt;
+
+ // liba
+ //
+ if (!an.empty ())
+ {
+ f = d;
+ f /= an;
+
+ if ((mt = file_mtime (f)) != timestamp_nonexistent)
+ {
+ // Enter the target. Note that because the search paths are
+ // normalized, the result is automatically normalized as well.
+ //
+ a = &targets.insert<liba> (d, p.name, ae, trace);
+
+ if (a->path ().empty ())
+ a->path (move (f));
+
+ a->mtime (mt);
+ }
+ }
+
+ // libso
+ //
+ if (!sn.empty ())
+ {
+ f = d;
+ f /= sn;
+
+ if ((mt = file_mtime (f)) != timestamp_nonexistent)
+ {
+ s = &targets.insert<libso> (d, p.name, se, trace);
+
+ if (s->path ().empty ())
+ s->path (move (f));
+
+ s->mtime (mt);
+ }
+ }
+
+ if (a != nullptr || s != nullptr)
+ {
+ pd = &d;
+ break;
+ }
+ }
+
+ if (a == nullptr && s == nullptr)
+ return nullptr;
+
+ if (l)
+ {
+ // Enter the target group.
+ //
+ lib& l (targets.insert<lib> (*pd, p.name, p.ext, trace));
+
+ // It should automatically link-up to the members we have found.
+ //
+ assert (l.a == a);
+ assert (l.so == s);
+
+ // Set the bin.lib variable to indicate what's available.
+ //
+ const char* bl (a != nullptr
+ ? (s != nullptr ? "both" : "static")
+ : "shared");
+ l.assign ("bin.lib") = bl;
+
+ p.target = &l;
+ }
+ else
+ p.target = p.is_a<liba> () ? static_cast<target*> (a) : s;
+
+ return p.target;
+ }
+
+ match_result link::
+ match (action a, target& t, const string& hint) const
+ {
+ tracer trace ("cxx::link::match");
+
+ // @@ TODO:
+ //
+ // - if path already assigned, verify extension?
+ //
+ // @@ Q:
+ //
+ // - if there is no .o, are we going to check if the one derived
+ // from target exist or can be built? A: No.
+ // What if there is a library. Probably ok if .a, not if .so.
+ // (i.e., a utility library).
+ //
+
+ type lt (link_type (t));
+
+ // Scan prerequisites and see if we can work with what we've got.
+ //
+ bool seen_cxx (false), seen_c (false), seen_obj (false),
+ seen_lib (false);
+
+ for (prerequisite_member p: group_prerequisite_members (a, t))
+ {
+ if (p.is_a<cxx> ())
+ {
+ seen_cxx = seen_cxx || true;
+ }
+ else if (p.is_a<c> ())
+ {
+ seen_c = seen_c || true;
+ }
+ else if (p.is_a<obja> ())
+ {
+ if (lt == type::so)
+ fail << "shared library " << t << " prerequisite " << p
+ << " is static object";
+
+ seen_obj = seen_obj || true;
+ }
+ else if (p.is_a<objso> () ||
+ p.is_a<obj> ())
+ {
+ seen_obj = seen_obj || true;
+ }
+ else if (p.is_a<liba> () ||
+ p.is_a<libso> () ||
+ p.is_a<lib> ())
+ {
+ seen_lib = seen_lib || true;
+ }
+ }
+
+ // We will only chain a C source if there is also a C++ source or we
+ // were explicitly told to.
+ //
+ if (seen_c && !seen_cxx && hint < "cxx")
+ {
+ level4 ([&]{trace << "c prerequisite(s) without c++ or hint";});
+ return nullptr;
+ }
+
+ // If we have any prerequisite libraries (which also means that
+ // we match), search/import and pre-match them to implement the
+ // "library meta-information protocol". Don't do this if we are
+ // called from the install rule just to check if we would match.
+ //
+ if (seen_lib && lt != type::e &&
+ a.operation () != install_id && a.outer_operation () != install_id)
+ {
+ if (t.group != nullptr)
+ t.group->prerequisite_targets.clear (); // lib{}'s
+
+ search_paths_cache lib_paths; // Extract lazily.
+
+ for (prerequisite_member p: group_prerequisite_members (a, t))
+ {
+ if (p.is_a<lib> () || p.is_a<liba> () || p.is_a<libso> ())
+ {
+ target* pt (nullptr);
+
+ // Handle imported libraries.
+ //
+ if (p.proj () != nullptr)
+ pt = search_library (lib_paths, p.prerequisite);
+
+ if (pt == nullptr)
+ {
+ pt = &p.search ();
+ match_only (a, *pt);
+ }
+
+ // If the prerequisite came from the lib{} group, then also
+ // add it to lib's prerequisite_targets.
+ //
+ if (!p.prerequisite.belongs (t))
+ t.group->prerequisite_targets.push_back (pt);
+
+ t.prerequisite_targets.push_back (pt);
+ }
+ }
+ }
+
+ return seen_cxx || seen_c || seen_obj || seen_lib ? &t : nullptr;
+ }
+
+ recipe link::
+ apply (action a, target& xt, const match_result&) const
+ {
+ tracer trace ("cxx::link::apply");
+
+ path_target& t (static_cast<path_target&> (xt));
+
+ type lt (link_type (t));
+ bool so (lt == type::so);
+ order lo (link_order (t));
+
+ // Derive file name from target name.
+ //
+ if (t.path ().empty ())
+ {
+ auto l (t["extension"]);
+ const char* e (l ? as<string> (*l).c_str () : nullptr);
+
+ switch (lt)
+ {
+ case type::e:
+ {
+ t.derive_path (e != nullptr ? e : "");
+ break;
+ }
+ case type::a:
+ case type::so:
+ {
+ auto l (t["bin.libprefix"]);
+ t.derive_path (e != nullptr ? e : (lt == type::a ? "a" : "so"),
+ l ? as<string> (*l).c_str () : "lib");
+ break;
+ }
+ }
+ }
+
+ t.prerequisite_targets.clear (); // See lib pre-match in match() above.
+
+ // Inject dependency on the output directory.
+ //
+ inject_parent_fsdir (a, t);
+
+ // We may need the project roots for rule chaining (see below).
+ // We will resolve them lazily only if needed.
+ //
+ scope* root (nullptr);
+ const dir_path* out_root (nullptr);
+ const dir_path* src_root (nullptr);
+
+ search_paths_cache lib_paths; // Extract lazily.
+
+ // Process prerequisites: do rule chaining for C and C++ source
+ // files as well as search and match.
+ //
+ // When cleaning, ignore prerequisites that are not in the same
+ // or a subdirectory of our strong amalgamation.
+ //
+ const dir_path* amlg (
+ a.operation () != clean_id
+ ? nullptr
+ : &t.strong_scope ().out_path ());
+
+ for (prerequisite_member p: group_prerequisite_members (a, t))
+ {
+ bool group (!p.prerequisite.belongs (t)); // Group's prerequisite.
+ target* pt (nullptr);
+
+ if (!p.is_a<c> () && !p.is_a<cxx> ())
+ {
+ // Handle imported libraries.
+ //
+ if (p.proj () != nullptr)
+ pt = search_library (lib_paths, p.prerequisite);
+
+ // The rest is the same basic logic as in search_and_match().
+ //
+ if (pt == nullptr)
+ pt = &p.search ();
+
+ if (a.operation () == clean_id && !pt->dir.sub (*amlg))
+ continue; // Skip.
+
+ // If this is the obj{} or lib{} target group, then pick the
+ // appropriate member and make sure it is searched and matched.
+ //
+ if (obj* o = pt->is_a<obj> ())
+ {
+ pt = so ? static_cast<target*> (o->so) : o->a;
+
+ if (pt == nullptr)
+ pt = &search (so ? objso::static_type : obja::static_type,
+ p.key ());
+ }
+ else if (lib* l = pt->is_a<lib> ())
+ {
+ pt = &link_member (*l, lo);
+ }
+
+ build2::match (a, *pt);
+ t.prerequisite_targets.push_back (pt);
+ continue;
+ }
+
+ if (root == nullptr)
+ {
+ // Which scope shall we use to resolve the root? Unlikely,
+ // but possible, the prerequisite is from a different project
+ // altogether. So we are going to use the target's project.
+ //
+ root = &t.root_scope ();
+ out_root = &root->out_path ();
+ src_root = &root->src_path ();
+ }
+
+ const prerequisite_key& cp (p.key ()); // c(xx){} prerequisite key.
+ const target_type& o_type (
+ group
+ ? obj::static_type
+ : (so ? objso::static_type : obja::static_type));
+
+ // Come up with the obj*{} target. The c(xx){} prerequisite
+ // directory can be relative (to the scope) or absolute. If it is
+ // relative, then use it as is. If it is absolute, then translate
+ // it to the corresponding directory under out_root. While the
+ // c(xx){} directory is most likely under src_root, it is also
+ // possible it is under out_root (e.g., generated source).
+ //
+ dir_path d;
+ {
+ const dir_path& cpd (*cp.tk.dir);
+
+ if (cpd.relative () || cpd.sub (*out_root))
+ d = cpd;
+ else
+ {
+ if (!cpd.sub (*src_root))
+ fail << "out of project prerequisite " << cp <<
+ info << "specify corresponding " << o_type.name << "{} "
+ << "target explicitly";
+
+ d = *out_root / cpd.leaf (*src_root);
+ }
+ }
+
+ target& ot (search (o_type, d, *cp.tk.name, nullptr, cp.scope));
+
+ // If we are cleaning, check that this target is in the same or
+ // a subdirectory of our strong amalgamation.
+ //
+ if (a.operation () == clean_id && !ot.dir.sub (*amlg))
+ {
+ // If we shouldn't clean obj{}, then it is fair to assume
+ // we shouldn't clean cxx{} either (generated source will
+ // be in the same directory as obj{} and if not, well, go
+ // find yourself another build system ;-)).
+ //
+ continue; // Skip.
+ }
+
+ // If we have created the obj{} target group, pick one of its
+ // members; the rest would be primarily concerned with it.
+ //
+ if (group)
+ {
+ obj& o (static_cast<obj&> (ot));
+ pt = so ? static_cast<target*> (o.so) : o.a;
+
+ if (pt == nullptr)
+ pt = &search (so ? objso::static_type : obja::static_type,
+ o.dir, o.name, o.ext, nullptr);
+ }
+ else
+ pt = &ot;
+
+ // If this obj*{} target already exists, then it needs to be
+ // "compatible" with what we are doing here.
+ //
+ // This gets a bit tricky. We need to make sure the source files
+ // are the same which we can only do by comparing the targets to
+ // which they resolve. But we cannot search the ot's prerequisites
+ // -- only the rule that matches can. Note, however, that if all
+ // this works out, then our next step is to match the obj*{}
+ // target. If things don't work out, then we fail, in which case
+ // searching and matching speculatively doesn't really hurt.
+ //
+ bool found (false);
+ for (prerequisite_member p1:
+ reverse_group_prerequisite_members (a, *pt))
+ {
+ // Ignore some known target types (fsdir, headers, libraries).
+ //
+ if (p1.is_a<fsdir> () ||
+ p1.is_a<h> () ||
+ (p.is_a<cxx> () && (p1.is_a<hxx> () ||
+ p1.is_a<ixx> () ||
+ p1.is_a<txx> ())) ||
+ p1.is_a<lib> () ||
+ p1.is_a<liba> () ||
+ p1.is_a<libso> ())
+ {
+ continue;
+ }
+
+ if (!p1.is_a<cxx> ())
+ fail << "synthesized target for prerequisite " << cp
+ << " would be incompatible with existing target " << *pt <<
+ info << "unexpected existing prerequisite type " << p1 <<
+ info << "specify corresponding obj{} target explicitly";
+
+ if (!found)
+ {
+ build2::match (a, *pt); // Now p1 should be resolved.
+
+ // Searching our own prerequisite is ok.
+ //
+ if (&p.search () != &p1.search ())
+ fail << "synthesized target for prerequisite " << cp << " would "
+ << "be incompatible with existing target " << *pt <<
+ info << "existing prerequisite " << p1 << " does not match "
+ << cp <<
+ info << "specify corresponding " << o_type.name << "{} target "
+ << "explicitly";
+
+ found = true;
+ // Check the rest of the prerequisites.
+ }
+ }
+
+ if (!found)
+ {
+ // Note: add the source to the group, not the member.
+ //
+ ot.prerequisites.emplace_back (p.as_prerequisite (trace));
+
+ // Add our lib*{} prerequisites to the object file (see
+ // cxx.export.poptions above for details). Note: no need
+ // to go into group members.
+ //
+ // Initially, we were only adding imported libraries, but
+ // there is a problem with this approach: the non-imported
+ // library might depend on the imported one(s) which we will
+ // never "see" unless we start with this library.
+ //
+ for (prerequisite& p: group_prerequisites (t))
+ {
+ if (p.is_a<lib> () || p.is_a<liba> () || p.is_a<libso> ())
+ ot.prerequisites.emplace_back (p);
+ }
+
+ build2::match (a, *pt);
+ }
+
+ t.prerequisite_targets.push_back (pt);
+ }
+
+ switch (a)
+ {
+ case perform_update_id: return &perform_update;
+ case perform_clean_id: return &perform_clean;
+ default: return noop_recipe; // Configure update.
+ }
+ }
+
+ target_state link::
+ perform_update (action a, target& xt)
+ {
+ path_target& t (static_cast<path_target&> (xt));
+
+ type lt (link_type (t));
+ bool so (lt == type::so);
+
+ if (!execute_prerequisites (a, t, t.mtime ()))
+ return target_state::unchanged;
+
+ // Translate paths to relative (to working directory) ones. This
+ // results in easier to read diagnostics.
+ //
+ path relt (relative (t.path ()));
+
+ scope& rs (t.root_scope ());
+ cstrings args;
+
+ // Storage.
+ //
+ string std;
+ string soname;
+ strings sargs;
+
+ if (lt == type::a)
+ {
+ //@@ ranlib
+ //
+ args.push_back ("ar");
+ args.push_back ("-rc");
+ args.push_back (relt.string ().c_str ());
+ }
+ else
+ {
+ args.push_back (as<string> (*rs["config.cxx"]).c_str ());
+ append_options (args, t, "cxx.coptions");
+ append_std (args, t, std);
+
+ if (so)
+ args.push_back ("-shared");
+
+ args.push_back ("-o");
+ args.push_back (relt.string ().c_str ());
+
+ // Set soname.
+ //
+ if (so)
+ {
+ soname = "-Wl,-soname," + relt.leaf ().string ();
+ args.push_back (soname.c_str ());
+ }
+
+ // Add rpaths. First the ones specified by the user so that they
+ // take precedence.
+ //
+ if (auto l = t["bin.rpath"])
+ for (const string& p: as<strings> (*l))
+ sargs.push_back ("-Wl,-rpath," + p);
+
+ // Then the paths of the shared libraries we are linking to.
+ //
+ for (target* pt: t.prerequisite_targets)
+ {
+ if (libso* ls = pt->is_a<libso> ())
+ sargs.push_back (
+ "-Wl,-rpath," + ls->path ().directory ().string ());
+ }
+ }
+
+ size_t oend (sargs.size ()); // Note the end of options.
+
+ for (target* pt: t.prerequisite_targets)
+ {
+ path_target* ppt;
+
+ if ((ppt = pt->is_a<obja> ()) ||
+ (ppt = pt->is_a<objso> ()) ||
+ (ppt = pt->is_a<liba> ()) ||
+ (ppt = pt->is_a<libso> ()))
+ {
+ sargs.push_back (relative (ppt->path ()).string ()); // string()&&
+ }
+ }
+
+ // Finish assembling args from sargs.
+ //
+ for (size_t i (0); i != sargs.size (); ++i)
+ {
+ if (lt != type::a && i == oend)
+ append_options (args, t, "cxx.loptions");
+
+ args.push_back (sargs[i].c_str ());
+ }
+
+ if (lt != type::a)
+ append_options (args, t, "cxx.libs");
+
+ args.push_back (nullptr);
+
+ if (verb >= 2)
+ print_process (args);
+ else if (verb)
+ text << "ld " << t;
+
+ try
+ {
+ process pr (args.data ());
+
+ if (!pr.wait ())
+ throw failed ();
+
+ // Should we go to the filesystem and get the new mtime? We
+ // know the file has been modified, so instead just use the
+ // current clock time. It has the advantage of having the
+ // subseconds precision.
+ //
+ t.mtime (system_clock::now ());
+ return target_state::changed;
+ }
+ catch (const process_error& e)
+ {
+ error << "unable to execute " << args[0] << ": " << e.what ();
+
+ // In a multi-threaded program that fork()'ed but did not exec(),
+ // it is unwise to try to do any kind of cleanup (like unwinding
+ // the stack and running destructors).
+ //
+ if (e.child ())
+ exit (1);
+
+ throw failed ();
+ }
+ }
+
+ link link::instance;
+ }
+}
diff --git a/build2/cxx/module b/build2/cxx/module
new file mode 100644
index 0000000..c712d0b
--- /dev/null
+++ b/build2/cxx/module
@@ -0,0 +1,23 @@
+// file : build2/cxx/module -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_CXX_MODULE
+#define BUILD2_CXX_MODULE
+
+#include <build2/types>
+#include <build2/utility>
+
+#include <build2/module>
+
+namespace build2
+{
+ namespace cxx
+ {
+ extern "C" bool
+ cxx_init (
+ scope&, scope&, const location&, unique_ptr<module>&, bool, bool);
+ }
+}
+
+#endif // BUILD2_CXX_MODULE
diff --git a/build2/cxx/module.cxx b/build2/cxx/module.cxx
new file mode 100644
index 0000000..4829a17
--- /dev/null
+++ b/build2/cxx/module.cxx
@@ -0,0 +1,230 @@
+// file : build2/cxx/module.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/cxx/module>
+
+#include <butl/process>
+#include <butl/fdstream>
+
+#include <build2/scope>
+#include <build2/diagnostics>
+
+#include <build2/config/utility>
+#include <build2/install/utility>
+
+#include <build2/bin/target>
+
+#include <build2/cxx/target>
+#include <build2/cxx/compile>
+#include <build2/cxx/link>
+#include <build2/cxx/install>
+
+using namespace std;
+using namespace butl;
+
+namespace build2
+{
+ namespace cxx
+ {
+ extern "C" bool
+ cxx_init (scope& r,
+ scope& b,
+ const location& loc,
+ std::unique_ptr<module>&,
+ bool first,
+ bool)
+ {
+ tracer trace ("cxx::init");
+ level5 ([&]{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.
+ //
+ {
+ auto l (b["bin.loaded"]);
+
+ if (!l || !as<bool> (*l))
+ load_module (false, "bin", r, b, loc);
+ }
+
+ // Enter module variables.
+ //
+ // @@ Probably should only be done on load; make sure reset() unloads
+ // modules.
+ //
+ // @@ Should probably cache the variable pointers so we don't have
+ // to keep looking them up.
+ //
+ if (first)
+ {
+ auto& v (var_pool);
+
+ v.find ("config.cxx", string_type); //@@ VAR type
+
+ v.find ("config.cxx.poptions", strings_type);
+ v.find ("config.cxx.coptions", strings_type);
+ v.find ("config.cxx.loptions", strings_type);
+ v.find ("config.cxx.libs", strings_type);
+
+ v.find ("cxx.poptions", strings_type);
+ v.find ("cxx.coptions", strings_type);
+ v.find ("cxx.loptions", strings_type);
+ v.find ("cxx.libs", strings_type);
+
+ v.find ("cxx.export.poptions", strings_type);
+ v.find ("cxx.export.coptions", strings_type);
+ v.find ("cxx.export.loptions", strings_type);
+ v.find ("cxx.export.libs", strings_type);
+
+ v.find ("cxx.std", string_type);
+ }
+
+ // Register target types.
+ //
+ {
+ auto& t (b.target_types);
+
+ t.insert<h> ();
+ t.insert<c> ();
+
+ t.insert<cxx> ();
+ t.insert<hxx> ();
+ t.insert<ixx> ();
+ t.insert<txx> ();
+ }
+
+ // Register rules.
+ //
+ {
+ using namespace bin;
+
+ auto& r (b.rules);
+
+ r.insert<obja> (perform_update_id, "cxx.compile", compile::instance);
+
+ r.insert<obja> (perform_update_id, "cxx.compile", compile::instance);
+ r.insert<obja> (perform_clean_id, "cxx.compile", compile::instance);
+
+ r.insert<objso> (perform_update_id, "cxx.compile", compile::instance);
+ r.insert<objso> (perform_clean_id, "cxx.compile", compile::instance);
+
+ r.insert<exe> (perform_update_id, "cxx.link", link::instance);
+ r.insert<exe> (perform_clean_id, "cxx.link", link::instance);
+
+ r.insert<liba> (perform_update_id, "cxx.link", link::instance);
+ r.insert<liba> (perform_clean_id, "cxx.link", link::instance);
+
+ r.insert<libso> (perform_update_id, "cxx.link", link::instance);
+ r.insert<libso> (perform_clean_id, "cxx.link", link::instance);
+
+ // Register for configure so that we detect unresolved imports
+ // during configuration rather that later, e.g., during update.
+ //
+ r.insert<obja> (configure_update_id, "cxx.compile", compile::instance);
+ r.insert<objso> (configure_update_id, "cxx.compile", compile::instance);
+
+ r.insert<exe> (configure_update_id, "cxx.link", link::instance);
+ r.insert<liba> (configure_update_id, "cxx.link", link::instance);
+ r.insert<libso> (configure_update_id, "cxx.link", link::instance);
+
+ //@@ Should we check if install module was loaded (see bin)?
+ //
+ r.insert<exe> (perform_install_id, "cxx.install", install::instance);
+ r.insert<liba> (perform_install_id, "cxx.install", install::instance);
+ r.insert<libso> (perform_install_id, "cxx.install", install::instance);
+ }
+
+ // Configure.
+ //
+
+ // config.cxx
+ //
+ if (first)
+ {
+ auto p (config::required (r, "config.cxx", "g++"));
+
+ // If we actually set a new value, test it by trying to execute.
+ //
+ if (p.second)
+ {
+ const string& cxx (as<string> (p.first));
+ const char* args[] = {cxx.c_str (), "-dumpversion", nullptr};
+
+ if (verb >= 2)
+ print_process (args);
+ else if (verb)
+ text << "test " << cxx;
+
+ string ver;
+ try
+ {
+ process pr (args, 0, -1); // Open pipe to stdout.
+ ifdstream is (pr.in_ofd);
+
+ bool r (getline (is, ver));
+
+ if (!r)
+ fail << "unexpected output from " << cxx;
+
+ if (!pr.wait ())
+ throw failed ();
+ }
+ catch (const process_error& e)
+ {
+ error << "unable to execute " << cxx << ": " << e.what ();
+
+ if (e.child ())
+ exit (1);
+
+ throw failed ();
+ }
+
+ if (verb >= 2)
+ text << cxx << " " << ver;
+ }
+ }
+
+ // config.cxx.{p,c,l}options
+ // config.cxx.libs
+ //
+ // These are optional. We also merge them into the corresponding
+ // cxx.* variables.
+ //
+ // The merging part gets a bit tricky if this module has already
+ // been loaded in one of the outer scopes. By doing the straight
+ // append we would just be repeating the same options over and
+ // over. So what we are going to do is only append to a value if
+ // it came from this scope. Then the usage for merging becomes:
+ //
+ // cxx.coptions = <overridable options> # Note: '='.
+ // using cxx
+ // cxx.coptions += <overriding options> # Note: '+='.
+ //
+ if (const value& v = config::optional (r, "config.cxx.poptions"))
+ b.assign ("cxx.poptions") += as<strings> (v);
+
+ if (const value& v = config::optional (r, "config.cxx.coptions"))
+ b.assign ("cxx.coptions") += as<strings> (v);
+
+ if (const value& v = config::optional (r, "config.cxx.loptions"))
+ b.assign ("cxx.loptions") += as<strings> (v);
+
+ if (const value& v = config::optional (r, "config.cxx.libs"))
+ b.assign ("cxx.libs") += as<strings> (v);
+
+ // Configure "installability" of our target types.
+ //
+ {
+ using build2::install::path;
+
+ path<hxx> (b, dir_path ("include")); // Install into install.include.
+ path<ixx> (b, dir_path ("include"));
+ path<txx> (b, dir_path ("include"));
+ path<h> (b, dir_path ("include"));
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/build2/cxx/target b/build2/cxx/target
new file mode 100644
index 0000000..c51c964
--- /dev/null
+++ b/build2/cxx/target
@@ -0,0 +1,78 @@
+// file : build2/cxx/target -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_CXX_TARGET
+#define BUILD2_CXX_TARGET
+
+#include <build2/target>
+
+namespace build2
+{
+ namespace cxx
+ {
+ class hxx: public file
+ {
+ public:
+ using file::file;
+
+ public:
+ static const target_type static_type;
+ virtual const target_type& dynamic_type () const {return static_type;}
+ };
+
+ class ixx: public file
+ {
+ public:
+ using file::file;
+
+ public:
+ static const target_type static_type;
+ virtual const target_type& dynamic_type () const {return static_type;}
+ };
+
+ class txx: public file
+ {
+ public:
+ using file::file;
+
+ public:
+ static const target_type static_type;
+ virtual const target_type& dynamic_type () const {return static_type;}
+ };
+
+ class cxx: public file
+ {
+ public:
+ using file::file;
+
+ public:
+ static const target_type static_type;
+ virtual const target_type& dynamic_type () const {return static_type;}
+ };
+
+ //@@ TMP
+ //
+ class h: public file
+ {
+ public:
+ using file::file;
+
+ public:
+ static const target_type static_type;
+ virtual const target_type& dynamic_type () const {return static_type;}
+ };
+
+ class c: public file
+ {
+ public:
+ using file::file;
+
+ public:
+ static const target_type static_type;
+ virtual const target_type& dynamic_type () const {return static_type;}
+ };
+ }
+}
+
+#endif // BUILD2_CXX_TARGET
diff --git a/build2/cxx/target.cxx b/build2/cxx/target.cxx
new file mode 100644
index 0000000..0990945
--- /dev/null
+++ b/build2/cxx/target.cxx
@@ -0,0 +1,81 @@
+// file : build2/cxx/target.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/cxx/target>
+
+using namespace std;
+
+namespace build2
+{
+ namespace cxx
+ {
+ constexpr const char ext_var[] = "extension";
+
+ constexpr const char hxx_ext_def[] = "hxx";
+ const target_type hxx::static_type
+ {
+ "hxx",
+ &file::static_type,
+ &target_factory<hxx>,
+ &target_extension_var<ext_var, hxx_ext_def>,
+ &search_file,
+ false
+ };
+
+ constexpr const char ixx_ext_def[] = "ixx";
+ const target_type ixx::static_type
+ {
+ "ixx",
+ &file::static_type,
+ &target_factory<ixx>,
+ &target_extension_var<ext_var, ixx_ext_def>,
+ &search_file,
+ false
+ };
+
+ constexpr const char txx_ext_def[] = "txx";
+ const target_type txx::static_type
+ {
+ "txx",
+ &file::static_type,
+ &target_factory<txx>,
+ &target_extension_var<ext_var, txx_ext_def>,
+ &search_file,
+ false
+ };
+
+ constexpr const char cxx_ext_def[] = "cxx";
+ const target_type cxx::static_type
+ {
+ "cxx",
+ &file::static_type,
+ &target_factory<cxx>,
+ &target_extension_var<ext_var, cxx_ext_def>,
+ &search_file,
+ false
+ };
+
+ constexpr const char h_ext_def[] = "h";
+ const target_type h::static_type
+ {
+ "h",
+ &file::static_type,
+ &target_factory<h>,
+ &target_extension_var<ext_var, h_ext_def>,
+ &search_file,
+ false
+ };
+
+ constexpr const char c_ext_def[] = "c";
+ const target_type c::static_type
+ {
+ "c",
+ &file::static_type,
+ &target_factory<c>,
+ &target_extension_var<ext_var, c_ext_def>,
+ &search_file,
+ false
+ };
+ }
+}
diff --git a/build2/cxx/utility b/build2/cxx/utility
new file mode 100644
index 0000000..b0deb08
--- /dev/null
+++ b/build2/cxx/utility
@@ -0,0 +1,37 @@
+// file : build2/cxx/utility -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_CXX_UTILITY
+#define BUILD2_CXX_UTILITY
+
+#include <string>
+
+#include <build2/types>
+#include <build2/target>
+
+#include <build2/config/utility>
+
+namespace build2
+{
+ namespace cxx
+ {
+ using config::append_options;
+
+ // T is either target or scope.
+ //
+ template <typename T>
+ void
+ append_std (cstrings& args, T&, std::string& storage);
+
+ // Append library options from one of the cxx.export.* variables
+ // recursively, prerequisite libraries first.
+ //
+ void
+ append_lib_options (cstrings& args, target&, const char* variable);
+ }
+}
+
+#include <build2/cxx/utility.txx>
+
+#endif // BUILD2_CXX_UTILITY
diff --git a/build2/cxx/utility.cxx b/build2/cxx/utility.cxx
new file mode 100644
index 0000000..fead1b4
--- /dev/null
+++ b/build2/cxx/utility.cxx
@@ -0,0 +1,29 @@
+// file : build2/cxx/utility.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/cxx/utility>
+
+#include <build2/bin/target>
+
+using namespace std;
+
+namespace build2
+{
+ namespace cxx
+ {
+ void
+ append_lib_options (cstrings& args, target& l, const char* var)
+ {
+ using namespace bin;
+
+ for (target* t: l.prerequisite_targets)
+ {
+ if (t->is_a<lib> () || t->is_a<liba> () || t->is_a<libso> ())
+ append_lib_options (args, *t, var);
+ }
+
+ append_options (args, l, var);
+ }
+ }
+}
diff --git a/build2/cxx/utility.txx b/build2/cxx/utility.txx
new file mode 100644
index 0000000..b35649e
--- /dev/null
+++ b/build2/cxx/utility.txx
@@ -0,0 +1,35 @@
+// file : build2/cxx/utility.txx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+using namespace std;
+
+namespace build2
+{
+ namespace cxx
+ {
+ template <typename T>
+ void
+ append_std (cstrings& args, T& t, std::string& s)
+ {
+ if (auto l = t["cxx.std"])
+ {
+ const std::string& v (as<string> (*l));
+
+ // Translate 11 to 0x and 14 to 1y for compatibility with
+ // older versions of the compiler.
+ //
+ s = "-std=c++";
+
+ if (v == "11")
+ s += "0x";
+ else if (v == "14")
+ s += "1y";
+ else
+ s += v;
+
+ args.push_back (s.c_str ());
+ }
+ }
+ }
+}
diff --git a/build2/diagnostics b/build2/diagnostics
new file mode 100644
index 0000000..52f999a
--- /dev/null
+++ b/build2/diagnostics
@@ -0,0 +1,402 @@
+// file : build2/diagnostics -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_DIAGNOSTICS
+#define BUILD2_DIAGNOSTICS
+
+#include <cstddef> // size_t
+#include <cstdint>
+#include <utility>
+#include <cassert>
+#include <sstream>
+#include <ostream>
+#include <exception>
+#include <type_traits>
+
+#include <butl/path>
+
+#include <build2/types>
+#include <build2/path-io>
+
+namespace build2
+{
+ struct diag_record;
+
+ // Throw this exception to terminate the build. The handler should
+ // assume that the diagnostics has already been issued.
+ //
+ class failed: public std::exception {};
+
+ // Flag that indicates whether paths should be inserted relative
+ // into this stream.
+ //
+ extern const int relative_index;
+
+ inline bool
+ relative (std::ostream& os) {return os.iword (relative_index);}
+
+ inline void
+ relative (std::ostream& os, bool v) {os.iword (relative_index) = v ? 1 : 0;}
+
+ // Print process commmand line. If the number of elements is specified
+ // (or the second version is used), then it will print the piped multi-
+ // process command line, if present. In this case, the expected format
+ // is as follows:
+ //
+ // name1 arg arg ... nullptr
+ // name2 arg arg ... nullptr
+ // ...
+ // nameN arg arg ... nullptr nullptr
+ //
+ void
+ print_process (diag_record&, const char* const* args, std::size_t n = 0);
+
+ void
+ print_process (const char* const* args, std::size_t n = 0);
+
+ inline void
+ print_process (diag_record& dr, const cstrings& args)
+ {
+ print_process (dr, args.data (), args.size ());
+ }
+
+ inline void
+ print_process (const cstrings& args)
+ {
+ print_process (args.data (), args.size ());
+ }
+
+ // Verbosity level.
+ //
+ // 0 - disabled
+ // 1 - high-level information messages
+ // 2 - essential underlying commands that are being executed
+ // 3 - all underlying commands that are being executed
+ // 4 - information helpful to the user (e.g., why a rule did not match)
+ // 5 - information helpful to the developer
+ // 6 - even more detailed information
+ //
+ // While uint8 is more than enough, use uint16 for the ease of printing.
+ //
+ extern std::uint16_t verb;
+
+ template <typename F> inline void level1 (const F& f) {if (verb >= 1) f ();}
+ template <typename F> inline void level2 (const F& f) {if (verb >= 2) f ();}
+ template <typename F> inline void level3 (const F& f) {if (verb >= 3) f ();}
+ template <typename F> inline void level4 (const F& f) {if (verb >= 4) f ();}
+ template <typename F> inline void level5 (const F& f) {if (verb >= 5) f ();}
+ template <typename F> inline void level6 (const F& f) {if (verb >= 6) f ();}
+
+ // Diagnostic facility, base infrastructure (potentially reusable).
+ //
+ extern std::ostream* diag_stream;
+
+ template <typename> struct diag_prologue;
+ template <typename> struct diag_mark;
+
+ typedef void (*diag_epilogue) (const diag_record&);
+
+ struct diag_record
+ {
+ template <typename T>
+ friend const diag_record&
+ operator<< (const diag_record& r, const T& x)
+ {
+ r.os_ << x;
+ return r;
+ }
+
+ diag_record (): empty_ (true), epilogue_ (nullptr) {}
+
+ template <typename B>
+ explicit
+ diag_record (const diag_prologue<B>& p)
+ : empty_ (true), epilogue_ (nullptr) { *this << p;}
+
+ template <typename B>
+ explicit
+ diag_record (const diag_mark<B>& m)
+ : empty_ (true), epilogue_ (nullptr) { *this << m;}
+
+ ~diag_record () noexcept(false);
+
+ void
+ append (diag_epilogue e) const
+ {
+ if (e != nullptr)
+ {
+ assert (epilogue_ == nullptr); // No multiple epilogues support.
+ epilogue_ = e;
+ }
+
+ if (empty_)
+ empty_ = false;
+ else
+ os_ << "\n ";
+ }
+
+ // Move constructible-only type.
+ //
+ /*
+ @@ libstdc++ doesn't yet have the ostringstream move support.
+
+ diag_record (diag_record&& r)
+ : os_ (std::move (r.os_))
+ {
+ empty_ = r.empty_;
+ r.empty_ = true;
+
+ epilogue_ = r.epilogue_;
+ r.epilogue_ = nullptr;
+ }
+ */
+
+ diag_record (diag_record&& r)
+ {
+ empty_ = r.empty_;
+ epilogue_ = r.epilogue_;
+
+ if (!empty_)
+ {
+ assert (false); //@@ Relative flag will not be transferred.
+ os_ << r.os_.str ();
+
+ r.empty_ = true;
+ r.epilogue_ = nullptr;
+ }
+ }
+
+ diag_record& operator= (diag_record&&) = delete;
+
+ diag_record (const diag_record&) = delete;
+ diag_record& operator= (const diag_record&) = delete;
+
+ public:
+ mutable std::ostringstream os_;
+
+ private:
+ mutable bool empty_;
+ mutable diag_epilogue epilogue_;
+ };
+
+ template <typename B>
+ struct diag_prologue: B
+ {
+ diag_prologue (diag_epilogue e = nullptr): B (), epilogue_ (e) {}
+
+ template <typename... A>
+ diag_prologue (A&&... a)
+ : B (std::forward<A> (a)...), epilogue_ (nullptr) {}
+
+ template <typename... A>
+ diag_prologue (diag_epilogue e, A&&... a)
+ : B (std::forward<A> (a)...), epilogue_ (e) {}
+
+ template <typename T>
+ diag_record
+ operator<< (const T& x) const
+ {
+ diag_record r;
+ r.append (epilogue_);
+ B::operator() (r);
+ r << x;
+ return r;
+ }
+
+ friend const diag_record&
+ operator<< (const diag_record& r, const diag_prologue& p)
+ {
+ r.append (p.epilogue_);
+ p (r);
+ return r;
+ }
+
+ private:
+ diag_epilogue epilogue_;
+ };
+
+ template <typename B>
+ struct diag_mark: B
+ {
+ diag_mark (): B () {}
+
+ template <typename... A>
+ diag_mark (A&&... a): B (std::forward<A> (a)...) {}
+
+ template <typename T>
+ diag_record
+ operator<< (const T& x) const
+ {
+ return B::operator() () << x;
+ }
+
+ friend const diag_record&
+ operator<< (const diag_record& r, const diag_mark& m)
+ {
+ return r << m ();
+ }
+ };
+
+ // Diagnostic facility, project specifics.
+ //
+ struct simple_prologue_base
+ {
+ explicit
+ simple_prologue_base (const char* type, const char* name, bool rel)
+ : type_ (type), name_ (name), relative_ (rel) {}
+
+ void
+ operator() (const diag_record& r) const;
+
+ private:
+ const char* type_;
+ const char* name_;
+ const bool relative_;
+ };
+ typedef diag_prologue<simple_prologue_base> simple_prologue;
+
+ class location
+ {
+ public:
+ location () {}
+ location (const char* f, std::uint64_t l, std::uint64_t c)
+ : file (f), line (l), column (c) {}
+
+ const char* file;
+ std::uint64_t line;
+ std::uint64_t column;
+ };
+
+ struct location_prologue_base
+ {
+ location_prologue_base (const char* type,
+ const char* name,
+ const location& l,
+ bool rel)
+ : type_ (type), name_ (name), loc_ (l), relative_ (rel) {}
+
+ void
+ operator() (const diag_record& r) const;
+
+ private:
+ const char* type_;
+ const char* name_;
+ const location loc_;
+ const bool relative_;
+ };
+ typedef diag_prologue<location_prologue_base> location_prologue;
+
+ // Here is the absolute/relative path rationale: we want it absolute
+ // in the error/warning/info streams to give the user the complete
+ // picture. But in the text stream (e.g., command lines), we print
+ // relative unless verbosity is greater than 1.
+ //
+ struct basic_mark_base
+ {
+ explicit
+ basic_mark_base (const char* type,
+ const char* name = nullptr,
+ const void* data = nullptr)
+ : type_ (type), name_ (name), data_ (data) {}
+
+ simple_prologue
+ operator() () const
+ {
+ return simple_prologue (type_, name_, false);
+ }
+
+ location_prologue
+ operator() (const location& l) const
+ {
+ return location_prologue (type_, name_, l, false);
+ }
+
+ template <typename L>
+ location_prologue
+ operator() (const L& l) const
+ {
+ return location_prologue (type_, name_, get_location (l, data_), false);
+ }
+
+ protected:
+ const char* type_;
+ const char* name_;
+ const void* data_;
+ };
+ typedef diag_mark<basic_mark_base> basic_mark;
+
+ extern const basic_mark error;
+ extern const basic_mark warn;
+ extern const basic_mark info;
+
+ // text
+ //
+ struct text_mark_base: basic_mark_base
+ {
+ text_mark_base (): basic_mark_base (nullptr) {}
+
+ simple_prologue
+ operator() () const
+ {
+ return simple_prologue (type_, name_, verb <= 1);
+ }
+ };
+ typedef diag_mark<text_mark_base> text_mark;
+
+ extern const text_mark text;
+
+ // trace
+ //
+ struct trace_mark_base: basic_mark_base
+ {
+ explicit
+ trace_mark_base (const char* name, const void* data = nullptr)
+ : basic_mark_base ("trace", name, data) {}
+ };
+ typedef diag_mark<trace_mark_base> trace_mark;
+
+ typedef trace_mark tracer;
+
+ // fail
+ //
+ template <typename E>
+ struct fail_mark_base
+ {
+ explicit
+ fail_mark_base (const void* data = nullptr): data_ (data) {}
+
+ simple_prologue
+ operator() () const
+ {
+ return simple_prologue (&epilogue, "error", nullptr, false);
+ }
+
+ location_prologue
+ operator() (const location& l) const
+ {
+ return location_prologue (&epilogue, "error", nullptr, l, false);
+ }
+
+ template <typename L>
+ location_prologue
+ operator() (const L& l) const
+ {
+ return location_prologue (
+ &epilogue, "error", nullptr, get_location (l, data_), false);
+ }
+
+ static void
+ epilogue (const diag_record&) {throw E ();}
+
+ private:
+ const void* data_;
+ };
+
+ template <typename E>
+ using fail_mark = diag_mark<fail_mark_base<E>>;
+
+ extern const fail_mark<failed> fail;
+}
+
+#endif // BUILD2_DIAGNOSTICS
diff --git a/build2/diagnostics.cxx b/build2/diagnostics.cxx
new file mode 100644
index 0000000..850c1d4
--- /dev/null
+++ b/build2/diagnostics.cxx
@@ -0,0 +1,125 @@
+// file : build2/diagnostics.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/diagnostics>
+
+#include <cstring> // strchr()
+#include <iostream>
+
+#include <build2/utility>
+
+using namespace std;
+
+namespace build2
+{
+ // Relative stream.
+ //
+ const int relative_index = ostream::xalloc ();
+
+ void
+ print_process (const char* const* args, size_t n)
+ {
+ diag_record r (text);
+ print_process (r, args, n);
+ }
+
+ void
+ print_process (diag_record& r, const char* const* args, size_t n)
+ {
+ size_t m (0);
+ const char* const* p (args);
+ do
+ {
+ if (m != 0)
+ r << " |"; // Trailing space will be added inside the loop.
+
+ for (m++; *p != nullptr; p++, m++)
+ {
+ if (p != args)
+ r << ' ';
+
+ // Quote if empty or contains spaces.
+ //
+ bool q (**p == '\0' || strchr (*p, ' ') != nullptr);
+
+ if (q)
+ r << '"';
+
+ r << *p;
+
+ if (q)
+ r << '"';
+ }
+
+ if (m < n) // Can we examine the next element?
+ {
+ p++;
+ m++;
+ }
+
+ } while (*p != nullptr);
+ }
+
+ // Diagnostics verbosity level.
+ //
+ uint16_t verb;
+
+ // Diagnostic facility, base infrastructure.
+ //
+ ostream* diag_stream = &cerr;
+
+ diag_record::
+ ~diag_record () noexcept(false)
+ {
+ // Don't flush the record if this destructor was called as part of
+ // the stack unwinding. Right now this means we cannot use this
+ // mechanism in destructors, which is not a big deal, except for
+ // one place: exception_guard. So for now we are going to have
+ // this ugly special check which we will be able to get rid of
+ // once C++17 uncaught_exceptions() becomes available.
+ //
+ if (!empty_ && (!std::uncaught_exception () || exception_unwinding_dtor))
+ {
+ *diag_stream << os_.str () << std::endl;
+
+ if (epilogue_ != nullptr)
+ epilogue_ (*this); // Can throw.
+ }
+ }
+
+ // Diagnostic facility, project specifics.
+ //
+
+ void simple_prologue_base::
+ operator() (const diag_record& r) const
+ {
+ relative (r.os_, relative_);
+
+ if (type_ != nullptr)
+ r << type_ << ": ";
+
+ if (name_ != nullptr)
+ r << name_ << ": ";
+ }
+
+ void location_prologue_base::
+ operator() (const diag_record& r) const
+ {
+ relative (r.os_, relative_);
+
+ r << loc_.file << ':' << loc_.line << ':' << loc_.column << ": ";
+
+ if (type_ != nullptr)
+ r << type_ << ": ";
+
+ if (name_ != nullptr)
+ r << name_ << ": ";
+ }
+
+ const basic_mark error ("error");
+ const basic_mark warn ("warning");
+ const basic_mark info ("info");
+ const text_mark text;
+ const fail_mark<failed> fail;
+}
diff --git a/build2/dist/module b/build2/dist/module
new file mode 100644
index 0000000..29dca71
--- /dev/null
+++ b/build2/dist/module
@@ -0,0 +1,26 @@
+// file : build2/dist/module -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_DIST_MODULE
+#define BUILD2_DIST_MODULE
+
+#include <build2/types>
+#include <build2/utility>
+
+#include <build2/module>
+
+namespace build2
+{
+ namespace dist
+ {
+ extern "C" void
+ dist_boot (scope&, const location&, unique_ptr<module>&);
+
+ extern "C" bool
+ dist_init (
+ scope&, scope&, const location&, unique_ptr<module>&, bool, bool);
+ }
+}
+
+#endif // BUILD2_DIST_MODULE
diff --git a/build2/dist/module.cxx b/build2/dist/module.cxx
new file mode 100644
index 0000000..00b709e
--- /dev/null
+++ b/build2/dist/module.cxx
@@ -0,0 +1,142 @@
+// file : build2/dist/module.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/dist/module>
+
+#include <build2/scope>
+#include <build2/file>
+#include <build2/diagnostics>
+
+#include <build2/config/utility>
+
+#include <build2/dist/rule>
+#include <build2/dist/operation>
+
+using namespace std;
+using namespace butl;
+
+namespace build2
+{
+ namespace dist
+ {
+ static rule rule_;
+
+ extern "C" void
+ dist_boot (scope& r, const location&, unique_ptr<module>&)
+ {
+ tracer trace ("dist::boot");
+
+ level5 ([&]{trace << "for " << r.out_path ();});
+
+ // Register meta-operation.
+ //
+ r.meta_operations.insert (dist_id, dist);
+ }
+
+ extern "C" bool
+ dist_init (scope& r,
+ scope&,
+ const location& l,
+ unique_ptr<module>&,
+ bool first,
+ bool)
+ {
+ tracer trace ("dist::init");
+
+ if (!first)
+ {
+ warn (l) << "multiple dist module initializations";
+ return true;
+ }
+
+ const dir_path& out_root (r.out_path ());
+ level5 ([&]{trace << "for " << out_root;});
+
+ // Enter module variables.
+ //
+ if (first)
+ {
+ auto& v (var_pool);
+
+ v.find ("dist", bool_type);
+
+ v.find ("dist.package", string_type);
+
+ v.find ("dist.root", dir_path_type);
+ v.find ("config.dist.root", dir_path_type);
+
+ //@@ VAR type
+ //
+ v.find ("dist.cmd", string_type);
+ v.find ("config.dist.cmd", string_type);
+
+ v.find ("dist.archives", strings_type);
+ v.find ("config.dist.archives", strings_type);
+ }
+
+ // Register our wildcard rule. Do it explicitly for the alias
+ // to prevent something like insert<target>(dist_id, test_id)
+ // taking precedence.
+ //
+ r.rules.insert<target> (dist_id, 0, "dist", rule_);
+ r.rules.insert<alias> (dist_id, 0, "dist.alias", rule_);
+
+ // Configuration.
+ //
+ // Note that we don't use any defaults for root -- the location
+ // must be explicitly specified or we will complain if and when
+ // we try to dist.
+ //
+ using namespace config;
+
+ bool s (specified (r, "config.dist"));
+
+ // dist.root
+ //
+ {
+ value& v (r.assign ("dist.root"));
+
+ if (s)
+ {
+ const value& cv (optional_absolute (r, "config.dist.root"));
+
+ if (cv && !cv.empty ())
+ v = cv;
+ }
+ }
+
+ // dist.cmd
+ //
+ {
+ value& v (r.assign ("dist.cmd"));
+
+ if (s)
+ {
+ const value& cv (required (r, "config.dist.cmd", "install").first);
+
+ if (cv && !cv.empty ())
+ v = cv;
+ }
+ else
+ v = "install";
+ }
+
+ // dist.archives
+ //
+ {
+ value& v (r.assign ("dist.archives"));
+
+ if (s)
+ {
+ const value& cv (optional (r, "config.dist.archives"));
+
+ if (cv && !cv.empty ())
+ v = cv;
+ }
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/build2/dist/operation b/build2/dist/operation
new file mode 100644
index 0000000..eb5da76
--- /dev/null
+++ b/build2/dist/operation
@@ -0,0 +1,18 @@
+// file : build2/dist/operation -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_DIST_OPERATION
+#define BUILD2_DIST_OPERATION
+
+#include <build2/operation>
+
+namespace build2
+{
+ namespace dist
+ {
+ extern meta_operation_info dist;
+ }
+}
+
+#endif // BUILD2_DIST_OPERATION
diff --git a/build2/dist/operation.cxx b/build2/dist/operation.cxx
new file mode 100644
index 0000000..24bdd31
--- /dev/null
+++ b/build2/dist/operation.cxx
@@ -0,0 +1,459 @@
+// file : build2/dist/operation.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/dist/operation>
+
+#include <cassert>
+
+#include <butl/process>
+#include <butl/filesystem>
+
+#include <build2/file>
+#include <build2/dump>
+#include <build2/scope>
+#include <build2/target>
+#include <build2/context>
+#include <build2/algorithm>
+#include <build2/diagnostics>
+
+using namespace std;
+using namespace butl;
+
+namespace build2
+{
+ namespace dist
+ {
+ static void
+ dist_meta_operation_pre ()
+ {
+ // Reset the dependency state so that we don't end up with stray
+ // files from previous batches.
+ //
+ // @@ This is called too late, after we have bootstrapped the
+ // project.
+ //
+ //reset ();
+ }
+
+ static operation_id
+ dist_operation_pre (operation_id o)
+ {
+ if (o != default_id)
+ fail << "explicit operation specified for dist meta-operation";
+
+ return o;
+ }
+
+ static void
+ dist_match (action, action_targets&)
+ {
+ // Don't match anything -- see execute ().
+ }
+
+ // install -d <dir>
+ //
+ static void
+ install (const string& cmd, const dir_path&);
+
+ // install <file> <dir>
+ //
+ static void
+ install (const string& cmd, file&, const dir_path&);
+
+ // cd <root> && tar|zip ... <pkg>.<ext> <pkg>
+ //
+ static void
+ archive (const dir_path& root, const string& pkg, const string& ext);
+
+ static void
+ dist_execute (action, const action_targets& ts, bool)
+ {
+ tracer trace ("dist_execute");
+
+ // For now we assume all the targets are from the same project.
+ //
+ target& t (*static_cast<target*> (ts[0]));
+ scope* rs (t.base_scope ().root_scope ());
+
+ if (rs == nullptr)
+ fail << "out of project target " << t;
+
+ const dir_path& out_root (rs->out_path ());
+ const dir_path& src_root (rs->src_path ());
+
+ if (out_root == src_root)
+ fail << "in-tree distribution of target " << t <<
+ info << "distribution requires out-of-tree build";
+
+ // Make sure we have the necessary configuration before
+ // we get down to business.
+ //
+ auto l (rs->vars["dist.root"]);
+
+ if (!l || l->empty ())
+ fail << "unknown root distribution directory" <<
+ info << "did you forget to specify config.dist.root?";
+
+ const dir_path& dist_root (as<dir_path> (*l));
+
+ if (!dir_exists (dist_root))
+ fail << "root distribution directory " << dist_root
+ << " does not exist";
+
+ l = rs->vars["dist.package"];
+
+ if (!l || l->empty ())
+ fail << "unknown distribution package name" <<
+ info << "did you forget to set dist.package?";
+
+ const string& dist_package (as<string> (*l));
+ const string& dist_cmd (as<string> (*rs->vars["dist.cmd"]));
+
+ // Get the list of operations supported by this project. Skip
+ // default_id.
+ //
+ for (operations::size_type id (default_id + 1);
+ id < rs->operations.size ();
+ ++id)
+ {
+ const operation_info* oi (rs->operations[id]);
+ if (oi == nullptr)
+ continue;
+
+ // Note that we are not calling operation_pre/post() callbacks
+ // here since the meta operation is dist and we know what we
+ // are doing.
+ //
+ current_inner_oif = oi;
+ current_outer_oif = nullptr;
+ current_mode = oi->mode;
+ dependency_count = 0;
+
+ action a (dist_id, id);
+
+ if (verb >= 6)
+ dump (a);
+
+ for (void* v: ts)
+ {
+ target& t (*static_cast<target*> (v));
+
+ if (rs != t.base_scope ().root_scope ())
+ fail << "out of project target " << t;
+
+ level5 ([&]{trace << diag_doing (a, t);});
+
+ match (a, t);
+ }
+
+ if (verb >= 6)
+ dump (a);
+ }
+
+ // Add buildfiles that are not normally loaded as part of the
+ // project, for example, the export stub. They will still be
+ // ignored on the next step if the user explicitly marked them
+ // nodist.
+ //
+ auto add_adhoc = [&src_root, &trace] (const dir_path& d, const char* f)
+ {
+ path p (d / path (f));
+ if (file_exists (p))
+ {
+ const char* e (p.extension ());
+ targets.insert<buildfile> (
+ p.directory (),
+ p.leaf ().base ().string (),
+ &extension_pool.find (e == nullptr ? "" : e), // Specified.
+ trace);
+ }
+ };
+
+ add_adhoc (src_root, "build/export.build");
+
+ // The same for subprojects that have been loaded.
+ //
+ if (auto l = rs->vars["subprojects"])
+ {
+ for (auto p: as<subprojects> (*l))
+ {
+ const dir_path& pd (p.second);
+ dir_path out_nroot (out_root / pd);
+ scope& nrs (scopes.find (out_nroot));
+
+ if (nrs.out_path () != out_nroot) // This subproject not loaded.
+ continue;
+
+ const dir_path& src_nroot (nrs.src_path ());
+
+ if (!src_nroot.sub (src_root)) // Not a strong amalgamation.
+ continue;
+
+ add_adhoc (src_nroot, "build/export.build");
+ }
+ }
+
+ // Collect the files. We want to take the snapshot of targets
+ // since updating some of them may result in more targets being
+ // entered.
+ //
+ action_targets files;
+ const variable& dist_var (var_pool.find ("dist"));
+
+ for (const auto& pt: targets)
+ {
+ file* ft (pt->is_a<file> ());
+
+ if (ft == nullptr) // Not a file.
+ continue;
+
+ if (ft->dir.sub (src_root))
+ {
+ // Include unless explicitly excluded.
+ //
+ auto l ((*ft)[dist_var]);
+
+ if (l && !as<bool> (*l))
+ level5 ([&]{trace << "excluding " << *ft;});
+ else
+ files.push_back (ft);
+
+ continue;
+ }
+
+ if (ft->dir.sub (out_root))
+ {
+ // Exclude unless explicitly included.
+ //
+ auto l ((*ft)[dist_var]);
+
+ if (l && as<bool> (*l))
+ {
+ level5 ([&]{trace << "including " << *ft;});
+ files.push_back (ft);
+ }
+
+ continue;
+ }
+ }
+
+ // Make sure what we need to distribute is up to date.
+ //
+ {
+ if (perform.meta_operation_pre != nullptr)
+ perform.meta_operation_pre ();
+
+ current_mif = &perform;
+
+ if (perform.operation_pre != nullptr)
+ perform.operation_pre (update_id);
+
+ current_inner_oif = &update;
+ current_outer_oif = nullptr;
+ current_mode = update.mode;
+ dependency_count = 0;
+
+ action a (perform_id, update_id);
+
+ perform.match (a, files);
+ perform.execute (a, files, true); // Run quiet.
+
+ if (perform.operation_post != nullptr)
+ perform.operation_post (update_id);
+
+ if (perform.meta_operation_post != nullptr)
+ perform.meta_operation_post ();
+ }
+
+ dir_path td (dist_root / dir_path (dist_package));
+
+ // Clean up the target directory.
+ //
+ // @@ Not for incremental dist?
+ //
+ if (build2::rmdir_r (td) == rmdir_status::not_empty)
+ fail << "unable to clean target directory " << td;
+
+ install (dist_cmd, td);
+
+ // Copy over all the files.
+ //
+ for (void* v: files)
+ {
+ file& t (*static_cast<file*> (v));
+
+ // Figure out where this file is inside the target directory.
+ //
+ dir_path d (td);
+ d /= t.dir.sub (src_root)
+ ? t.dir.leaf (src_root)
+ : t.dir.leaf (out_root);
+
+ if (!dir_exists (d))
+ install (dist_cmd, d);
+
+ install (dist_cmd, t, d);
+ }
+
+ // Archive if requested.
+ //
+ if (auto l = rs->vars["dist.archives"])
+ {
+ for (const string& e: as<strings> (*l))
+ archive (dist_root, dist_package, e);
+ }
+ }
+
+ // install -d <dir>
+ //
+ static void
+ install (const string& cmd, const dir_path& d)
+ {
+ path reld (relative (d));
+
+ cstrings args {cmd.c_str (), "-d"};
+
+ args.push_back ("-m");
+ args.push_back ("755");
+ args.push_back (reld.string ().c_str ());
+ args.push_back (nullptr);
+
+ if (verb >= 2)
+ print_process (args);
+ else if (verb)
+ text << "dist -d " << d;
+
+ try
+ {
+ process pr (args.data ());
+
+ if (!pr.wait ())
+ throw failed ();
+ }
+ catch (const process_error& e)
+ {
+ error << "unable to execute " << args[0] << ": " << e.what ();
+
+ if (e.child ())
+ exit (1);
+
+ throw failed ();
+ }
+ }
+
+ // install <file> <dir>
+ //
+ static void
+ install (const string& cmd, file& t, const dir_path& d)
+ {
+ path reld (relative (d));
+ path relf (relative (t.path ()));
+
+ cstrings args {cmd.c_str ()};
+
+ // Preserve timestamps. This could becomes important if, for
+ // example, we have pre-generated sources. Note that the
+ // install-sh script doesn't support this option, while both
+ // Linux and BSD install's do.
+ //
+ args.push_back ("-p");
+
+ // Assume the file is executable if the owner has execute
+ // permission, in which case we make it executable for
+ // everyone.
+ //
+ args.push_back ("-m");
+ args.push_back (
+ (path_permissions (t.path ()) & permissions::xu) == permissions::xu
+ ? "755"
+ : "644");
+
+ args.push_back (relf.string ().c_str ());
+ args.push_back (reld.string ().c_str ());
+ args.push_back (nullptr);
+
+ if (verb >= 2)
+ print_process (args);
+ else if (verb)
+ text << "dist " << t;
+
+ try
+ {
+ process pr (args.data ());
+
+ if (!pr.wait ())
+ throw failed ();
+ }
+ catch (const process_error& e)
+ {
+ error << "unable to execute " << args[0] << ": " << e.what ();
+
+ if (e.child ())
+ exit (1);
+
+ throw failed ();
+ }
+ }
+
+ static void
+ archive (const dir_path& root, const string& pkg, const string& e)
+ {
+ string a (pkg + '.' + e);
+
+ // Delete old archive for good measure.
+ //
+ path ap (root / path (a));
+ if (file_exists (ap))
+ rmfile (ap);
+
+ // Use zip for .zip archives. Everything else goes to tar in the
+ // auto-compress mode (-a).
+ //
+ cstrings args;
+ if (e == "zip")
+ args = {"zip", "-rq", a.c_str (), pkg.c_str (), nullptr};
+ else
+ args = {"tar", "-a", "-cf", a.c_str (), pkg.c_str (), nullptr};
+
+ if (verb >= 2)
+ print_process (args);
+ else if (verb)
+ text << args[0] << " " << ap;
+
+ try
+ {
+ // Change child's working directory to dist_root.
+ //
+ process pr (root.string ().c_str (), args.data ());
+
+ if (!pr.wait ())
+ throw failed ();
+ }
+ catch (const process_error& e)
+ {
+ error << "unable to execute " << args[0] << ": " << e.what ();
+
+ if (e.child ())
+ exit (1);
+
+ throw failed ();
+ }
+ }
+
+ meta_operation_info dist {
+ "dist",
+ "distribute",
+ "distributing",
+ "has nothing to distribute", // We cannot "be distributed".
+ &dist_meta_operation_pre,
+ &dist_operation_pre,
+ &load, // normal load
+ &search, // normal search
+ &dist_match,
+ &dist_execute,
+ nullptr, // operation post
+ nullptr // meta-operation post
+ };
+ }
+}
diff --git a/build2/dist/rule b/build2/dist/rule
new file mode 100644
index 0000000..2fbf33b
--- /dev/null
+++ b/build2/dist/rule
@@ -0,0 +1,29 @@
+// file : build2/dist/rule -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_DIST_RULE
+#define BUILD2_DIST_RULE
+
+#include <build2/rule>
+#include <build2/types>
+#include <build2/target>
+#include <build2/operation>
+
+namespace build2
+{
+ namespace dist
+ {
+ class rule: public build2::rule
+ {
+ public:
+ virtual match_result
+ match (action, target&, const std::string&) const;
+
+ virtual recipe
+ apply (action, target&, const match_result&) const;
+ };
+ }
+}
+
+#endif // BUILD2_DIST_RULE
diff --git a/build2/dist/rule.cxx b/build2/dist/rule.cxx
new file mode 100644
index 0000000..10f35f5
--- /dev/null
+++ b/build2/dist/rule.cxx
@@ -0,0 +1,55 @@
+// file : build2/dist/rule.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/dist/rule>
+
+#include <build2/scope>
+#include <build2/target>
+#include <build2/algorithm>
+#include <build2/diagnostics>
+
+using namespace std;
+
+namespace build2
+{
+ namespace dist
+ {
+ match_result rule::
+ match (action, target& t, const std::string&) const
+ {
+ return t; // We always match.
+ }
+
+ recipe rule::
+ apply (action a, target& t, const match_result&) const
+ {
+ const dir_path& out_root (t.root_scope ().out_path ());
+
+ auto r (group_prerequisite_members (a, t, false));
+ for (auto i (r.begin ()); i != r.end (); ++i)
+ {
+ prerequisite_member p (*i);
+
+ // Skip prerequisites imported from other projects.
+ //
+ if (p.proj () != nullptr)
+ continue;
+
+ // If we can, go inside see-through groups.
+ //
+ if (p.type ().see_through && i.enter_group ())
+ continue;
+
+ target& pt (p.search ());
+
+ // Don't match targets that are outside of our project.
+ //
+ if (pt.dir.sub (out_root))
+ build2::match (a, pt);
+ }
+
+ return noop_recipe; // We will never be executed.
+ }
+ }
+}
diff --git a/build2/dump b/build2/dump
new file mode 100644
index 0000000..8ac600e
--- /dev/null
+++ b/build2/dump
@@ -0,0 +1,18 @@
+// file : build2/dump -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_DUMP
+#define BUILD2_DUMP
+
+#include <build2/operation>
+
+namespace build2
+{
+ // Dump the state pertaining to the specified action.
+ //
+ void
+ dump (action);
+}
+
+#endif // BUILD2_DUMP
diff --git a/build2/dump.cxx b/build2/dump.cxx
new file mode 100644
index 0000000..19172cf
--- /dev/null
+++ b/build2/dump.cxx
@@ -0,0 +1,253 @@
+// file : build2/dump.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/dump>
+
+#include <string>
+#include <cassert>
+
+#include <build2/scope>
+#include <build2/target>
+#include <build2/variable>
+#include <build2/context>
+#include <build2/diagnostics>
+
+using namespace std;
+
+namespace build2
+{
+ static void
+ dump_variable (ostream& os, const variable& var, const value& val)
+ {
+ os << var.name << " = ";
+
+ if (val.null ())
+ os << "[null]";
+ else
+ os << val.data_;
+ }
+
+ static void
+ dump_variables (ostream& os, string& ind, const variable_map& vars)
+ {
+ for (const auto& e: vars)
+ {
+ os << endl
+ << ind;
+
+ dump_variable (os, e.first, e.second);
+ }
+ }
+
+ static void
+ dump_variables (ostream& os, string& ind, const variable_type_map& vtm)
+ {
+ for (const auto& vt: vtm)
+ {
+ const target_type& t (vt.first);
+ const variable_pattern_map& vpm (vt.second);
+
+ for (const auto& vp: vpm)
+ {
+ const string p (vp.first);
+ const variable_map& vars (vp.second);
+
+ os << endl
+ << ind;
+
+ if (t != target::static_type)
+ os << t.name << '{';
+
+ os << p;
+
+ if (t != target::static_type)
+ os << '}';
+
+ os << ':';
+
+ if (vars.size () == 1)
+ {
+ os << ' ';
+ dump_variable (os, vars.begin ()->first, vars.begin ()->second);
+ }
+ else
+ {
+ os << endl
+ << ind << '{';
+ ind += " ";
+ dump_variables (os, ind, vars);
+ ind.resize (ind.size () - 2);
+ os << endl
+ << ind << '}';
+ }
+ }
+ }
+ }
+
+ static void
+ dump_target (ostream& os, string& ind, action a, const target& t)
+ {
+ os << ind << t;
+
+ if (t.group != nullptr)
+ os << "->" << *t.group;
+
+ os << ':';
+
+ for (const prerequisite& p: t.prerequisites)
+ {
+ os << ' ';
+
+ // Print it as target if one has been cached.
+ //
+ if (p.target != nullptr)
+ os << *p.target;
+ else
+ os << p;
+ }
+
+ // If the target has been matched to a rule, also print resolved
+ // prerequisite targets.
+ //
+ if (t.recipe (a))
+ {
+ bool first (true);
+ for (const target* pt: t.prerequisite_targets)
+ {
+ if (pt == nullptr) // Skipped.
+ continue;
+
+ os << (first ? " | " : " ") << *pt;
+ first = false;
+ }
+ }
+
+ // Print target-specific variables.
+ //
+ if (!t.vars.empty ())
+ {
+ os << endl
+ << ind << '{';
+ ind += " ";
+ dump_variables (os, ind, t.vars);
+ ind.resize (ind.size () - 2);
+ os << endl
+ << ind << '}';
+ }
+ }
+
+ static void
+ dump_scope (ostream& os,
+ string& ind,
+ action a,
+ 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 outer scope.
+ //
+ os << ind << relative (d) << ":" << endl
+ << ind << '{';
+
+ const dir_path* orb (relative_base);
+ relative_base = &d;
+
+ ind += " ";
+
+ bool vb (false), sb (false); // Variable/scope block.
+
+ // Target type/pattern-sepcific variables.
+ //
+ if (!p.target_vars.empty ())
+ {
+ dump_variables (os, ind, p.target_vars);
+ vb = true;
+ }
+
+ // Scope variables.
+ //
+ if (!p.vars.empty ())
+ {
+ if (vb)
+ os << endl;
+
+ dump_variables (os, ind, p.vars);
+ vb = true;
+ }
+
+ // Nested scopes of which we are an immediate parent.
+ //
+ 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;
+ vb = false;
+ }
+
+ if (sb)
+ os << endl; // Extra newline between scope blocks.
+
+ os << endl;
+ dump_scope (os, ind, a, i);
+ sb = true;
+ }
+
+ // Targets.
+ //
+ for (const auto& pt: targets)
+ {
+ const target& t (*pt);
+
+ if (&p != &t.base_scope ())
+ continue;
+
+ if (vb || sb)
+ {
+ os << endl;
+ vb = sb = false;
+ }
+
+ os << endl;
+ dump_target (os, ind, a, t);
+ }
+
+ ind.resize (ind.size () - 2);
+ relative_base = orb;
+
+ os << endl
+ << ind << '}';
+ }
+
+ void
+ dump (action a)
+ {
+ auto i (scopes.begin ());
+ assert (i->second == global_scope);
+
+ string ind;
+ ostream& os (*diag_stream);
+ dump_scope (os, ind, a, i);
+ os << endl;
+ }
+}
diff --git a/build2/file b/build2/file
new file mode 100644
index 0000000..21da0c0
--- /dev/null
+++ b/build2/file
@@ -0,0 +1,144 @@
+// file : build2/file -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_FILE
+#define BUILD2_FILE
+
+#include <map>
+#include <string>
+
+#include <build2/types>
+#include <build2/scope>
+#include <build2/variable> // list_value
+
+namespace build2
+{
+ class target;
+ class location;
+ class prerequisite_key;
+
+ using subprojects = std::map<std::string, dir_path>;
+
+ extern const dir_path build_dir; // build
+ extern const dir_path bootstrap_dir; // build/bootstrap
+
+ extern const path root_file; // build/root.build
+ extern const path bootstrap_file; // build/bootstrap.build
+ extern const path src_root_file; // build/bootstrap/src-root.build
+
+ bool
+ is_src_root (const dir_path&);
+
+ bool
+ is_out_root (const dir_path&);
+
+ // Given an src_base directory, look for a project's src_root
+ // based on the presence of known special files. Return empty
+ // path if not found.
+ //
+ dir_path
+ find_src_root (const dir_path&);
+
+ // The same as above but for project's out. Note that we also
+ // check whether a directory happens to be src_root, in case
+ // this is an in-tree build. The second argument is the out
+ // flag that is set to true if this is src_root.
+ //
+ dir_path
+ find_out_root (const dir_path&, bool* src = nullptr);
+
+ void
+ source (const path& buildfile, scope& root, scope& base);
+
+ // As above but first check if this buildfile has already been
+ // sourced for the base scope.
+ //
+ void
+ source_once (const path& buildfile, scope& root, scope& base);
+
+ // As above but checks against the specified scope rather than base.
+ //
+ void
+ source_once (const path& buildfile, scope& root, scope& base, scope& once);
+
+ // Create project's root scope. Only set the src_root variable if the
+ // passed src_root value is not empty.
+ //
+ 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
+ bootstrap_out (scope& root);
+
+ // Bootstrap the project's root scope, the src part. Return true if
+ // we loaded anything (which confirms the src_root is not bogus).
+ //
+ bool
+ bootstrap_src (scope& root);
+
+ // Create and bootstrap outer root scopes, if any. Loading is
+ // done by load_root_pre() below.
+ //
+ void
+ create_bootstrap_outer (scope& root);
+
+ // Create and bootstrap inner root scopes between root and base,
+ // if any. Return the innermost created root scope or root if
+ // none were created. Loading is done by load_root_pre() below.
+ //
+ scope&
+ create_bootstrap_inner (scope& root, const dir_path& out_base);
+
+ // Load project's root[-pre].build unless already loaded. Also
+ // make sure all outer root scopes are loaded prior to loading
+ // this root scope.
+ //
+ void
+ load_root_pre (scope& root);
+
+ // Import has two phases: the first is triggered by the import
+ // directive in the buildfile. It will try to find and load the
+ // project. Failed that, it will return the project-qualified
+ // name of the target which will be used to create a project-
+ // qualified prerequisite. This gives the rule that will be
+ // searching this prerequisite a chance to do some target-type
+ // specific search. For example, a C++ link rule can search
+ // for lib{} prerequisites in the C++ compiler default library
+ // search paths (so that we end up with functionality identical
+ // to -lfoo). If, however, the rule didn't do any of that (or
+ // failed to find anything usable), it calls the standard
+ // prerequisite search() function which sees this is a project-
+ // qualified prerequisite and goes straight to the second phase
+ // of import. Here, currently, we simply fail but in the future
+ // this will be the place where we can call custom "last resort"
+ // import hooks. For example, we can hook a package manager that
+ // will say, "Hey, I see you are trying to import foo and I see
+ // there is a package foo available in repository bar. Wanna
+ // download and use it?"
+ //
+ names
+ import (scope& base, name, const location&);
+
+ target&
+ import (const prerequisite_key&);
+}
+
+#include <build2/file.ixx>
+
+#endif // BUILD2_FILE
diff --git a/build2/file.cxx b/build2/file.cxx
new file mode 100644
index 0000000..cdaa79a
--- /dev/null
+++ b/build2/file.cxx
@@ -0,0 +1,980 @@
+// file : build2/file.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/file>
+
+#include <fstream>
+#include <utility> // move()
+#include <system_error>
+
+#include <butl/filesystem>
+
+#include <build2/scope>
+#include <build2/context>
+#include <build2/parser>
+#include <build2/prerequisite>
+#include <build2/diagnostics>
+
+#include <build2/token>
+#include <build2/lexer>
+
+using namespace std;
+using namespace butl;
+
+namespace build2
+{
+ const dir_path build_dir ("build");
+ const dir_path bootstrap_dir ("build/bootstrap");
+
+ const path root_file ("build/root.build");
+ const path bootstrap_file ("build/bootstrap.build");
+ const path src_root_file ("build/bootstrap/src-root.build");
+
+ bool
+ is_src_root (const dir_path& d)
+ {
+ // @@ Can we have root without bootstrap? I don't think so.
+ //
+ return file_exists (d / bootstrap_file) || file_exists (d / root_file);
+ }
+
+ bool
+ is_out_root (const dir_path& d)
+ {
+ return file_exists (d / src_root_file);
+ }
+
+ dir_path
+ find_src_root (const dir_path& b)
+ {
+ for (dir_path d (b); !d.root () && d != home; d = d.directory ())
+ {
+ if (is_src_root (d))
+ return d;
+ }
+
+ return dir_path ();
+ }
+
+ dir_path
+ find_out_root (const dir_path& b, bool* src)
+ {
+ for (dir_path d (b); !d.root () && d != home; d = d.directory ())
+ {
+ bool s (false);
+ if ((s = is_src_root (d)) || is_out_root (d)) // Order is important!
+ {
+ if (src != nullptr)
+ *src = s;
+
+ return d;
+ }
+ }
+
+ return dir_path ();
+ }
+
+ static void
+ source (const path& bf, scope& root, scope& base, bool boot)
+ {
+ tracer trace ("source");
+
+ try
+ {
+ ifstream ifs (bf.string ());
+ if (!ifs.is_open ())
+ fail << "unable to open " << bf;
+
+ ifs.exceptions (ifstream::failbit | ifstream::badbit);
+
+ level5 ([&]{trace << "sourcing " << bf;});
+
+ parser p (boot);
+ p.parse_buildfile (ifs, bf, root, base);
+ }
+ catch (const ifstream::failure&)
+ {
+ fail << "unable to read buildfile " << bf;
+ }
+ }
+
+ void
+ source (const path& bf, scope& root, scope& base)
+ {
+ return source (bf, root, base, false);
+ }
+
+ void
+ source_once (const path& bf, scope& root, scope& base, scope& once)
+ {
+ tracer trace ("source_once");
+
+ if (!once.buildfiles.insert (bf).second)
+ {
+ level5 ([&]{trace << "skipping already sourced " << bf;});
+ return;
+ }
+
+ source (bf, root, base);
+ }
+
+ scope&
+ create_root (const dir_path& out_root, const dir_path& src_root)
+ {
+ 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
+ // additional meta/operations being added.
+ //
+ if (rs.meta_operations.empty ())
+ {
+ rs.meta_operations.insert (perform_id, perform);
+
+ rs.operations.insert (default_id, default_);
+ rs.operations.insert (update_id, update);
+ rs.operations.insert (clean_id, clean);
+ }
+
+ // If this is already a root scope, verify that things are
+ // consistent.
+ //
+ {
+ value& v (rs.assign ("out_root"));
+
+ if (!v)
+ v = out_root;
+ else
+ {
+ const dir_path& p (as<dir_path> (v));
+
+ if (p != out_root)
+ fail << "new out_root " << out_root << " does not match "
+ << "existing " << p;
+ }
+ }
+
+ if (!src_root.empty ())
+ {
+ value& v (rs.assign ("src_root"));
+
+ if (!v)
+ v = src_root;
+ else
+ {
+ const dir_path& p (as<dir_path> (v));
+
+ if (p != src_root)
+ fail << "new src_root " << src_root << " does not match "
+ << "existing " << p;
+ }
+ }
+
+ return rs;
+ }
+
+ 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.out_path () / path ("build/bootstrap/src-root.build"));
+
+ if (!file_exists (bf))
+ return;
+
+ //@@ TODO: if bootstrap files can source other bootstrap files
+ // (the way to express dependecies), then we need a way to
+ // prevent multiple sourcing. We handle it here but we still
+ // need something like source_once (once [scope] source).
+ //
+ source_once (bf, root, root);
+ }
+
+ // Extract the specified variable value from a buildfile. It is
+ // expected to be the first non-comment line and not to rely on
+ // any variable expansion other than those from the global scope.
+ //
+ static value
+ extract_variable (const path& bf, const char* var)
+ {
+ try
+ {
+ ifstream ifs (bf.string ());
+ if (!ifs.is_open ())
+ fail << "unable to open " << bf;
+
+ ifs.exceptions (ifstream::failbit | ifstream::badbit);
+
+ path rbf (diag_relative (bf));
+
+ lexer lex (ifs, rbf.string ());
+ token t (lex.next ());
+ token_type tt;
+
+ if (t.type != token_type::name || t.value != var ||
+ ((tt = lex.next ().type) != token_type::equal &&
+ tt != token_type::equal_plus &&
+ tt != token_type::plus_equal))
+ {
+ error << "variable '" << var << "' expected as first line in " << rbf;
+ throw failed (); // Suppress "used uninitialized" warning.
+ }
+
+ parser p;
+ temp_scope tmp (*global_scope);
+ p.parse_variable (lex, tmp, t.value, tt);
+
+ auto l (tmp.vars[var]);
+ assert (l.defined ());
+ value& v (*l);
+ return move (v); // Steal the value, the scope is going away.
+ }
+ catch (const ifstream::failure&)
+ {
+ fail << "unable to read buildfile " << bf;
+ }
+
+ return value (); // Never reaches.
+ }
+
+ // Extract the project name from bootstrap.build.
+ //
+ static string
+ find_project_name (const dir_path& out_root,
+ const dir_path& fallback_src_root,
+ bool* src_hint = nullptr)
+ {
+ tracer trace ("find_project_name");
+
+ // Load the project name. If this subdirectory is the subproject's
+ // src_root, then we can get directly to that. Otherwise, we first
+ // have to discover its src_root.
+ //
+ const dir_path* src_root;
+ value src_root_v; // Need it to live until the end.
+
+ if (src_hint != nullptr ? *src_hint : is_src_root (out_root))
+ src_root = &out_root;
+ else
+ {
+ path f (out_root / src_root_file);
+
+ if (!fallback_src_root.empty () && !file_exists (f))
+ src_root = &fallback_src_root;
+ else
+ {
+ src_root_v = extract_variable (f, "src_root");
+ src_root = &as<dir_path> (src_root_v);
+ level5 ([&]{trace << "extracted src_root " << *src_root << " for "
+ << out_root;});
+ }
+ }
+
+ string name;
+ {
+ value v (extract_variable (*src_root / bootstrap_file, "project"));
+ name = move (as<string> (v));
+ }
+
+ level5 ([&]{trace << "extracted project name '" << name << "' for "
+ << *src_root;});
+ return name;
+ }
+
+ // Scan the specified directory for any subprojects. If a subdirectory
+ // is a subproject, then enter it into the map, handling the duplicates.
+ // Otherwise, scan the subdirectory recursively.
+ //
+ static void
+ find_subprojects (subprojects& sps,
+ const dir_path& d,
+ const dir_path& root,
+ bool out)
+ {
+ tracer trace ("find_subprojects");
+
+ for (const dir_entry& de: dir_iterator (d))
+ {
+ // 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.
+ //
+ try
+ {
+ if (de.type () != entry_type::directory)
+ continue;
+ }
+ catch (const system_error& e)
+ {
+ continue;
+ }
+
+ dir_path sd (d / path_cast<dir_path> (de.path ()));
+
+ bool src (false);
+ if (!((out && is_out_root (sd)) || (src = is_src_root (sd))))
+ {
+ find_subprojects (sps, sd, root, out);
+ continue;
+ }
+
+ // Calculate relative subdirectory for this subproject.
+ //
+ dir_path dir (sd.leaf (root));
+ level5 ([&]{trace << "subproject " << sd << " as " << dir;});
+
+ // Load its name. Note that here we don't use fallback src_root
+ // since this function is used to scan both out_root and src_root.
+ //
+ string name (find_project_name (sd, dir_path (), &src));
+
+ // If the name is empty, then is is an unnamed project. While the
+ // 'project' variable stays empty, here we come up with a surrogate
+ // name for a key. The idea is that such a key should never conflict
+ // with a real project name. We ensure this by using the project's
+ // sub-directory and appending trailing '/' to it.
+ //
+ if (name.empty ())
+ name = dir.posix_string () + '/';
+
+ // @@ Can't use move() because we may need the values in diagnostics
+ // below. Looks like C++17 try_emplace() is what we need.
+ //
+ auto rp (sps.emplace (name, dir));
+
+ // Handle duplicates.
+ //
+ if (!rp.second)
+ {
+ const dir_path& dir1 (rp.first->second);
+
+ if (dir != dir1)
+ fail << "inconsistent subproject directories for " << name <<
+ info << "first alternative: " << dir1 <<
+ info << "second alternative: " << dir;
+
+ level6 ([&]{trace << "skipping duplicate";});
+ }
+ }
+ }
+
+ bool
+ bootstrap_src (scope& root)
+ {
+ tracer trace ("bootstrap_src");
+
+ bool r (false);
+
+ const dir_path& out_root (root.out_path ());
+ const dir_path& src_root (root.src_path ());
+
+ path bf (src_root / path ("build/bootstrap.build"));
+
+ if (file_exists (bf))
+ {
+ // 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.
+ //
+ if (root.buildfiles.insert (bf).second)
+ source (bf, root, root, true);
+ else
+ level5 ([&]{trace << "skipping already sourced " << bf;});
+
+ r = true;
+ }
+
+ // See if we are a part of an amalgamation. There are two key
+ // players: the outer root scope which may already be present
+ // (i.e., we were loaded as part of an amalgamation) and the
+ // amalgamation variable that may or may not be set by the
+ // user (in bootstrap.build) or by an earlier call to this
+ // function for the same scope. When set by the user, the
+ // empty special value means that the project shall not be
+ // amalgamated (and which we convert to NULL below). When
+ // calculated, the NULL value indicates that we are not
+ // amalgamated.
+ //
+ {
+ auto rp (root.vars.assign ("amalgamation")); // Set NULL by default.
+ value& v (rp.first);
+
+ if (v && v.empty ()) // Convert empty to NULL.
+ v = nullptr;
+
+ if (scope* aroot = root.parent_scope ()->root_scope ())
+ {
+ const dir_path& ad (aroot->out_path ());
+ dir_path rd (ad.relative (out_root));
+
+ // If we already have the amalgamation variable set, verify
+ // that aroot matches its value.
+ //
+ if (!rp.second)
+ {
+ if (!v)
+ {
+ fail << out_root << " cannot be amalgamated" <<
+ info << "amalgamated by " << ad;
+ }
+ else
+ {
+ const dir_path& vd (as<dir_path> (v));
+
+ if (vd != rd)
+ {
+ fail << "inconsistent amalgamation of " << out_root <<
+ info << "specified: " << vd <<
+ info << "actual: " << rd << " by " << ad;
+ }
+ }
+ }
+ else
+ {
+ // Otherwise, use the outer root as our amalgamation.
+ //
+ level5 ([&]{trace << out_root << " amalgamated as " << rd;});
+ v = move (rd);
+ }
+ }
+ else if (rp.second)
+ {
+ // If there is no outer root and the amalgamation variable
+ // hasn't been set, then we need to check if any of the
+ // outer directories is a project's out_root. If so, then
+ // that's our amalgamation.
+ //
+ const dir_path& ad (find_out_root (out_root.directory ()));
+
+ if (!ad.empty ())
+ {
+ dir_path rd (ad.relative (out_root));
+ level5 ([&]{trace << out_root << " amalgamated as " << rd;});
+ v = move (rd);
+ }
+ }
+ }
+
+ // See if we have any subprojects. In a sense, this is the other
+ // side/direction of the amalgamation logic above. Here, the
+ // subprojects variable may or may not be set by the user (in
+ // bootstrap.build) or by an earlier call to this function for
+ // the same scope. When set by the user, the empty special value
+ // means that there are no subproject and none should be searched
+ // for (and which we convert to NULL below). Otherwise, it is a
+ // list of directory[=project] pairs. The directory must be
+ // relative to our out_root. If the project name is not specified,
+ // then we have to figure it out. When subprojects are calculated,
+ // the NULL value indicates that we found no subprojects.
+ //
+ {
+ const variable& var (var_pool.find ("subprojects"));
+ auto rp (root.vars.assign(var)); // Set NULL by default.
+ value& v (rp.first);
+
+ if (rp.second)
+ {
+ // No subprojects set so we need to figure out if there are any.
+ //
+ // First we are going to scan our out_root and find all the
+ // pre-configured subprojects. Then, if out_root != src_root,
+ // we are going to do the same for src_root. Here, however,
+ // we need to watch out for duplicates.
+ //
+ subprojects sps;
+
+ if (dir_exists (out_root))
+ find_subprojects (sps, out_root, out_root, true);
+
+ if (out_root != src_root)
+ find_subprojects (sps, src_root, src_root, false);
+
+ if (!sps.empty ()) // Keep it NULL if no subprojects.
+ v = move (sps);
+ }
+ else if (v)
+ {
+ // Convert empty to NULL.
+ //
+ if (v.empty ())
+ v = nullptr;
+ else
+ {
+ // Pre-scan the value and convert it to the "canonical" form,
+ // that is, a list of simple=dir pairs.
+ //
+ for (auto i (v.data_.begin ()); i != v.data_.end (); ++i)
+ {
+ bool p (i->pair != '\0');
+
+ if (p)
+ {
+ // Project name.
+ //
+ if (!assign<string> (*i) || as<string> (*i).empty ())
+ fail << "expected project name instead of '" << *i << "' in "
+ << "the subprojects variable";
+
+ ++i; // Got to have the second half of the pair.
+ }
+
+ if (!assign<dir_path> (*i))
+ fail << "expected directory instead of '" << *i << "' in the "
+ << "subprojects variable";
+
+ auto& d (as<dir_path> (*i));
+
+ // Figure out the project name if the user didn't specify one.
+ //
+ if (!p)
+ {
+ // Pass fallback src_root since this is a subproject that
+ // was specified by the user so it is most likely in our
+ // src.
+ //
+ string n (find_project_name (out_root / d, src_root / d));
+
+ // See find_subprojects() for details on unnamed projects.
+ //
+ if (n.empty ())
+ n = d.posix_string () + '/';
+
+ i = v.data_.emplace (i, move (n));
+
+ i->pair = '=';
+ ++i;
+ }
+ }
+
+ // Make it of the map type.
+ //
+ assign<subprojects> (v, var);
+ }
+ }
+ }
+
+ return r;
+ }
+
+ void
+ create_bootstrap_outer (scope& root)
+ {
+ auto l (root.vars["amalgamation"]);
+
+ if (!l)
+ return;
+
+ const dir_path& d (as<dir_path> (*l));
+ dir_path out_root (root.out_path () / d);
+ out_root.normalize ();
+
+ // src_root is a bit more complicated. Here we have three cases:
+ //
+ // 1. Amalgamation's src_root is "parallel" to the sub-project's.
+ // 2. Amalgamation's src_root is the same as its out_root.
+ // 3. Some other pre-configured (via src-root.build) src_root.
+ //
+ // So we need to try all these cases in some sensible order.
+ // #3 should probably be tried first since that src_root was
+ // explicitly configured by the user. After that, #2 followed
+ // by #1 seems reasonable.
+ //
+ scope& rs (create_root (out_root, dir_path ()));
+ bootstrap_out (rs); // #3 happens here, if at all.
+
+ value& v (rs.assign ("src_root"));
+
+ if (!v)
+ {
+ if (is_src_root (out_root)) // #2
+ v = out_root;
+ else // #1
+ {
+ dir_path src_root (root.src_path () / d);
+ src_root.normalize ();
+ v = move (src_root);
+ }
+ }
+
+ setup_root (rs);
+
+ bootstrap_src (rs);
+ create_bootstrap_outer (rs);
+
+ // Check if we are strongly amalgamated by this outer root scope.
+ //
+ if (root.src_path ().sub (rs.src_path ()))
+ root.strong_ = rs.strong_scope (); // Itself or some outer scope.
+ }
+
+ scope&
+ create_bootstrap_inner (scope& root, const dir_path& out_base)
+ {
+ if (auto l = root.vars["subprojects"])
+ {
+ for (const name& n: *l)
+ {
+ if (n.pair != '\0')
+ continue; // Skip project names.
+
+ dir_path out_root (root.out_path () / n.dir);
+
+ if (!out_base.sub (out_root))
+ continue;
+
+ // The same logic to src_root as in create_bootstrap_outer().
+ //
+ scope& rs (create_root (out_root, dir_path ()));
+ bootstrap_out (rs);
+
+ value& v (rs.assign ("src_root"));
+
+ if (!v)
+ v = is_src_root (out_root)
+ ? out_root
+ : (root.src_path () / n.dir);
+
+ setup_root (rs);
+
+ bootstrap_src (rs);
+
+ // Check if we strongly amalgamated this inner root scope.
+ //
+ if (rs.src_path ().sub (root.src_path ()))
+ rs.strong_ = root.strong_scope (); // Itself or some outer scope.
+
+ // See if there are more inner roots.
+ //
+ return create_bootstrap_inner (rs, out_base);
+ }
+ }
+
+ return root;
+ }
+
+ void
+ load_root_pre (scope& root)
+ {
+ tracer trace ("root_pre");
+
+ // First load outer roots, if any.
+ //
+ if (scope* rs = root.parent_scope ()->root_scope ())
+ load_root_pre (*rs);
+
+ // Finish off loading bootstrapped modules.
+ //
+ for (auto& p: root.modules)
+ {
+ const string& n (p.first);
+ module_state& s (p.second);
+
+ if (s.boot)
+ {
+ load_module (false, n, root, root, s.loc);
+ assert (!s.boot);
+ }
+ }
+
+ // Load root.build.
+ //
+ path bf (root.src_path () / path ("build/root.build"));
+
+ if (file_exists (bf))
+ source_once (bf, root, root);
+ }
+
+ names
+ import (scope& ibase, name target, const location& loc)
+ {
+ tracer trace ("import");
+
+ // If there is no project specified for this target, then our
+ // run will be short and sweet: we simply return it as empty-
+ // project-qualified and let someone else (e.g., a rule) take
+ // a stab at it.
+ //
+ if (target.unqualified ())
+ {
+ target.proj = &project_name_pool.find ("");
+ return names {move (target)};
+ }
+
+ // Otherwise, get the project name and convert the target to
+ // unqualified.
+ //
+ const string& project (*target.proj);
+ target.proj = nullptr;
+
+ scope& iroot (*ibase.root_scope ());
+
+ // Figure out this project's out_root.
+ //
+ dir_path out_root;
+ dir_path fallback_src_root; // We have seen this already, haven't we..?
+
+ // First search subprojects, starting with our root and then trying
+ // outer roots for as long as we are inside an amalgamation.
+ //
+ for (scope* r (&iroot);; r = r->parent_scope ()->root_scope ())
+ {
+ // First check the amalgamation itself.
+ //
+ if (r != &iroot && as<string> (*r->vars["project"]) == project)
+ {
+ out_root = r->out_path ();
+ fallback_src_root = r->src_path ();
+ break;
+ }
+
+ if (auto l = r->vars["subprojects"])
+ {
+ const auto& m (as<subprojects> (*l));
+ auto i (m.find (project));
+
+ if (i != m.end ())
+ {
+ const dir_path& d ((*i).second);
+ out_root = r->out_path () / d;
+ fallback_src_root = r->src_path () / d;
+ break;
+ }
+ }
+
+ if (!r->vars["amalgamation"])
+ break;
+ }
+
+ // Then try the config.import.* mechanism.
+ //
+ if (out_root.empty ())
+ {
+ const variable& var (
+ var_pool.find ("config.import." + project, dir_path_type));
+
+ if (auto l = iroot[var])
+ {
+ out_root = as<dir_path> (*l);
+
+ if (l.belongs (*global_scope)) // A value from command line.
+ {
+ // Process the path by making it absolute and normalized.
+ //
+ if (out_root.relative ())
+ out_root = work / out_root;
+
+ out_root.normalize ();
+
+ // Set on our root scope (part of our configuration).
+ //
+ iroot.assign (var) = out_root;
+
+ // Also update the command-line value. This is necessary to avoid
+ // a warning issued by the config module about global/root scope
+ // value mismatch. Not very clean.
+ //
+ dir_path& d (as<dir_path> (const_cast<value&> (*l)));
+ if (d != out_root)
+ d = out_root;
+ }
+ }
+ else
+ {
+ // If we can't find the project, convert it back into qualified
+ // target and return to let someone else (e.g., a rule) to take
+ // a stab at it.
+ //
+ target.proj = &project;
+ level5 ([&]{trace << "postponing " << target;});
+ return names {move (target)};
+ }
+ }
+
+ // Bootstrap the imported root scope. This is pretty similar to
+ // what we do in main() except that here we don't try to guess
+ // src_root.
+ //
+ dir_path src_root (is_src_root (out_root) ? out_root : dir_path ());
+ scope& root (create_root (out_root, src_root));
+
+ bootstrap_out (root);
+
+ // Check that the bootstrap process set src_root.
+ //
+ if (auto l = root.vars["src_root"])
+ {
+ const dir_path& p (as<dir_path> (*l));
+
+ if (!src_root.empty () && p != src_root)
+ fail (loc) << "bootstrapped src_root " << p << " does not match "
+ << "discovered " << src_root;
+ }
+ // Otherwise, use fallback if available.
+ //
+ else if (!fallback_src_root.empty ())
+ {
+ value& v (root.assign ("src_root"));
+ v = move (fallback_src_root);
+ }
+ 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
+ // load_root_pre() below.
+ //
+ create_bootstrap_outer (root);
+
+ // Load the imported root scope.
+ //
+ load_root_pre (root);
+
+ // Create a temporary scope so that the export stub does not mess
+ // up any of our variables.
+ //
+ temp_scope ts (ibase);
+
+ // "Pass" the imported project's roots to the stub.
+ //
+ ts.assign ("out_root") = move (out_root);
+ ts.assign ("src_root") = move (src_root);
+
+ // Also pass the target being imported.
+ //
+ {
+ value& v (ts.assign ("target"));
+
+ if (!target.empty ()) // Otherwise leave NULL.
+ v = move (target);
+ }
+
+ // Load the export stub. Note that it is loaded in the context
+ // of the importing project, not the imported one. The export
+ // stub will normally switch to the imported root scope at some
+ // point.
+ //
+ path es (root.src_path () / path ("build/export.build"));
+
+ try
+ {
+ ifstream ifs (es.string ());
+ if (!ifs.is_open ())
+ fail (loc) << "unable to open " << es;
+
+ ifs.exceptions (ifstream::failbit | ifstream::badbit);
+
+ level5 ([&]{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?
+ //
+ parser p;
+ return p.parse_export_stub (ifs, es, iroot, ts);
+ }
+ catch (const ifstream::failure&)
+ {
+ fail (loc) << "unable to read buildfile " << es;
+ }
+
+ return names (); // Never reached.
+ }
+
+ target&
+ import (const prerequisite_key& pk)
+ {
+ assert (pk.proj != nullptr);
+ const string& p (*pk.proj);
+
+ // @@ We no longer have location. This is especially bad for the
+ // empty case, i.e., where do I need to specify the project
+ // name)? Looks like the only way to do this is to keep location
+ // in name and then in prerequisite. Perhaps one day...
+ //
+ if (!p.empty ())
+ fail << "unable to import target " << pk <<
+ info << "consider explicitly specifying its project out_root via the "
+ << "config.import." << p << " command line variable";
+ else
+ fail << "unable to import target " << pk <<
+ info << "consider adding its installation location" <<
+ info << "or explicitly specifying its project name";
+
+ throw failed (); // No return.
+ }
+}
diff --git a/build2/file.ixx b/build2/file.ixx
new file mode 100644
index 0000000..b446ef4
--- /dev/null
+++ b/build2/file.ixx
@@ -0,0 +1,12 @@
+// file : build2/file.ixx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+namespace build2
+{
+ inline void
+ source_once (const path& bf, scope& root, scope& base)
+ {
+ return source_once (bf, root, base, base);
+ }
+}
diff --git a/build2/install/module b/build2/install/module
new file mode 100644
index 0000000..91b5ecb
--- /dev/null
+++ b/build2/install/module
@@ -0,0 +1,26 @@
+// file : build2/install/module -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_INSTALL_MODULE
+#define BUILD2_INSTALL_MODULE
+
+#include <build2/types>
+#include <build2/utility>
+
+#include <build2/module>
+
+namespace build2
+{
+ namespace install
+ {
+ extern "C" void
+ install_boot (scope&, const location&, unique_ptr<module>&);
+
+ extern "C" bool
+ install_init (
+ scope&, scope&, const location&, unique_ptr<module>&, bool, bool);
+ }
+}
+
+#endif // BUILD2_INSTALL_MODULE
diff --git a/build2/install/module.cxx b/build2/install/module.cxx
new file mode 100644
index 0000000..c6ad717
--- /dev/null
+++ b/build2/install/module.cxx
@@ -0,0 +1,188 @@
+// file : build2/install/module.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/install/module>
+
+#include <build2/scope>
+#include <build2/target>
+#include <build2/rule>
+#include <build2/operation>
+#include <build2/diagnostics>
+
+#include <build2/config/utility>
+
+#include <build2/install/rule>
+#include <build2/install/utility>
+#include <build2/install/operation>
+
+using namespace std;
+using namespace butl;
+
+namespace build2
+{
+ namespace install
+ {
+ // Set install.<name>.* values based on config.install.<name>.* ones
+ // or the defaults. If none of config.install.* values were specified,
+ // then we do omitted/delayed configuration. Note that we still need
+ // to set all the install.* values to defaults, as if we had the
+ // default configuration.
+ //
+ // If override is true, then override values that came from outer
+ // configurations. We have to do this for paths that contain the
+ // package name.
+ //
+ template <typename T>
+ static void
+ set_var (bool spec,
+ scope& r,
+ const char* name,
+ const char* var,
+ const T* dv,
+ bool override = false)
+ {
+ string vn;
+ const value* cv (nullptr);
+
+ if (spec)
+ {
+ vn = "config.install.";
+ vn += name;
+ vn += var;
+ const variable& vr (
+ var_pool.find (move (vn), &value_traits<T>::value_type));
+
+ cv = dv != nullptr
+ ? &config::required (r, vr, *dv, override).first.get ()
+ : &config::optional (r, vr);
+ }
+
+ vn = "install.";
+ vn += name;
+ vn += var;
+ const variable& vr (
+ var_pool.find (move (vn), &value_traits<T>::value_type));
+
+ value& v (r.assign (vr));
+
+ if (spec)
+ {
+ if (*cv && !cv->empty ())
+ v = *cv;
+ }
+ else
+ {
+ if (dv != nullptr)
+ v = *dv;
+ }
+ }
+
+ static void
+ set_dir (bool s, // specified
+ scope& r, // root scope
+ const char* n, // var name
+ const string& ps, // path (as string)
+ const string& fm = string (), // file mode
+ const string& dm = string (), // dir mode
+ const string& c = string (), // command
+ bool o = false) // override
+ {
+ dir_path p (ps);
+ set_var (s, r, n, "", p.empty () ? nullptr : &p, o);
+ set_var (s, r, n, ".mode", fm.empty () ? nullptr : &fm);
+ set_var (s, r, n, ".dir_mode", dm.empty () ? nullptr : &dm);
+ set_var<string> (s, r, n, ".sudo", nullptr);
+ set_var (s, r, n, ".cmd", c.empty () ? nullptr : &c);
+ set_var<strings> (s, r, n, ".options", nullptr);
+ }
+
+ static alias_rule alias_;
+ static file_rule file_;
+
+ extern "C" void
+ install_boot (scope& r, const location&, unique_ptr<module>&)
+ {
+ tracer trace ("install::boot");
+
+ level5 ([&]{trace << "for " << r.out_path ();});
+
+ // Register the install operation.
+ //
+ r.operations.insert (install_id, install);
+ }
+
+ extern "C" bool
+ install_init (scope& r,
+ scope& b,
+ const location& l,
+ unique_ptr<module>&,
+ bool first,
+ bool)
+ {
+ tracer trace ("install::init");
+
+ if (!first)
+ {
+ warn (l) << "multiple install module initializations";
+ return true;
+ }
+
+ const dir_path& out_root (r.out_path ());
+ level5 ([&]{trace << "for " << out_root;});
+
+ // Enter module variables.
+ //
+ // Note that the set_dir() calls below enter some more.
+ //
+ if (first)
+ {
+ auto& v (var_pool);
+
+ v.find ("install", dir_path_type);
+ }
+
+ // Register our alias and file installer rule.
+ //
+ b.rules.insert<alias> (perform_install_id, "install.alias", alias_);
+ b.rules.insert<file> (perform_install_id, "install.file", file_);
+
+ // Configuration.
+ //
+ // Note that we don't use any defaults for root -- the location
+ // must be explicitly specified or the installer will complain
+ // if and when we try to install.
+ //
+ if (first)
+ {
+ bool s (config::specified (r, "config.install"));
+ const string& n (as<string> (*r["project"]));
+
+ set_dir (s, r, "root", "", "", "755", "install");
+ set_dir (s, r, "data_root", "root", "644");
+ set_dir (s, r, "exec_root", "root", "755");
+
+ set_dir (s, r, "sbin", "exec_root/sbin");
+ set_dir (s, r, "bin", "exec_root/bin");
+ set_dir (s, r, "lib", "exec_root/lib");
+ set_dir (s, r, "libexec", "exec_root/libexec/" + n, "", "", "", true);
+
+ set_dir (s, r, "data", "data_root/share/" + n, "", "", "", true);
+ set_dir (s, r, "include", "data_root/include");
+
+ set_dir (s, r, "doc", "data_root/share/doc/" + n, "", "", "", true);
+ set_dir (s, r, "man", "data_root/share/man");
+
+ set_dir (s, r, "man1", "man/man1");
+ }
+
+ // Configure "installability" for built-in target types.
+ //
+ path<doc> (b, dir_path ("doc")); // Install into install.doc.
+ path<man> (b, dir_path ("man")); // Install into install.man.
+ path<man1> (b, dir_path ("man1")); // Install into install.man1.
+
+ return true;
+ }
+ }
+}
diff --git a/build2/install/operation b/build2/install/operation
new file mode 100644
index 0000000..20630d5
--- /dev/null
+++ b/build2/install/operation
@@ -0,0 +1,18 @@
+// file : build2/install/operation -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_INSTALL_OPERATION
+#define BUILD2_INSTALL_OPERATION
+
+#include <build2/operation>
+
+namespace build2
+{
+ namespace install
+ {
+ extern operation_info install;
+ }
+}
+
+#endif // BUILD2_INSTALL_OPERATION
diff --git a/build2/install/operation.cxx b/build2/install/operation.cxx
new file mode 100644
index 0000000..eb7cdb1
--- /dev/null
+++ b/build2/install/operation.cxx
@@ -0,0 +1,32 @@
+// file : build2/install/operation.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/install/operation>
+
+using namespace std;
+using namespace butl;
+
+namespace build2
+{
+ namespace install
+ {
+ static operation_id
+ install_pre (meta_operation_id mo)
+ {
+ // Run update as a pre-operation, unless we are disfiguring.
+ //
+ return mo != disfigure_id ? update_id : 0;
+ }
+
+ operation_info install {
+ "install",
+ "install",
+ "installing",
+ "has nothing to install", // We cannot "be installed".
+ execution_mode::first,
+ &install_pre,
+ nullptr
+ };
+ }
+}
diff --git a/build2/install/rule b/build2/install/rule
new file mode 100644
index 0000000..aeb7f13
--- /dev/null
+++ b/build2/install/rule
@@ -0,0 +1,49 @@
+// file : build2/install/rule -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_INSTALL_RULE
+#define BUILD2_INSTALL_RULE
+
+#include <build2/rule>
+#include <build2/types>
+#include <build2/target>
+#include <build2/operation>
+
+namespace build2
+{
+ namespace install
+ {
+ class alias_rule: public rule
+ {
+ public:
+ virtual match_result
+ match (action, target&, const std::string&) const;
+
+ virtual recipe
+ apply (action, target&, const match_result&) const;
+ };
+
+ class file_rule: public rule
+ {
+ public:
+ virtual match_result
+ match (action, target&, const std::string&) const;
+
+ // Return NULL if this prerequisite should be ignored and pointer to its
+ // target otherwise. The default implementation ignores prerequsites that
+ // are outside of this target's project.
+ //
+ virtual target*
+ filter (action, target&, prerequisite_member) const;
+
+ virtual recipe
+ apply (action, target&, const match_result&) const;
+
+ static target_state
+ perform_install (action, target&);
+ };
+ }
+}
+
+#endif // BUILD2_INSTALL_RULE
diff --git a/build2/install/rule.cxx b/build2/install/rule.cxx
new file mode 100644
index 0000000..16feca8
--- /dev/null
+++ b/build2/install/rule.cxx
@@ -0,0 +1,410 @@
+// file : build2/install/rule.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/install/rule>
+
+#include <butl/process>
+#include <butl/filesystem>
+
+#include <build2/scope>
+#include <build2/target>
+#include <build2/algorithm>
+#include <build2/diagnostics>
+
+#include <build2/config/utility>
+
+using namespace std;
+using namespace butl;
+
+namespace build2
+{
+ namespace install
+ {
+ // Lookup the install or install.* variable. Return NULL if
+ // not found or if the value is the special 'false' name (which
+ // means do not install). T is either scope or target.
+ //
+ template <typename T>
+ static const dir_path*
+ lookup (T& t, const string& var)
+ {
+ auto l (t[var]);
+
+ if (!l)
+ return nullptr;
+
+ const dir_path& r (as<dir_path> (*l));
+ return r.simple () && r.string () == "false" ? nullptr : &r;
+ }
+
+ // alias_rule
+ //
+ match_result alias_rule::
+ match (action, target& t, const std::string&) const
+ {
+ return t;
+ }
+
+ recipe alias_rule::
+ apply (action a, target& t, const match_result&) const
+ {
+ tracer trace ("install::alias_rule::apply");
+
+ for (prerequisite p: group_prerequisites (t))
+ {
+ target& pt (search (p));
+
+ // Check if this prerequisite is explicitly "not installable",
+ // that is, there is the 'install' variable and its value is
+ // false.
+ //
+ // At first, this might seem redundand since we could have let
+ // the file_rule below take care of it. The nuance is this: this
+ // prerequsite can be in a different subproject that hasn't loaded
+ // the install module (and therefore has no file_rule registered).
+ // The typical example would be the 'tests' subproject.
+ //
+ auto l (pt["install"]);
+
+ if (l && as<dir_path> (*l).string () == "false")
+ {
+ level5 ([&]{trace << "ignoring " << pt;});
+ continue;
+ }
+
+ build2::match (a, pt);
+ t.prerequisite_targets.push_back (&pt);
+ }
+
+ return default_recipe;
+ }
+
+ // file_rule
+ //
+
+ match_result file_rule::
+ match (action a, target& t, const std::string&) const
+ {
+ // First determine if this target should be installed (called
+ // "installable" for short).
+ //
+ if (lookup (t, "install") == nullptr)
+ // If this is the update pre-operation, signal that we don't match so
+ // that some other rule can take care of it.
+ //
+ return a.operation () == update_id ? nullptr : match_result (t, false);
+
+ match_result mr (t, true);
+
+ // If this is the update pre-operation, change the recipe action
+ // to (update, 0) (i.e., "unconditional update").
+ //
+ if (a.operation () == update_id)
+ mr.recipe_action = action (a.meta_operation (), update_id);
+
+ return mr;
+ }
+
+ target* file_rule::
+ filter (action, target& t, prerequisite_member p) const
+ {
+ target& pt (p.search ());
+ return pt.in (t.root_scope ()) ? &pt : nullptr;
+ }
+
+ recipe file_rule::
+ apply (action a, target& t, const match_result& mr) const
+ {
+ if (!mr.bvalue) // Not installable.
+ return noop_recipe;
+
+ // Ok, if we are here, then this means:
+ //
+ // 1. This target is installable.
+ // 2. The action is either
+ // a. (perform, install, 0) or
+ // b. (*, update, install)
+ //
+ // In both cases, the next step is to search, match, and collect
+ // all the installable prerequisites.
+ //
+ // @@ Perhaps if [noinstall] will be handled by the
+ // group_prerequisite_members machinery, then we can just
+ // run standard search_and_match()? Will need an indicator
+ // that it was forced (e.g., [install]) for filter() below.
+ //
+ for (prerequisite_member p: group_prerequisite_members (a, t))
+ {
+ // Ignore unresolved targets that are imported from other projects.
+ // We are definitely not installing those.
+ //
+ if (p.proj () != nullptr)
+ continue;
+
+ // Let a customized rule have its say.
+ //
+ target* pt (filter (a, t, p));
+ if (pt == nullptr)
+ continue;
+
+ // See if the user instructed us not to install it.
+ //
+ auto l ((*pt)["install"]);
+ if (l && as<dir_path> (*l).string () == "false")
+ continue;
+
+ build2::match (a, *pt);
+
+ // If the matched rule returned noop_recipe, then the target
+ // state will be set to unchanged as an optimization. Use this
+ // knowledge to optimize things on our side as well since this
+ // will help a lot in case of any static installable content
+ // (headers, documentation, etc).
+ //
+ if (pt->state () != target_state::unchanged)
+ t.prerequisite_targets.push_back (pt);
+ else
+ unmatch (a, *pt); // No intent to execute.
+ }
+
+ // This is where we diverge depending on the operation. In the
+ // update pre-operation, we need to make sure that this target
+ // as well as all its installable prerequisites are up to date.
+ //
+ if (a.operation () == update_id)
+ {
+ // Save the prerequisite targets that we found since the
+ // call to match_delegate() below will wipe them out.
+ //
+ target::prerequisite_targets_type p;
+
+ if (!t.prerequisite_targets.empty ())
+ p.swap (t.prerequisite_targets);
+
+ // Find the "real" update rule, that is, the rule that would
+ // have been found if we signalled that we do not match from
+ // match() above.
+ //
+ recipe d (match_delegate (a, t).first);
+
+ // If we have no installable prerequisites, then simply redirect
+ // to it.
+ //
+ if (p.empty ())
+ return d;
+
+ // Ok, the worst case scenario: we need to cause update of
+ // prerequisite targets and also delegate to the real update.
+ //
+ return [pt = move (p), dr = move (d)]
+ (action a, target& t) mutable -> target_state
+ {
+ // Do the target update first.
+ //
+ target_state r (execute_delegate (dr, a, t));
+
+ // Swap our prerequisite targets back in and execute.
+ //
+ t.prerequisite_targets.swap (pt);
+ r |= execute_prerequisites (a, t);
+ pt.swap (t.prerequisite_targets); // In case we get re-executed.
+
+ return r;
+ };
+ }
+ else
+ return &perform_install;
+ }
+
+ struct install_dir
+ {
+ dir_path dir;
+ string sudo;
+ string cmd; //@@ VAR type
+ const_strings_value options {nullptr};
+ string mode;
+ string dir_mode;
+ };
+
+ // install -d <dir>
+ //
+ static void
+ install (const install_dir& base, const dir_path& d)
+ {
+ path reld (relative (d));
+
+ cstrings args;
+
+ if (!base.sudo.empty ())
+ args.push_back (base.sudo.c_str ());
+
+ args.push_back (base.cmd.c_str ());
+ args.push_back ("-d");
+
+ if (base.options.d != nullptr) //@@ VAR
+ config::append_options (args, base.options);
+
+ args.push_back ("-m");
+ args.push_back (base.dir_mode.c_str ());
+ args.push_back (reld.string ().c_str ());
+ args.push_back (nullptr);
+
+ if (verb >= 2)
+ print_process (args);
+ else if (verb)
+ text << "install " << d;
+
+ try
+ {
+ process pr (args.data ());
+
+ if (!pr.wait ())
+ throw failed ();
+ }
+ catch (const process_error& e)
+ {
+ error << "unable to execute " << args[0] << ": " << e.what ();
+
+ if (e.child ())
+ exit (1);
+
+ throw failed ();
+ }
+ }
+
+ // install <file> <dir>
+ //
+ static void
+ install (const install_dir& base, file& t)
+ {
+ path reld (relative (base.dir));
+ path relf (relative (t.path ()));
+
+ cstrings args;
+
+ if (!base.sudo.empty ())
+ args.push_back (base.sudo.c_str ());
+
+ args.push_back (base.cmd.c_str ());
+
+ if (base.options.d != nullptr) //@@ VAR
+ config::append_options (args, base.options);
+
+ args.push_back ("-m");
+ args.push_back (base.mode.c_str ());
+ args.push_back (relf.string ().c_str ());
+ args.push_back (reld.string ().c_str ());
+ args.push_back (nullptr);
+
+ if (verb >= 2)
+ print_process (args);
+ else if (verb)
+ text << "install " << t;
+
+ try
+ {
+ process pr (args.data ());
+
+ if (!pr.wait ())
+ throw failed ();
+ }
+ catch (const process_error& e)
+ {
+ error << "unable to execute " << args[0] << ": " << e.what ();
+
+ if (e.child ())
+ exit (1);
+
+ throw failed ();
+ }
+ }
+
+ // Resolve installation directory name to absolute directory path,
+ // creating leading directories as necessary.
+ //
+ static install_dir
+ resolve (scope& s, dir_path d, const string* var = nullptr)
+ {
+ install_dir r;
+
+ if (d.absolute ())
+ {
+ d.normalize ();
+
+ // Make sure it already exists (this will normally be
+ // install.root with everything else defined in term of it).
+ //
+ if (!dir_exists (d))
+ fail << "installation directory " << d << " does not exist";
+ }
+ else
+ {
+ // If it is relative, then the first component is treated
+ // as the installation directory name, e.g., bin, sbin, lib,
+ // etc. Look it up and recurse.
+ //
+ const string& sn (*d.begin ());
+ const string var ("install." + sn);
+ if (const dir_path* dn = lookup (s, var))
+ {
+ r = resolve (s, *dn, &var);
+ d = r.dir / dir_path (++d.begin (), d.end ());
+ d.normalize ();
+
+ if (!dir_exists (d))
+ install (r, d); // install -d
+ }
+ else
+ fail << "unknown installation directory name " << sn <<
+ info << "did you forget to specify config." << var << "?";
+ }
+
+ r.dir = move (d);
+
+ // Override components in install_dir if we have our own.
+ //
+ if (var != nullptr)
+ {
+ if (auto l = s[*var + ".sudo"]) r.sudo = as<string> (*l);
+ if (auto l = s[*var + ".cmd"]) r.cmd = as<string> (*l);
+ if (auto l = s[*var + ".mode"]) r.mode = as<string> (*l);
+ if (auto l = s[*var + ".dir_mode"]) r.dir_mode = as<string> (*l);
+ if (auto l = s[*var + ".options"]) r.options = as<strings> (*l);
+ }
+
+ // Set defaults for unspecified components.
+ //
+ if (r.cmd.empty ()) r.cmd = "install";
+ if (r.mode.empty ()) r.mode = "644";
+ if (r.dir_mode.empty ()) r.dir_mode = "755";
+
+ return r;
+ }
+
+ target_state file_rule::
+ perform_install (action a, target& t)
+ {
+ file& ft (static_cast<file&> (t));
+ assert (!ft.path ().empty ()); // Should have been assigned by update.
+
+ // First handle installable prerequisites.
+ //
+ target_state r (execute_prerequisites (a, t));
+
+ // Resolve and, if necessary, create target directory.
+ //
+ install_dir d (
+ resolve (t.base_scope (),
+ as<dir_path> (*t["install"]))); // We know it's there.
+
+ // Override mode if one was specified.
+ //
+ if (auto l = t["install.mode"])
+ d.mode = as<string> (*l);
+
+ install (d, ft);
+ return (r |= target_state::changed);
+ }
+ }
+}
diff --git a/build2/install/utility b/build2/install/utility
new file mode 100644
index 0000000..3cfc3e2
--- /dev/null
+++ b/build2/install/utility
@@ -0,0 +1,40 @@
+// file : build2/install/utility -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_INSTALL_UTILITY
+#define BUILD2_INSTALL_UTILITY
+
+#include <string>
+#include <utility>
+
+#include <build2/scope>
+#include <build2/types>
+
+namespace build2
+{
+ namespace install
+ {
+ // Set install path, mode for a target type.
+ //
+ template <typename T>
+ inline void
+ path (scope& s, dir_path d)
+ {
+ auto r (s.target_vars[T::static_type]["*"].assign ("install"));
+ if (r.second) // Already set by the user?
+ r.first.get () = std::move (d);
+ }
+
+ template <typename T>
+ inline void
+ mode (scope& s, std::string m)
+ {
+ auto r (s.target_vars[T::static_type]["*"].assign ("install.mode"));
+ if (r.second) // Already set by the user?
+ r.first.get () = std::move (m);
+ }
+ }
+}
+
+#endif // BUILD2_INSTALL_UTILITY
diff --git a/build2/lexer b/build2/lexer
new file mode 100644
index 0000000..51f3e56
--- /dev/null
+++ b/build2/lexer
@@ -0,0 +1,138 @@
+// file : build2/lexer -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_LEXER
+#define BUILD2_LEXER
+
+#include <stack>
+#include <string>
+#include <iosfwd>
+#include <cstddef> // size_t
+#include <cstdint> // uint64_t
+#include <cassert>
+#include <exception>
+
+#include <butl/char-scanner>
+
+#include <build2/types>
+#include <build2/utility>
+
+#include <build2/token>
+#include <build2/diagnostics>
+
+namespace build2
+{
+ // Context-dependent lexing mode. In the value mode we don't treat
+ // certain characters (e.g., +, =) as special so that we can use
+ // them in the variable values, e.g., 'foo = g++'. In contrast, in
+ // the variable mode, we restrict certain character (e.g., /) from
+ // appearing in the name. The pairs mode is just like value except
+ // that we split names separated by the pair character. The eval
+ // mode is used in the evaluation context.
+ //
+ // The alternnative modes must be set manually. The value and pairs
+ // modes are automatically reset after the end of the line. The
+ // variable mode is reset after the name token. And the eval mode
+ // is reset after the closing ')'.
+ //
+ // Quoted is an internal mode and should not be set explicitly.
+ //
+ enum class lexer_mode {normal, variable, value, pairs, eval, quoted};
+
+ class lexer: protected butl::char_scanner
+ {
+ public:
+ lexer (std::istream& is,
+ const std::string& name,
+ void (*processor) (token&, const lexer&) = nullptr)
+ : char_scanner (is), fail (name), processor_ (processor), sep_ (false)
+ {
+ mode_.push (lexer_mode::normal);
+ }
+
+ const std::string&
+ name () const {return fail.name_;}
+
+ // Note: sets mode for the next token. If mode is pairs, then
+ // the second argument specifies the separator character.
+ //
+ void
+ mode (lexer_mode m, char pair_separator = '=')
+ {
+ mode_.push (m);
+ pair_separator_ = pair_separator;
+ }
+
+ // Expire the current mode early.
+ //
+ void
+ expire_mode () {mode_.pop ();}
+
+ lexer_mode
+ mode () const {return mode_.top ();}
+
+ char
+ pair_separator () const {return pair_separator_;}
+
+ // Scanner.
+ //
+ token
+ next ();
+
+ // Peek at the first character of the next token. Return the character
+ // or 0 if the next token will be eos. Also return an indicator of
+ // whether the next token will be separated.
+ //
+ pair<char, bool>
+ peek_char ();
+
+ private:
+ token
+ next_impl ();
+
+ token
+ next_eval ();
+
+ token
+ next_quoted ();
+
+ token
+ name (bool separated);
+
+ // Return true if we have seen any spaces. Skipped empty lines
+ // don't count. In other words, we are only interested in spaces
+ // that are on the same line as the following non-space character.
+ //
+ bool
+ skip_spaces ();
+
+ xchar
+ escape ();
+
+ // Diagnostics.
+ //
+ private:
+ struct fail_mark_base: build2::fail_mark_base<failed>
+ {
+ fail_mark_base (const std::string& n): name_ (n) {}
+
+ location_prologue
+ operator() (const xchar&) const;
+
+ std::string name_;
+ };
+ typedef diag_mark<fail_mark_base> fail_mark;
+
+ private:
+ fail_mark fail;
+
+ void (*processor_) (token&, const lexer&);
+
+ std::stack<lexer_mode> mode_;
+ char pair_separator_;
+ bool sep_; // True if we skipped spaces in peek().
+ };
+}
+
+#endif // BUILD2_LEXER
diff --git a/build2/lexer.cxx b/build2/lexer.cxx
new file mode 100644
index 0000000..e4d03c4
--- /dev/null
+++ b/build2/lexer.cxx
@@ -0,0 +1,431 @@
+// file : build2/lexer.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/lexer>
+
+using namespace std;
+
+namespace build2
+{
+ typedef token_type type;
+
+ token lexer::
+ next ()
+ {
+ token t (next_impl ());
+ if (processor_ != nullptr)
+ processor_ (t, *this);
+ return t;
+ }
+
+ pair<char, bool> lexer::
+ peek_char ()
+ {
+ // In the quoted mode we don't skip spaces.
+ //
+ sep_ = mode_.top () != lexer_mode::quoted && skip_spaces ();
+ xchar c (peek ());
+ return make_pair (eos (c) ? '\0' : char (c), sep_);
+ }
+
+ token lexer::
+ next_impl ()
+ {
+ lexer_mode m (mode_.top ());
+
+ // For some modes we have dedicated imlementations of next().
+ //
+ switch (m)
+ {
+ case lexer_mode::eval: return next_eval ();
+ case lexer_mode::quoted: return next_quoted ();
+ default: break;
+ }
+
+ bool sep (skip_spaces ());
+
+ xchar c (get ());
+ uint64_t ln (c.line), cn (c.column);
+
+ if (eos (c))
+ return token (type::eos, sep, ln, cn);
+
+ switch (c)
+ {
+ // NOTE: remember to update name(), next_eval() if adding new
+ // special characters.
+ //
+ case '\n':
+ {
+ // Expire value/pairs mode at the end of the line.
+ //
+ if (m == lexer_mode::value || m == lexer_mode::pairs)
+ mode_.pop ();
+
+ return token (type::newline, sep, ln, cn);
+ }
+ case '{': return token (type::lcbrace, sep, ln, cn);
+ case '}': return token (type::rcbrace, sep, ln, cn);
+ case '$': return token (type::dollar, sep, ln, cn);
+ case '(': return token (type::lparen, sep, ln, cn);
+ case ')': return token (type::rparen, sep, ln, cn);
+ }
+
+ // Handle pair separator.
+ //
+ if (m == lexer_mode::pairs && c == pair_separator_)
+ return token (c, sep, ln, cn);
+
+ // The following characters are not treated as special in the
+ // value or pairs mode.
+ //
+ if (m != lexer_mode::value && m != lexer_mode::pairs)
+ {
+ switch (c)
+ {
+ // NOTE: remember to update name(), next_eval() if adding new
+ // special characters.
+ //
+ case ':': return token (type::colon, sep, ln, cn);
+ case '=':
+ {
+ if (peek () == '+')
+ {
+ get ();
+ return token (type::equal_plus, sep, ln, cn);
+ }
+ else
+ return token (type::equal, sep, ln, cn);
+ }
+ case '+':
+ {
+ if (peek () == '=')
+ {
+ get ();
+ return token (type::plus_equal, sep, ln, cn);
+ }
+ }
+ }
+ }
+
+ // Otherwise it is a name.
+ //
+ unget (c);
+ return name (sep);
+ }
+
+ token lexer::
+ next_eval ()
+ {
+ bool sep (skip_spaces ());
+ xchar c (get ());
+
+ if (eos (c))
+ fail (c) << "unterminated evaluation context";
+
+ uint64_t ln (c.line), cn (c.column);
+
+ // This mode is quite a bit like the value mode when it comes
+ // to special characters.
+ //
+ switch (c)
+ {
+ // NOTE: remember to update name() if adding new special characters.
+ //
+ case '\n': fail (c) << "newline in evaluation context";
+ case '{': return token (type::lcbrace, sep, ln, cn);
+ case '}': return token (type::rcbrace, sep, ln, cn);
+ case '$': return token (type::dollar, sep, ln, cn);
+ case '(': return token (type::lparen, sep, ln, cn);
+ case ')':
+ {
+ mode_.pop (); // Expire eval mode.
+ return token (type::rparen, sep, ln, cn);
+ }
+ }
+
+ // Otherwise it is a name.
+ //
+ unget (c);
+ return name (sep);
+ }
+
+ token lexer::
+ next_quoted ()
+ {
+ xchar c (get ());
+
+ if (eos (c))
+ fail (c) << "unterminated double-quoted sequence";
+
+ uint64_t ln (c.line), cn (c.column);
+
+ switch (c)
+ {
+ case '$': return token (type::dollar, false, ln, cn);
+ case '(': return token (type::lparen, false, ln, cn);
+ }
+
+ // Otherwise it is a name.
+ //
+ unget (c);
+ return name (false);
+ }
+
+ token lexer::
+ name (bool sep)
+ {
+ xchar c (peek ());
+ assert (!eos (c));
+
+ uint64_t ln (c.line), cn (c.column);
+ string lexeme;
+
+ lexer_mode m (mode_.top ());
+ bool quoted (m == lexer_mode::quoted);
+
+ for (; !eos (c); c = peek ())
+ {
+ bool done (false);
+
+ // Handle pair separator.
+ //
+ if (m == lexer_mode::pairs && c == pair_separator_)
+ break;
+
+ // The following characters are not treated as special in the
+ // value/pairs, eval, and quoted modes.
+ //
+ if (m != lexer_mode::value &&
+ m != lexer_mode::pairs &&
+ m != lexer_mode::eval &&
+ m != lexer_mode::quoted)
+ {
+ switch (c)
+ {
+ case ':':
+ case '+':
+ case '=':
+ {
+ done = true;
+ break;
+ }
+ }
+
+ if (done)
+ break;
+ }
+
+ // While these extra characters are treated as the name end in
+ // the variable mode.
+ //
+ if (m == lexer_mode::variable)
+ {
+ switch (c)
+ {
+ case '/':
+ case '-':
+ {
+ done = true;
+ break;
+ }
+ }
+
+ if (done)
+ break;
+ }
+
+ // If we are quoted, these are ordinary characters.
+ //
+ if (m != lexer_mode::quoted)
+ {
+ switch (c)
+ {
+ case ' ':
+ case '\t':
+ case '\n':
+ case '#':
+ case '{':
+ case '}':
+ case ')':
+ {
+ done = true;
+ break;
+ }
+ case '\\':
+ {
+ get ();
+ c = escape ();
+ if (c != '\n') // Ignore.
+ lexeme += c;
+ continue;
+ }
+ case '\'':
+ {
+ // If we are in the variable mode, then treat quote as just
+ // another separator.
+ //
+ if (m == lexer_mode::variable)
+ {
+ done = true;
+ break;
+ }
+ else
+ {
+ get ();
+
+ for (c = get (); !eos (c) && c != '\''; c = get ())
+ lexeme += c;
+
+ if (eos (c))
+ fail (c) << "unterminated single-quoted sequence";
+
+ quoted = true;
+ continue;
+ }
+ }
+ }
+
+ if (done)
+ break;
+ }
+
+ switch (c)
+ {
+ case '$':
+ case '(':
+ {
+ done = true;
+ break;
+ }
+ case '\"':
+ {
+ // If we are in the variable mode, then treat quote as just
+ // another separator.
+ //
+ if (m == lexer_mode::variable)
+ {
+ done = true;
+ break;
+ }
+ else
+ {
+ get ();
+
+ if (m == lexer_mode::quoted)
+ mode_.pop ();
+ else
+ {
+ mode_.push (lexer_mode::quoted);
+ quoted = true;
+ }
+
+ m = mode_.top ();
+ continue;
+ }
+ }
+ default:
+ {
+ get ();
+ lexeme += c;
+ continue;
+ }
+ }
+
+ assert (done);
+ break;
+ }
+
+ if (m == lexer_mode::quoted && eos (c))
+ fail (c) << "unterminated double-quoted sequence";
+
+ // Expire variable mode at the end of the name.
+ //
+ if (m == lexer_mode::variable)
+ mode_.pop ();
+
+ return token (lexeme, sep, quoted, ln, cn);
+ }
+
+ bool lexer::
+ skip_spaces ()
+ {
+ bool r (sep_);
+ sep_ = false;
+
+ xchar c (peek ());
+ bool start (c.column == 1);
+
+ for (; !eos (c); c = peek ())
+ {
+ switch (c)
+ {
+ case ' ':
+ case '\t':
+ {
+ r = true;
+ break;
+ }
+ case '\n':
+ {
+ // Skip empty lines.
+ //
+ if (start)
+ {
+ r = false;
+ break;
+ }
+
+ return r;
+ }
+ case '#':
+ {
+ get ();
+
+ // Read until newline or eos.
+ //
+ for (c = peek (); !eos (c) && c != '\n'; c = peek ())
+ get ();
+
+ r = true;
+ continue;
+ }
+ case '\\':
+ {
+ get ();
+
+ if (peek () == '\n')
+ break; // Ignore.
+
+ unget (c);
+ // Fall through.
+ }
+ default:
+ return r; // Not a space.
+ }
+
+ get ();
+ }
+
+ return r;
+ }
+
+ lexer::xchar lexer::
+ escape ()
+ {
+ xchar c (get ());
+
+ if (eos (c))
+ fail (c) << "unterminated escape sequence";
+
+ return c;
+ }
+
+ location_prologue lexer::fail_mark_base::
+ operator() (const xchar& c) const
+ {
+ return build2::fail_mark_base<failed>::operator() (
+ location (name_.c_str (), c.line, c.column));
+ }
+}
diff --git a/build2/module b/build2/module
new file mode 100644
index 0000000..4fac698
--- /dev/null
+++ b/build2/module
@@ -0,0 +1,86 @@
+// file : build2/module -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_MODULE
+#define BUILD2_MODULE
+
+#include <map>
+
+#include <build2/types>
+#include <build2/utility>
+
+#include <build2/diagnostics>
+
+namespace build2
+{
+ class scope;
+ class location;
+
+ class module
+ {
+ public:
+ virtual
+ ~module () = default;
+ };
+
+ extern "C"
+ using module_boot_function =
+ void (scope& root, const location&, unique_ptr<module>&);
+
+ // Return false if the module configuration (normally based on the default
+ // values) was unsuccessful but this is not (yet) an error. One example
+ // would be the optional use of a module. Or a module might remain
+ // unconfigured for as long as it is actually not used (e.g., install,
+ // dist). The return value is used to set the <module>.configured variable.
+ //
+ extern "C"
+ using module_init_function =
+ bool (scope& root,
+ scope& base,
+ const location&,
+ unique_ptr<module>&,
+ bool first, // First time for this project.
+ bool optional); // Loaded with 'using?' (optional module).
+
+
+ struct module_state
+ {
+ bool boot; // True if the module boot'ed but not yet init'ed.
+ module_init_function* init;
+ unique_ptr<build2::module> module;
+ const location loc; // Boot location.
+ };
+
+ using loaded_module_map = std::map<string, module_state>;
+
+ // Load and boot the specified module.
+ //
+ void
+ boot_module (const string& name, scope& root, const location&);
+
+ // Load (if not already loaded) and initialize the specified module. Used
+ // by the parser but also by some modules to load prerequisite modules.
+ // Return true if the module was both successfully loaded and configured
+ // (false can only be returned if optional).
+ //
+ bool
+ load_module (bool optional,
+ const std::string& name,
+ scope& root,
+ scope& base,
+ const location&);
+
+ // Builtin modules.
+ //
+ struct module_functions
+ {
+ module_boot_function* boot;
+ module_init_function* init;
+ };
+
+ using available_module_map = std::map<string, module_functions>;
+ extern available_module_map builtin_modules;
+}
+
+#endif // BUILD2_MODULE
diff --git a/build2/module.cxx b/build2/module.cxx
new file mode 100644
index 0000000..288569f
--- /dev/null
+++ b/build2/module.cxx
@@ -0,0 +1,114 @@
+// file : build2/module.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/module>
+
+#include <utility> // make_pair()
+
+#include <build2/scope>
+#include <build2/variable>
+#include <build2/diagnostics>
+
+using namespace std;
+
+namespace build2
+{
+ available_module_map builtin_modules;
+
+ void
+ boot_module (const string& name, scope& rs, const location& loc)
+ {
+ // First see if this modules has already been loaded for this project.
+ //
+ loaded_module_map& lm (rs.modules);
+ auto i (lm.find (name));
+
+ if (i != lm.end ())
+ {
+ module_state& s (i->second);
+
+ // The only valid situation here is if the module has already been
+ // bootstrapped.
+ //
+ assert (s.boot);
+ return;
+ }
+
+ // Otherwise search for this module.
+ //
+ auto j (builtin_modules.find (name));
+
+ if (j == builtin_modules.end ())
+ fail (loc) << "unknown module " << name;
+
+ const module_functions& mf (j->second);
+
+ if (mf.boot == nullptr)
+ fail (loc) << "module " << name << " shouldn't be loaded in bootstrap";
+
+ i = lm.emplace (name, module_state {true, mf.init, nullptr, loc}).first;
+ mf.boot (rs, loc, i->second.module);
+ }
+
+ bool
+ load_module (bool opt,
+ const string& name,
+ scope& rs,
+ scope& bs,
+ const location& loc)
+ {
+ // First see if this modules has already been loaded for this project.
+ //
+ loaded_module_map& lm (rs.modules);
+ auto i (lm.find (name));
+ bool f (i == lm.end ());
+
+ if (f)
+ {
+ // Otherwise search for this module.
+ //
+ auto j (builtin_modules.find (name));
+
+ if (j == builtin_modules.end ())
+ {
+ if (!opt)
+ fail (loc) << "unknown module " << name;
+ }
+ else
+ {
+ const module_functions& mf (j->second);
+
+ if (mf.boot != nullptr)
+ fail (loc) << "module " << name << " should be loaded in bootstrap";
+
+ i = lm.emplace (
+ name, module_state {false, mf.init, nullptr, loc}).first;
+ }
+ }
+ else
+ {
+ module_state& s (i->second);
+
+ if (s.boot)
+ {
+ s.boot = false;
+ f = true; // This is a first call to init.
+ }
+ }
+
+ bool l (i != lm.end ());
+ bool c (l && i->second.init (rs, bs, loc, i->second.module, f, opt));
+
+ const variable& lv (var_pool.find (name + ".loaded",
+ variable_visibility::project,
+ bool_type));
+ const variable& cv (var_pool.find (name + ".configured",
+ variable_visibility::project,
+ bool_type));
+ bs.assign (lv) = l;
+ bs.assign (cv) = c;
+
+ return l && c;
+ }
+}
diff --git a/build2/name b/build2/name
new file mode 100644
index 0000000..a0672c7
--- /dev/null
+++ b/build2/name
@@ -0,0 +1,113 @@
+// file : build2/name -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_NAME
+#define BUILD2_NAME
+
+#include <string>
+#include <vector>
+#include <iosfwd>
+#include <utility> // move()
+
+#include <butl/path>
+
+// Note: include <build2/types> instead of this file directly.
+//
+namespace build2
+{
+ using butl::dir_path;
+
+ // A name is what we operate on by default. Depending on the context,
+ // it can be interpreted as a target or prerequisite name. A name
+ // without a type and directory can be used to represent any text.
+ // A name with directory and empty value represents a directory.
+ // A name may also be project-qualified. If the project name is
+ // empty, then it means the name is in a project other than our
+ // own (e.g., it is installed).
+ //
+ // If pair is not '\0', then this name and the next in the list
+ // form a pair.
+ //
+ struct name
+ {
+ name () = default;
+
+ explicit name (std::string v): value (std::move (v)) {}
+ name& operator= (std::string v) {return *this = name (std::move (v));}
+
+ explicit name (dir_path d): dir (std::move (d)) {}
+ name& operator= (dir_path d) {return *this = name (std::move (d));}
+
+ name (std::string t, std::string v)
+ : type (std::move (t)), value (std::move (v)) {}
+
+ name (dir_path d, std::string t, std::string v)
+ : dir (std::move (d)), type (std::move (t)), value (std::move (v)) {}
+
+ // The first argument should be from project_name_pool.
+ //
+ name (const std::string* p, dir_path d, std::string t, std::string v)
+ : proj (p),
+ dir (std::move (d)),
+ type (std::move (t)),
+ value (std::move (v)) {}
+
+ bool
+ qualified () const {return proj != nullptr;}
+
+ bool
+ unqualified () const {return proj == nullptr;}
+
+ bool
+ typed () const {return !type.empty ();}
+
+ bool
+ untyped () const {return type.empty ();}
+
+ bool
+ empty () const {return dir.empty () && value.empty ();}
+
+ // Note that strictly speaking the following tests should be
+ // orthogonal to qualification. However, the vast majority of
+ // cases where we expect a simple or directory name, we also
+ // expect it to be unqualified.
+ //
+ // Note also that empty name is simple but not a directory.
+ //
+ bool
+ simple () const {return unqualified () && untyped () && dir.empty ();}
+
+ bool
+ directory () const
+ {return unqualified () && untyped () && !dir.empty () && value.empty ();}
+
+ const std::string* proj = nullptr; // Points to project_name_pool.
+ dir_path dir;
+ std::string type;
+ std::string value;
+ char pair = '\0'; // Pair symbol, if any.
+ };
+
+ inline bool
+ operator== (const name& x, const name& y)
+ {
+ return x.proj == y.proj && // Pooled, so can just compare pointers.
+ x.type == y.type &&
+ x.dir == y.dir &&
+ x.value == y.value;
+ }
+
+ inline bool
+ operator!= (const name& x, const name& y) {return !(x == y);}
+
+ typedef std::vector<name> names;
+
+ std::ostream&
+ operator<< (std::ostream&, const name&);
+
+ std::ostream&
+ operator<< (std::ostream&, const names&);
+}
+
+#endif // BUILD2_NAME
diff --git a/build2/name.cxx b/build2/name.cxx
new file mode 100644
index 0000000..9ebfa1f
--- /dev/null
+++ b/build2/name.cxx
@@ -0,0 +1,63 @@
+// file : build2/name.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/name>
+
+#include <ostream>
+
+#include <build2/diagnostics>
+
+using namespace std;
+
+namespace build2
+{
+ ostream&
+ operator<< (ostream& os, const name& n)
+ {
+ if (n.proj != nullptr)
+ os << *n.proj << '%';
+
+ // If the value is empty, then we want to print the directory
+ // inside {}, e.g., dir{bar/}, not bar/dir{}. We also want to
+ // print {} for an empty name.
+ //
+ bool d (!n.dir.empty ());
+ bool v (!n.value.empty ());
+ bool t (!n.type.empty () || (!d && !v));
+
+ if (v)
+ os << n.dir;
+
+ if (t)
+ os << n.type << '{';
+
+ if (v)
+ os << n.value;
+ else
+ os << n.dir;
+
+ if (t)
+ os << '}';
+
+ return os;
+ }
+
+ ostream&
+ operator<< (ostream& os, const names& ns)
+ {
+ for (auto i (ns.begin ()), e (ns.end ()); i != e; )
+ {
+ const name& n (*i);
+ ++i;
+ os << n;
+
+ if (n.pair != '\0')
+ os << n.pair;
+ else if (i != e)
+ os << ' ';
+ }
+
+ return os;
+ }
+}
diff --git a/build2/operation b/build2/operation
new file mode 100644
index 0000000..a843c9d
--- /dev/null
+++ b/build2/operation
@@ -0,0 +1,359 @@
+// file : build2/operation -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_OPERATION
+#define BUILD2_OPERATION
+
+#include <string>
+#include <iosfwd>
+#include <vector>
+#include <cstdint>
+#include <functional> // reference_wrapper
+
+#include <butl/string-table>
+
+#include <build2/types>
+
+namespace build2
+{
+ class location;
+ class scope;
+ class target_key;
+
+ // While we are using uint8_t for the meta/operation ids, we assume
+ // that each is limited to 4 bits (max 128 entries) so that we can
+ // store the combined action id in uint8_t as well. This makes our
+ // life easier when it comes to defining switch labels for action
+ // ids (no need to mess with endian-ness).
+ //
+ // Note that 0 is not a valid meta/operation/action id.
+ //
+ using meta_operation_id = std::uint8_t;
+ using operation_id = std::uint8_t;
+ using action_id = std::uint8_t;
+
+ // Meta-operations and operations are not the end of the story. We
+ // also have operation nesting (currently only one level deep) which
+ // is used to implement pre/post operations (currently, but may be
+ // useful for other things). Here is the idea: the test operation
+ // needs to make sure that the targets that it needs to test are
+ // up-to-date. So it runs update as its pre-operation. It is almost
+ // like an ordinary update except that it has test as its outer
+ // operation (the meta-operations are always the same). This way a
+ // rule can recognize that this is "update for test" and do something
+ // differently. For example, if an executable is not a test, then
+ // there is no use updating it. At the same time, most rules will
+ // ignore the fact that this is a nested update and for them it is
+ // "update as usual".
+ //
+ struct action
+ {
+ action (): inner_id (0), outer_id (0) {} // Invalid action.
+
+ bool
+ valid () const {return inner_id != 0;}
+
+ // If this is not a nested operation, then outer should be 0.
+ //
+ action (meta_operation_id m, operation_id inner, operation_id outer = 0)
+ : inner_id ((m << 4) | inner),
+ outer_id (outer == 0 ? 0 : (m << 4) | outer) {}
+
+ meta_operation_id
+ meta_operation () const {return inner_id >> 4;}
+
+ operation_id
+ operation () const {return inner_id & 0xF;}
+
+ operation_id
+ outer_operation () const {return outer_id & 0xF;}
+
+ // Implicit conversion operator to action_id for the switch()
+ // statement, etc. Most places will only care about the inner
+ // operation.
+ //
+ operator action_id () const {return inner_id;}
+
+ action_id inner_id;
+ action_id outer_id;
+ };
+
+ // This is an "overrides" comparison, i.e., it returns true
+ // if the recipe for x overrides recipe for y. The idea is
+ // that for the same inner operation, action with an outer
+ // operation is "weaker" than the one without.
+ //
+ inline bool
+ operator> (action x, action y)
+ {
+ return x.inner_id != y.inner_id ||
+ (x.outer_id != y.outer_id && y.outer_id != 0);
+ }
+
+ // Note that these ignore the outer operation.
+ //
+ inline bool
+ operator== (action x, action y) {return x.inner_id == y.inner_id;}
+
+ inline bool
+ operator!= (action x, action y) {return !(x == y);}
+
+ std::ostream&
+ operator<< (std::ostream&, action);
+
+ // Id constants for build-in and pre-defined meta/operations.
+ //
+ const meta_operation_id perform_id = 1;
+ const meta_operation_id configure_id = 2;
+ const meta_operation_id disfigure_id = 3;
+ const meta_operation_id dist_id = 4;
+
+ // The default operation is a special marker that can be used to
+ // indicate that no operation was explicitly specified by the user.
+ //
+ const operation_id default_id = 1; // Shall be first.
+ const operation_id update_id = 2;
+ const operation_id clean_id = 3;
+ const operation_id test_id = 4;
+ const operation_id install_id = 5;
+
+ const action_id perform_update_id = (perform_id << 4) | update_id;
+ const action_id perform_clean_id = (perform_id << 4) | clean_id;
+ const action_id perform_test_id = (perform_id << 4) | test_id;
+ const action_id perform_install_id = (perform_id << 4) | install_id;
+
+ const action_id configure_update_id = (configure_id << 4) | update_id;
+
+ // Recipe execution mode.
+ //
+ // When a target is a prerequisite of another target, its recipe can be
+ // executed before the dependent's recipe (the normal case) or after.
+ // We will call these "front" and "back" execution modes, respectively
+ // (think "the prerequisite is 'front-running' the dependent").
+ //
+ // There could also be several dependent targets and the prerequisite's
+ // recipe can be execute as part of the first dependent (the normal
+ // case) or last (or for all/some of them; see the recipe execution
+ // protocol in <target>). We will call these "first" and "last"
+ // execution modes, respectively.
+ //
+ // Now you may be having a hard time imagining where a mode other than
+ // the normal one (first/front) could be useful. An the answer is,
+ // compensating or inverse operations such as clean, uninstall, etc.
+ // If we use the last/back mode for, say, clean, then we will remove
+ // targets in the order inverse to the way they were updated. While
+ // this sounds like an elegant idea, are there any practical benefits
+ // of doing it this way. As it turns out there is (at least) one: when
+ // we are removing a directory (see fsdir{}), we want to do it after
+ // all the targets that depend on it (such as files, sub-directories)
+ // were removed. If we do it before, then the directory won't be empty
+ // yet.
+ //
+ // It appears that this execution mode is dictated by the essence of
+ // the operation. Constructive operations (those that "do") seem to
+ // naturally use the first/front mode. That is, we need to "do" the
+ // prerequisite first before we can "do" the dependent. While the
+ // destructive ones (those that "undo") seem to need last/back. That
+ // is, we need to "undo" all the dependents before we can "undo" the
+ // prerequisite (say, we need to remove all the files before we can
+ // remove their directory).
+ //
+ // If you noticed the parallel with the way C++ construction and
+ // destruction works for base/derived object then you earned a gold
+ // star!
+ //
+ // Note that the front/back mode is realized in the dependen's recipe
+ // (which is another indication that it is a property of the operation).
+ //
+ enum class execution_mode {first, last};
+
+ // Meta-operation info.
+ //
+
+ // Normally a list of resolved and matched targets to execute. But
+ // can be something else, depending on the meta-operation.
+ //
+ typedef std::vector<void*> action_targets;
+
+ struct meta_operation_info
+ {
+ const std::string name;
+
+ // Name derivatives for diagnostics. If empty, then the meta-
+ // operation need not be mentioned.
+ //
+ const std::string name_do; // E.g., [to] 'configure'.
+ const std::string name_doing; // E.g., [while] 'configuring'.
+ const std::string name_done; // E.g., 'is configured'.
+
+ // If operation_pre() is not NULL, then it may translate default_id
+ // (and only default_id) to some other operation. If not translated,
+ // then default_id is used. If, however, operation_pre() is NULL,
+ // then default_id is translated to update_id.
+ //
+ void (*meta_operation_pre) (); // Start of meta-operation batch.
+ operation_id (*operation_pre) (operation_id); // Start of operation batch.
+
+ // Meta-operation-specific logic to load the buildfile, search and match
+ // the targets, and execute the action on the targets.
+ //
+ void (*load) (const path& buildfile,
+ scope& root,
+ const dir_path& out_base,
+ const dir_path& src_base,
+ const location&);
+
+ void (*search) (scope& root,
+ const target_key&,
+ const location&,
+ action_targets&);
+
+ void (*match) (action, action_targets&);
+
+ void (*execute) (action, const action_targets&, bool quiet);
+
+ void (*operation_post) (operation_id); // End of operation batch.
+ void (*meta_operation_post) (); // End of meta-operation batch.
+ };
+
+ // Built-in meta-operations.
+ //
+
+ // perform
+ //
+
+ // Load the buildfile. This is the default implementation that first
+ // calls root_pre(), then creates the scope for out_base, and, finally,
+ // loads the buildfile unless it has already been loaded for the root
+ // scope.
+ //
+ void
+ load (const path& buildfile,
+ scope& root,
+ const dir_path& out_base,
+ const dir_path& src_base,
+ const location&);
+
+ // Search and match the target. This is the default implementation
+ // that does just that and adds a pointer to the target to the list.
+ //
+ void
+ search (scope&, const target_key&, const location&, action_targets&);
+
+ void
+ match (action, action_targets&);
+
+ // Execute the action on the list of targets. This is the default
+ // implementation that does just that while issuing appropriate
+ // diagnostics (unless quiet).
+ //
+ void
+ execute (action, const action_targets&, bool quiet);
+
+ extern meta_operation_info perform;
+
+ // Operation info.
+ //
+ struct operation_info
+ {
+ const std::string name;
+
+ // Name derivatives for diagnostics. Note that unlike meta-operations,
+ // these can only be empty for the default operation (id 1), And
+ // meta-operations that make use of the default operation shall not
+ // have empty derivatives (failed which only target name will be
+ // printed).
+ //
+ const std::string name_do; // E.g., [to] 'update'.
+ const std::string name_doing; // E.g., [while] 'updating'.
+ const std::string name_done; // E.g., 'is up to date'.
+
+ const execution_mode mode;
+
+ // If the returned operation_id's are not 0, then they are injected
+ // as pre/post operations for this operation. Can be NULL if unused.
+ // The returned operation_id shall not be default_id.
+ //
+ operation_id (*pre) (meta_operation_id);
+ operation_id (*post) (meta_operation_id);
+ };
+
+ // Built-in operations.
+ //
+ extern operation_info default_;
+ extern operation_info update;
+ extern operation_info clean;
+
+ // Global meta/operation tables. Each registered meta/operation
+ // is assigned an id which is used as an index in the per-project
+ // registered meta/operation lists.
+ //
+ // We have three types of meta/operations: built-in (e.g., perform,
+ // update), pre-defined (e.g., configure, test), and dynamically-
+ // defined. For built-in ones, both the id and implementation are
+ // part of the build2 core. For pre-defined, the id is registered
+ // as part of the core but the implementation is loaded as part of
+ // a module. The idea with pre-defined operations is that they have
+ // common, well-established semantics but could still be optional.
+ // Another aspect of pre-defined operations is that often rules
+ // across multiple modules need to know their ids. Finally,
+ // dynamically-defined meta/operations have their ids registered
+ // as part of a module load. In this case, the meta/operation is
+ // normally (but not necessarily) fully implemented by this module.
+ //
+ // Note also that the name of a meta/operation in a sense defines
+ // its semantics. It would be strange to have an operation called
+ // test that does two very different things in different projects.
+ //
+ extern butl::string_table<meta_operation_id> meta_operation_table;
+ extern butl::string_table<operation_id> operation_table;
+
+ // These are "sparse" in the sense that we may have "holes" that
+ // are represented as NULL pointers. Also, lookup out of bounds
+ // is treated as a hole.
+ //
+ template <typename T>
+ struct sparse_vector
+ {
+ using base_type = std::vector<T*>;
+ using size_type = typename base_type::size_type;
+
+ void
+ insert (size_type i, T& x)
+ {
+ size_type n (v_.size ());
+
+ if (i < n)
+ v_[i] = &x;
+ else
+ {
+ if (n != i)
+ v_.resize (i, nullptr); // Add holes.
+ v_.push_back (&x);
+ }
+ }
+
+ T*
+ operator[] (size_type i) const
+ {
+ return i < v_.size () ? v_[i] : nullptr;
+ }
+
+ bool
+ empty () const {return v_.empty ();}
+
+ // Note that this is more of a "max index" rather than size.
+ //
+ size_type
+ size () const {return v_.size ();}
+
+ private:
+ base_type v_;
+ };
+
+ using meta_operations = sparse_vector<const meta_operation_info>;
+ using operations = sparse_vector<const operation_info>;
+}
+
+#endif // BUILD2_OPERATION
diff --git a/build2/operation.cxx b/build2/operation.cxx
new file mode 100644
index 0000000..f4eae64
--- /dev/null
+++ b/build2/operation.cxx
@@ -0,0 +1,232 @@
+// file : build2/operation.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/operation>
+
+#include <vector>
+#include <ostream>
+#include <cassert>
+#include <functional> // reference_wrapper
+
+#include <butl/utility> // reverse_iterate
+
+#include <build2/scope>
+#include <build2/target>
+#include <build2/file>
+#include <build2/algorithm>
+#include <build2/diagnostics>
+#include <build2/dump>
+
+using namespace std;
+using namespace butl;
+
+namespace build2
+{
+ // action
+ //
+ ostream&
+ operator<< (ostream& os, action a)
+ {
+ uint16_t
+ m (a.meta_operation ()),
+ i (a.operation ()),
+ o (a.outer_operation ());
+
+ os << '(' << m << ',';
+
+ if (o != 0)
+ os << o << '(';
+
+ os << i;
+
+ if (o != 0)
+ os << ')';
+
+ os << ')';
+
+ return os;
+ }
+
+ // perform
+ //
+ void
+ load (const path& bf,
+ scope& root,
+ const dir_path& out_base,
+ const dir_path& src_base,
+ const location&)
+ {
+ // Load project's root[-pre].build.
+ //
+ load_root_pre (root);
+
+ // Create the base scope. Note that its existence doesn't
+ // mean it was already setup as a base scope; it can be the
+ // same as root.
+ //
+ 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.
+ //
+ source_once (bf, root, base, root);
+ }
+
+ void
+ search (scope&,
+ const target_key& tk,
+ const location& l,
+ action_targets& ts)
+ {
+ tracer trace ("search");
+
+ auto i (targets.find (tk, trace));
+ if (i == targets.end ())
+ fail (l) << "unknown target " << tk;
+
+ ts.push_back (i->get ());
+ }
+
+ void
+ match (action a, action_targets& ts)
+ {
+ tracer trace ("match");
+
+ if (verb >= 6)
+ dump (a);
+
+ for (void* vt: ts)
+ {
+ target& t (*static_cast<target*> (vt));
+ level5 ([&]{trace << "matching " << t;});
+ match (a, t);
+ }
+
+ if (verb >= 6)
+ dump (a);
+ }
+
+ void
+ execute (action a, const action_targets& ts, bool quiet)
+ {
+ tracer trace ("execute");
+
+ // Execute collecting postponed targets (to be re-examined later).
+ // Do it in reverse order if the execution mode is 'last'.
+ //
+ vector<reference_wrapper<target>> psp;
+
+ auto body (
+ [a, quiet, &psp, &trace] (void* v)
+ {
+ target& t (*static_cast<target*> (v));
+
+ level5 ([&]{trace << diag_doing (a, t);});
+
+ switch (execute (a, t))
+ {
+ case target_state::unchanged:
+ {
+ if (!quiet)
+ info << diag_done (a, t);
+ break;
+ }
+ case target_state::postponed:
+ psp.push_back (t);
+ break;
+ case target_state::changed:
+ break;
+ case target_state::failed:
+ //@@ This could probably happen in a parallel build.
+ default:
+ assert (false);
+ }
+ });
+
+ if (current_mode == execution_mode::first)
+ for (void* v: ts) body (v);
+ else
+ for (void* v: reverse_iterate (ts)) body (v);
+
+ // We should have executed every target that we matched.
+ //
+ assert (dependency_count == 0);
+
+ // Re-examine postponed targets. This is the only reliable way to
+ // find out whether the target has changed.
+ //
+ for (target& t: psp)
+ {
+ switch (execute (a, t))
+ {
+ case target_state::unchanged:
+ {
+ if (!quiet)
+ info << diag_done (a, t);
+ break;
+ }
+ case target_state::changed:
+ break;
+ case target_state::postponed:
+ assert (false);
+ case target_state::failed:
+ //@@ This could probably happen in a parallel build.
+ default:
+ assert (false);
+ }
+ }
+ }
+
+ meta_operation_info perform {
+ "perform",
+ "",
+ "",
+ "",
+ nullptr, // meta-operation pre
+ nullptr, // operation pre
+ &load,
+ &search,
+ &match,
+ &execute,
+ nullptr, // operation post
+ nullptr // meta-operation post
+ };
+
+ // operations
+ //
+ operation_info default_ {
+ "<default>",
+ "",
+ "",
+ "",
+ execution_mode::first,
+ nullptr,
+ nullptr
+ };
+
+ operation_info update {
+ "update",
+ "update",
+ "updating",
+ "is up to date",
+ execution_mode::first,
+ nullptr,
+ nullptr
+ };
+
+ operation_info clean {
+ "clean",
+ "clean",
+ "cleaning",
+ "is clean",
+ execution_mode::last,
+ nullptr,
+ nullptr
+ };
+
+ // Tables.
+ //
+ string_table<meta_operation_id> meta_operation_table;
+ string_table<operation_id> operation_table;
+}
diff --git a/build2/options b/build2/options
new file mode 100644
index 0000000..596980e
--- /dev/null
+++ b/build2/options
@@ -0,0 +1,328 @@
+// -*- C++ -*-
+//
+// This file was generated by CLI, a command line interface
+// compiler for C++.
+//
+
+#ifndef BUILD2_OPTIONS
+#define BUILD2_OPTIONS
+
+// Begin prologue.
+//
+//
+// End prologue.
+
+#include <iosfwd>
+#include <string>
+#include <cstddef>
+#include <exception>
+
+#ifndef CLI_POTENTIALLY_UNUSED
+# if defined(_MSC_VER) || defined(__xlC__)
+# define CLI_POTENTIALLY_UNUSED(x) (void*)&x
+# else
+# define CLI_POTENTIALLY_UNUSED(x) (void)x
+# endif
+#endif
+
+namespace cl
+{
+ class usage_para
+ {
+ public:
+ enum value
+ {
+ none,
+ text,
+ option
+ };
+
+ usage_para (value);
+
+ operator value () const
+ {
+ return v_;
+ }
+
+ private:
+ value v_;
+ };
+
+ class unknown_mode
+ {
+ public:
+ enum value
+ {
+ skip,
+ stop,
+ fail
+ };
+
+ unknown_mode (value);
+
+ operator value () const
+ {
+ return v_;
+ }
+
+ private:
+ value v_;
+ };
+
+ // Exceptions.
+ //
+
+ class exception: public std::exception
+ {
+ public:
+ virtual void
+ print (::std::ostream&) const = 0;
+ };
+
+ ::std::ostream&
+ operator<< (::std::ostream&, const exception&);
+
+ class unknown_option: public exception
+ {
+ public:
+ virtual
+ ~unknown_option () throw ();
+
+ unknown_option (const std::string& option);
+
+ const std::string&
+ option () const;
+
+ virtual void
+ print (::std::ostream&) const;
+
+ virtual const char*
+ what () const throw ();
+
+ private:
+ std::string option_;
+ };
+
+ class unknown_argument: public exception
+ {
+ public:
+ virtual
+ ~unknown_argument () throw ();
+
+ unknown_argument (const std::string& argument);
+
+ const std::string&
+ argument () const;
+
+ virtual void
+ print (::std::ostream&) const;
+
+ virtual const char*
+ what () const throw ();
+
+ private:
+ std::string argument_;
+ };
+
+ class missing_value: public exception
+ {
+ public:
+ virtual
+ ~missing_value () throw ();
+
+ missing_value (const std::string& option);
+
+ const std::string&
+ option () const;
+
+ virtual void
+ print (::std::ostream&) const;
+
+ virtual const char*
+ what () const throw ();
+
+ private:
+ std::string option_;
+ };
+
+ class invalid_value: public exception
+ {
+ public:
+ virtual
+ ~invalid_value () throw ();
+
+ invalid_value (const std::string& option,
+ const std::string& value);
+
+ const std::string&
+ option () const;
+
+ const std::string&
+ value () const;
+
+ virtual void
+ print (::std::ostream&) const;
+
+ virtual const char*
+ what () const throw ();
+
+ private:
+ std::string option_;
+ std::string value_;
+ };
+
+ class eos_reached: public exception
+ {
+ public:
+ virtual void
+ print (::std::ostream&) const;
+
+ virtual const char*
+ what () const throw ();
+ };
+
+ class scanner
+ {
+ public:
+ virtual
+ ~scanner ();
+
+ virtual bool
+ more () = 0;
+
+ virtual const char*
+ peek () = 0;
+
+ virtual const char*
+ next () = 0;
+
+ virtual void
+ skip () = 0;
+ };
+
+ class argv_scanner: public scanner
+ {
+ public:
+ argv_scanner (int& argc, char** argv, bool erase = false);
+ argv_scanner (int start, int& argc, char** argv, bool erase = false);
+
+ int
+ end () const;
+
+ virtual bool
+ more ();
+
+ virtual const char*
+ peek ();
+
+ virtual const char*
+ next ();
+
+ virtual void
+ skip ();
+
+ private:
+ int i_;
+ int& argc_;
+ char** argv_;
+ bool erase_;
+ };
+
+ template <typename X>
+ struct parser;
+}
+
+#include <cstdint>
+
+class options
+{
+ public:
+ options ();
+
+ options (int& argc,
+ char** argv,
+ bool erase = false,
+ ::cl::unknown_mode option = ::cl::unknown_mode::fail,
+ ::cl::unknown_mode argument = ::cl::unknown_mode::stop);
+
+ options (int start,
+ int& argc,
+ char** argv,
+ bool erase = false,
+ ::cl::unknown_mode option = ::cl::unknown_mode::fail,
+ ::cl::unknown_mode argument = ::cl::unknown_mode::stop);
+
+ options (int& argc,
+ char** argv,
+ int& end,
+ bool erase = false,
+ ::cl::unknown_mode option = ::cl::unknown_mode::fail,
+ ::cl::unknown_mode argument = ::cl::unknown_mode::stop);
+
+ options (int start,
+ int& argc,
+ char** argv,
+ int& end,
+ bool erase = false,
+ ::cl::unknown_mode option = ::cl::unknown_mode::fail,
+ ::cl::unknown_mode argument = ::cl::unknown_mode::stop);
+
+ options (::cl::scanner&,
+ ::cl::unknown_mode option = ::cl::unknown_mode::fail,
+ ::cl::unknown_mode argument = ::cl::unknown_mode::stop);
+
+ // Option accessors.
+ //
+ const bool&
+ help () const;
+
+ const bool&
+ version () const;
+
+ const bool&
+ v () const;
+
+ const bool&
+ q () const;
+
+ const std::uint16_t&
+ verbose () const;
+
+ bool
+ verbose_specified () const;
+
+ // Print usage information.
+ //
+ static ::cl::usage_para
+ print_usage (::std::ostream&,
+ ::cl::usage_para = ::cl::usage_para::none);
+
+ // Implementation details.
+ //
+ protected:
+ bool
+ _parse (const char*, ::cl::scanner&);
+
+ private:
+ void
+ _parse (::cl::scanner&,
+ ::cl::unknown_mode option,
+ ::cl::unknown_mode argument);
+
+ public:
+ bool help_;
+ bool version_;
+ bool v_;
+ bool q_;
+ std::uint16_t verbose_;
+ bool verbose_specified_;
+};
+
+#include <build2/options.ixx>
+
+// Begin epilogue.
+//
+//
+// End epilogue.
+
+#endif // BUILD2_OPTIONS
diff --git a/build2/options.cli b/build2/options.cli
new file mode 100644
index 0000000..8e30985
--- /dev/null
+++ b/build2/options.cli
@@ -0,0 +1,31 @@
+// file : build2/options.cli
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+include <cstdint>;
+
+class options
+{
+ bool --help {"Print usage information and exit."};
+ bool --version {"Print version and exit."};
+
+ bool -v
+ {
+ "Print actual commands being executed. This is equivalent to
+ \cb{--verbose 2}."
+ };
+
+ bool -q
+ {
+ "Run quietly, only printing error messages. This is equivalent
+ to \cb{--verbose 0}."
+ };
+
+ std::uint16_t --verbose = 1
+ {
+ "<level>",
+ "Set the diagnostics verbosity to <level> between 0 (disabled) and
+ 6 (lots of information). The default is 1. @@ Need to document
+ further."
+ };
+};
diff --git a/build2/options.cxx b/build2/options.cxx
new file mode 100644
index 0000000..aa72be1
--- /dev/null
+++ b/build2/options.cxx
@@ -0,0 +1,549 @@
+// -*- C++ -*-
+//
+// This file was generated by CLI, a command line interface
+// compiler for C++.
+//
+
+// Begin prologue.
+//
+//
+// End prologue.
+
+#include <build2/options>
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+#include <ostream>
+#include <sstream>
+
+namespace cl
+{
+ // unknown_option
+ //
+ unknown_option::
+ ~unknown_option () throw ()
+ {
+ }
+
+ void unknown_option::
+ print (::std::ostream& os) const
+ {
+ os << "unknown option '" << option ().c_str () << "'";
+ }
+
+ const char* unknown_option::
+ what () const throw ()
+ {
+ return "unknown option";
+ }
+
+ // unknown_argument
+ //
+ unknown_argument::
+ ~unknown_argument () throw ()
+ {
+ }
+
+ void unknown_argument::
+ print (::std::ostream& os) const
+ {
+ os << "unknown argument '" << argument ().c_str () << "'";
+ }
+
+ const char* unknown_argument::
+ what () const throw ()
+ {
+ return "unknown argument";
+ }
+
+ // missing_value
+ //
+ missing_value::
+ ~missing_value () throw ()
+ {
+ }
+
+ void missing_value::
+ print (::std::ostream& os) const
+ {
+ os << "missing value for option '" << option ().c_str () << "'";
+ }
+
+ const char* missing_value::
+ what () const throw ()
+ {
+ return "missing option value";
+ }
+
+ // invalid_value
+ //
+ invalid_value::
+ ~invalid_value () throw ()
+ {
+ }
+
+ void invalid_value::
+ print (::std::ostream& os) const
+ {
+ os << "invalid value '" << value ().c_str () << "' for option '"
+ << option ().c_str () << "'";
+ }
+
+ const char* invalid_value::
+ what () const throw ()
+ {
+ return "invalid option value";
+ }
+
+ // eos_reached
+ //
+ void eos_reached::
+ print (::std::ostream& os) const
+ {
+ os << what ();
+ }
+
+ const char* eos_reached::
+ what () const throw ()
+ {
+ return "end of argument stream reached";
+ }
+
+ // scanner
+ //
+ scanner::
+ ~scanner ()
+ {
+ }
+
+ // argv_scanner
+ //
+ bool argv_scanner::
+ more ()
+ {
+ return i_ < argc_;
+ }
+
+ const char* argv_scanner::
+ peek ()
+ {
+ if (i_ < argc_)
+ return argv_[i_];
+ else
+ throw eos_reached ();
+ }
+
+ const char* argv_scanner::
+ next ()
+ {
+ if (i_ < argc_)
+ {
+ const char* r (argv_[i_]);
+
+ if (erase_)
+ {
+ for (int i (i_ + 1); i < argc_; ++i)
+ argv_[i - 1] = argv_[i];
+
+ --argc_;
+ argv_[argc_] = 0;
+ }
+ else
+ ++i_;
+
+ return r;
+ }
+ else
+ throw eos_reached ();
+ }
+
+ void argv_scanner::
+ skip ()
+ {
+ if (i_ < argc_)
+ ++i_;
+ else
+ throw eos_reached ();
+ }
+
+ template <typename X>
+ struct parser
+ {
+ static void
+ parse (X& x, bool& xs, scanner& s)
+ {
+ std::string o (s.next ());
+
+ if (s.more ())
+ {
+ std::string v (s.next ());
+ std::istringstream is (v);
+ if (!(is >> x && is.eof ()))
+ throw invalid_value (o, v);
+ }
+ else
+ throw missing_value (o);
+
+ xs = true;
+ }
+ };
+
+ template <>
+ struct parser<bool>
+ {
+ static void
+ parse (bool& x, scanner& s)
+ {
+ s.next ();
+ x = true;
+ }
+ };
+
+ template <>
+ struct parser<std::string>
+ {
+ static void
+ parse (std::string& x, bool& xs, scanner& s)
+ {
+ const char* o (s.next ());
+
+ if (s.more ())
+ x = s.next ();
+ else
+ throw missing_value (o);
+
+ xs = true;
+ }
+ };
+
+ template <typename X>
+ struct parser<std::vector<X> >
+ {
+ static void
+ parse (std::vector<X>& c, bool& xs, scanner& s)
+ {
+ X x;
+ bool dummy;
+ parser<X>::parse (x, dummy, s);
+ c.push_back (x);
+ xs = true;
+ }
+ };
+
+ template <typename X>
+ struct parser<std::set<X> >
+ {
+ static void
+ parse (std::set<X>& c, bool& xs, scanner& s)
+ {
+ X x;
+ bool dummy;
+ parser<X>::parse (x, dummy, s);
+ c.insert (x);
+ xs = true;
+ }
+ };
+
+ template <typename K, typename V>
+ struct parser<std::map<K, V> >
+ {
+ static void
+ parse (std::map<K, V>& m, bool& xs, scanner& s)
+ {
+ const char* o (s.next ());
+
+ if (s.more ())
+ {
+ std::string ov (s.next ());
+ std::string::size_type p = ov.find ('=');
+
+ K k = K ();
+ V v = V ();
+ std::string kstr (ov, 0, p);
+ std::string vstr (ov, (p != std::string::npos ? p + 1 : ov.size ()));
+
+ int ac (2);
+ char* av[] =
+ {
+ const_cast<char*> (o), 0
+ };
+
+ bool dummy;
+ if (!kstr.empty ())
+ {
+ av[1] = const_cast<char*> (kstr.c_str ());
+ argv_scanner s (0, ac, av);
+ parser<K>::parse (k, dummy, s);
+ }
+
+ if (!vstr.empty ())
+ {
+ av[1] = const_cast<char*> (vstr.c_str ());
+ argv_scanner s (0, ac, av);
+ parser<V>::parse (v, dummy, s);
+ }
+
+ m[k] = v;
+ }
+ else
+ throw missing_value (o);
+
+ xs = true;
+ }
+ };
+
+ template <typename X, typename T, T X::*M>
+ void
+ thunk (X& x, scanner& s)
+ {
+ parser<T>::parse (x.*M, s);
+ }
+
+ template <typename X, typename T, T X::*M, bool X::*S>
+ void
+ thunk (X& x, scanner& s)
+ {
+ parser<T>::parse (x.*M, x.*S, s);
+ }
+}
+
+#include <map>
+#include <cstring>
+
+// options
+//
+
+options::
+options ()
+: help_ (),
+ version_ (),
+ v_ (),
+ q_ (),
+ verbose_ (1),
+ verbose_specified_ (false)
+{
+}
+
+options::
+options (int& argc,
+ char** argv,
+ bool erase,
+ ::cl::unknown_mode opt,
+ ::cl::unknown_mode arg)
+: help_ (),
+ version_ (),
+ v_ (),
+ q_ (),
+ verbose_ (1),
+ verbose_specified_ (false)
+{
+ ::cl::argv_scanner s (argc, argv, erase);
+ _parse (s, opt, arg);
+}
+
+options::
+options (int start,
+ int& argc,
+ char** argv,
+ bool erase,
+ ::cl::unknown_mode opt,
+ ::cl::unknown_mode arg)
+: help_ (),
+ version_ (),
+ v_ (),
+ q_ (),
+ verbose_ (1),
+ verbose_specified_ (false)
+{
+ ::cl::argv_scanner s (start, argc, argv, erase);
+ _parse (s, opt, arg);
+}
+
+options::
+options (int& argc,
+ char** argv,
+ int& end,
+ bool erase,
+ ::cl::unknown_mode opt,
+ ::cl::unknown_mode arg)
+: help_ (),
+ version_ (),
+ v_ (),
+ q_ (),
+ verbose_ (1),
+ verbose_specified_ (false)
+{
+ ::cl::argv_scanner s (argc, argv, erase);
+ _parse (s, opt, arg);
+ end = s.end ();
+}
+
+options::
+options (int start,
+ int& argc,
+ char** argv,
+ int& end,
+ bool erase,
+ ::cl::unknown_mode opt,
+ ::cl::unknown_mode arg)
+: help_ (),
+ version_ (),
+ v_ (),
+ q_ (),
+ verbose_ (1),
+ verbose_specified_ (false)
+{
+ ::cl::argv_scanner s (start, argc, argv, erase);
+ _parse (s, opt, arg);
+ end = s.end ();
+}
+
+options::
+options (::cl::scanner& s,
+ ::cl::unknown_mode opt,
+ ::cl::unknown_mode arg)
+: help_ (),
+ version_ (),
+ v_ (),
+ q_ (),
+ verbose_ (1),
+ verbose_specified_ (false)
+{
+ _parse (s, opt, arg);
+}
+
+::cl::usage_para options::
+print_usage (::std::ostream& os, ::cl::usage_para p)
+{
+ CLI_POTENTIALLY_UNUSED (os);
+
+ if (p == ::cl::usage_para::text)
+ os << ::std::endl;
+
+ os << "--help Print usage information and exit." << ::std::endl;
+
+ os << "--version Print version and exit." << ::std::endl;
+
+ os << "-v Print actual commands being executed." << ::std::endl;
+
+ os << "-q Run quietly, only printing error messages." << ::std::endl;
+
+ os << "--verbose <level> Set the diagnostics verbosity to <level> between 0 (disabled)" << ::std::endl
+ << " and 6 (lots of information)." << ::std::endl;
+
+ p = ::cl::usage_para::option;
+
+ return p;
+}
+
+typedef
+std::map<std::string, void (*) (options&, ::cl::scanner&)>
+_cli_options_map;
+
+static _cli_options_map _cli_options_map_;
+
+struct _cli_options_map_init
+{
+ _cli_options_map_init ()
+ {
+ _cli_options_map_["--help"] =
+ &::cl::thunk< options, bool, &options::help_ >;
+ _cli_options_map_["--version"] =
+ &::cl::thunk< options, bool, &options::version_ >;
+ _cli_options_map_["-v"] =
+ &::cl::thunk< options, bool, &options::v_ >;
+ _cli_options_map_["-q"] =
+ &::cl::thunk< options, bool, &options::q_ >;
+ _cli_options_map_["--verbose"] =
+ &::cl::thunk< options, std::uint16_t, &options::verbose_,
+ &options::verbose_specified_ >;
+ }
+};
+
+static _cli_options_map_init _cli_options_map_init_;
+
+bool options::
+_parse (const char* o, ::cl::scanner& s)
+{
+ _cli_options_map::const_iterator i (_cli_options_map_.find (o));
+
+ if (i != _cli_options_map_.end ())
+ {
+ (*(i->second)) (*this, s);
+ return true;
+ }
+
+ return false;
+}
+
+void options::
+_parse (::cl::scanner& s,
+ ::cl::unknown_mode opt_mode,
+ ::cl::unknown_mode arg_mode)
+{
+ bool opt = true;
+
+ while (s.more ())
+ {
+ const char* o = s.peek ();
+
+ if (std::strcmp (o, "--") == 0)
+ {
+ s.skip ();
+ opt = false;
+ continue;
+ }
+
+ if (opt && _parse (o, s));
+ else if (opt && std::strncmp (o, "-", 1) == 0 && o[1] != '\0')
+ {
+ switch (opt_mode)
+ {
+ case ::cl::unknown_mode::skip:
+ {
+ s.skip ();
+ continue;
+ }
+ case ::cl::unknown_mode::stop:
+ {
+ break;
+ }
+ case ::cl::unknown_mode::fail:
+ {
+ throw ::cl::unknown_option (o);
+ }
+ }
+
+ break;
+ }
+ else
+ {
+ switch (arg_mode)
+ {
+ case ::cl::unknown_mode::skip:
+ {
+ s.skip ();
+ continue;
+ }
+ case ::cl::unknown_mode::stop:
+ {
+ break;
+ }
+ case ::cl::unknown_mode::fail:
+ {
+ throw ::cl::unknown_argument (o);
+ }
+ }
+
+ break;
+ }
+ }
+}
+
+// Begin epilogue.
+//
+//
+// End epilogue.
+
diff --git a/build2/options.ixx b/build2/options.ixx
new file mode 100644
index 0000000..245e0c0
--- /dev/null
+++ b/build2/options.ixx
@@ -0,0 +1,165 @@
+// -*- C++ -*-
+//
+// This file was generated by CLI, a command line interface
+// compiler for C++.
+//
+
+// Begin prologue.
+//
+//
+// End prologue.
+
+namespace cl
+{
+ // usage_para
+ //
+ inline usage_para::
+ usage_para (value v)
+ : v_ (v)
+ {
+ }
+
+ // unknown_mode
+ //
+ inline unknown_mode::
+ unknown_mode (value v)
+ : v_ (v)
+ {
+ }
+
+ // exception
+ //
+ inline ::std::ostream&
+ operator<< (::std::ostream& os, const exception& e)
+ {
+ e.print (os);
+ return os;
+ }
+
+ // unknown_option
+ //
+ inline unknown_option::
+ unknown_option (const std::string& option)
+ : option_ (option)
+ {
+ }
+
+ inline const std::string& unknown_option::
+ option () const
+ {
+ return option_;
+ }
+
+ // unknown_argument
+ //
+ inline unknown_argument::
+ unknown_argument (const std::string& argument)
+ : argument_ (argument)
+ {
+ }
+
+ inline const std::string& unknown_argument::
+ argument () const
+ {
+ return argument_;
+ }
+
+ // missing_value
+ //
+ inline missing_value::
+ missing_value (const std::string& option)
+ : option_ (option)
+ {
+ }
+
+ inline const std::string& missing_value::
+ option () const
+ {
+ return option_;
+ }
+
+ // invalid_value
+ //
+ inline invalid_value::
+ invalid_value (const std::string& option,
+ const std::string& value)
+ : option_ (option), value_ (value)
+ {
+ }
+
+ inline const std::string& invalid_value::
+ option () const
+ {
+ return option_;
+ }
+
+ inline const std::string& invalid_value::
+ value () const
+ {
+ return value_;
+ }
+
+ // argv_scanner
+ //
+ inline argv_scanner::
+ argv_scanner (int& argc, char** argv, bool erase)
+ : i_ (1), argc_ (argc), argv_ (argv), erase_ (erase)
+ {
+ }
+
+ inline argv_scanner::
+ argv_scanner (int start, int& argc, char** argv, bool erase)
+ : i_ (start), argc_ (argc), argv_ (argv), erase_ (erase)
+ {
+ }
+
+ inline int argv_scanner::
+ end () const
+ {
+ return i_;
+ }
+}
+
+// options
+//
+
+inline const bool& options::
+help () const
+{
+ return this->help_;
+}
+
+inline const bool& options::
+version () const
+{
+ return this->version_;
+}
+
+inline const bool& options::
+v () const
+{
+ return this->v_;
+}
+
+inline const bool& options::
+q () const
+{
+ return this->q_;
+}
+
+inline const std::uint16_t& options::
+verbose () const
+{
+ return this->verbose_;
+}
+
+inline bool options::
+verbose_specified () const
+{
+ return this->verbose_specified_;
+}
+
+// Begin epilogue.
+//
+//
+// End epilogue.
diff --git a/build2/parser b/build2/parser
new file mode 100644
index 0000000..f8bd97a
--- /dev/null
+++ b/build2/parser
@@ -0,0 +1,296 @@
+// file : build2/parser -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_PARSER
+#define BUILD2_PARSER
+
+#include <string>
+#include <iosfwd>
+
+#include <build2/types>
+#include <build2/utility>
+
+#include <build2/spec>
+#include <build2/lexer>
+#include <build2/token>
+#include <build2/variable> // list_value
+#include <build2/diagnostics>
+
+namespace build2
+{
+ class scope;
+ class target;
+
+ class parser
+ {
+ public:
+ typedef build2::names names_type;
+ typedef build2::variable variable_type;
+
+ // If boot is true, then we are parsing bootstrap.build and modules
+ // should only be bootstrapped.
+ //
+ parser (bool boot = false): fail (&path_), boot_ (boot) {}
+
+ // Issue diagnostics and throw failed in case of an error.
+ //
+ void
+ parse_buildfile (std::istream&, const path&, scope& root, scope& base);
+
+ buildspec
+ parse_buildspec (std::istream&, const std::string& name);
+
+ token
+ parse_variable (lexer&, scope&, std::string name, token_type kind);
+
+ names_type
+ parse_export_stub (std::istream& is, const path& p, scope& r, scope& b)
+ {
+ parse_buildfile (is, p, r, b);
+ return std::move (export_value_);
+ }
+
+ // Recursive descent parser.
+ //
+ protected:
+ void
+ clause (token&, token_type&);
+
+ void
+ print (token&, token_type&);
+
+ void
+ source (token&, token_type&);
+
+ void
+ include (token&, token_type&);
+
+ void
+ import (token&, token_type&);
+
+ void
+ export_ (token&, token_type&);
+
+ void
+ using_ (token&, token_type&);
+
+ void
+ define (token&, token_type&);
+
+ void
+ if_else (token&, token_type&);
+
+ void
+ variable (token&, token_type&, std::string name, token_type kind);
+
+ std::string
+ variable_name (names_type&&, const location&);
+
+ names_type
+ variable_value (token&, token_type&, const variable_type&);
+
+ names_type
+ eval (token&, token_type&);
+
+ // If chunk is true, then parse the smallest but complete, name-wise,
+ // chunk of input. Note that in this case you may still end up with
+ // multiple names, for example, {foo bar}.
+ //
+ names_type
+ names (token& t, token_type& tt, bool chunk = false)
+ {
+ names_type ns;
+ names (t, tt, ns, chunk, 0, nullptr, nullptr, nullptr);
+ return ns;
+ }
+
+ void
+ names (token&, token_type&,
+ names_type&,
+ bool chunk,
+ std::size_t pair,
+ const std::string* prj,
+ const dir_path* dir,
+ const std::string* type);
+
+ size_t
+ names_trailer (token&, token_type&,
+ names_type&,
+ size_t pair,
+ const std::string* prj,
+ const dir_path* dir,
+ const std::string* type);
+
+ // Skip until newline or eos.
+ //
+ void
+ skip_line (token&, token_type&);
+
+ // Skip until block-closing } or eos, taking into account nested blocks.
+ //
+ void
+ skip_block (token&, token_type&);
+
+ // Return true if the name token can be considered a directive keyword.
+ //
+ bool
+ keyword (token&);
+
+ // Buildspec.
+ //
+ buildspec
+ buildspec_clause (token&, token_type&, token_type end);
+
+ // Utilities.
+ //
+ protected:
+
+ // 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&);
+
+ void
+ process_default_target (token&);
+
+ // Enter buildfile as a target.
+ //
+ void
+ enter_buildfile (const path&);
+
+ // Lexer.
+ //
+ protected:
+ token_type
+ next (token&, token_type&);
+
+ // Be careful with peeking and switching the lexer mode. See keyword()
+ // for more information.
+ //
+ token_type
+ peek ();
+
+ const token&
+ peeked () const
+ {
+ assert (peeked_);
+ return peek_;
+ }
+
+ void
+ mode (lexer_mode m, char ps = '=')
+ {
+ if (replay_ != replay::play)
+ lexer_->mode (m, ps);
+ }
+
+ lexer_mode
+ mode () const
+ {
+ assert (replay_ != replay::play);
+ return lexer_->mode ();
+ }
+
+ void
+ expire_mode ()
+ {
+ if (replay_ != replay::play)
+ lexer_->expire_mode ();
+ }
+
+ // Token saving and replaying. Note that is can only be used in certain
+ // contexts. Specifically, the lexer mode should be the same and the code
+ // that parses a replay must not interact with the lexer directly (e.g.,
+ // the keyword() test). For now we don't enforce any of this.
+ //
+ // Note also that the peeked token is not part of the replay, until it
+ // is "got".
+ //
+ //
+ void
+ replay_save ()
+ {
+ assert (replay_ == replay::stop);
+ replay_ = replay::save;
+ }
+
+ void
+ replay_play ()
+ {
+ assert ((replay_ == replay::save && !replay_data_.empty ()) ||
+ (replay_ == replay::play && replay_i_ == replay_data_.size ()));
+
+ replay_i_ = 0;
+ replay_ = replay::play;
+ }
+
+ void
+ replay_stop ()
+ {
+ replay_data_.clear ();
+ replay_ = replay::stop;
+ }
+
+ const token&
+ replay_next ()
+ {
+ assert (replay_i_ != replay_data_.size ());
+ return replay_data_[replay_i_++];
+ }
+
+ struct replay_guard
+ {
+ replay_guard (parser& p, bool start = true)
+ : p_ (start ? &p : nullptr)
+ {
+ if (p_ != nullptr)
+ p_->replay_save ();
+ }
+
+ void
+ play ()
+ {
+ if (p_ != nullptr)
+ p_->replay_play ();
+ }
+
+ ~replay_guard ()
+ {
+ if (p_ != nullptr)
+ p_->replay_stop ();
+ }
+
+ private:
+ parser* p_;
+ };
+
+ // Diagnostics.
+ //
+ protected:
+ const fail_mark<failed> fail;
+
+ protected:
+ bool boot_;
+
+ const std::string* path_; // Path processed by diag_relative() and pooled.
+ lexer* lexer_;
+ target* target_; // Current target, if any.
+ scope* scope_; // Current base scope (out_base).
+ scope* root_; // Current root scope (out_root).
+ target* default_target_;
+ names_type export_value_;
+
+ token peek_ = token (token_type::eos, false, 0, 0);
+ bool peeked_ = false;
+
+ enum class replay {stop, save, play} replay_ = replay::stop;
+ vector<token> replay_data_;
+ size_t replay_i_; // Position of the next token during replay.
+ };
+}
+
+#endif // BUILD2_PARSER
diff --git a/build2/parser.cxx b/build2/parser.cxx
new file mode 100644
index 0000000..139d2a2
--- /dev/null
+++ b/build2/parser.cxx
@@ -0,0 +1,2206 @@
+// file : build2/parser.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/parser>
+
+#include <cctype> // is{alpha alnum}()
+
+#include <memory> // unique_ptr
+#include <fstream>
+#include <utility> // move()
+#include <iterator> // make_move_iterator()
+#include <iostream>
+
+#include <build2/types>
+#include <build2/utility>
+#include <build2/version>
+
+#include <build2/scope>
+#include <build2/target>
+#include <build2/prerequisite>
+#include <build2/variable>
+#include <build2/module>
+#include <build2/file>
+#include <build2/diagnostics>
+#include <build2/context>
+
+using namespace std;
+
+namespace build2
+{
+ static location
+ get_location (const token&, const void*);
+
+ typedef token_type type;
+
+ void parser::
+ parse_buildfile (istream& is, const path& p, scope& root, scope& base)
+ {
+ enter_buildfile (p);
+
+ path_ = &path_pool.find (diag_relative (p)); // Relative to work.
+
+ lexer l (is, *path_);
+ lexer_ = &l;
+ target_ = nullptr;
+ scope_ = &base;
+ root_ = &root;
+ default_target_ = nullptr;
+
+ token t (type::eos, false, 0, 0);
+ type tt;
+ next (t, tt);
+
+ clause (t, tt);
+
+ if (tt != type::eos)
+ fail (t) << "unexpected " << t;
+
+ process_default_target (t);
+ }
+
+ token parser::
+ parse_variable (lexer& l, scope& s, string name, type kind)
+ {
+ path_ = &l.name (); // Note: not pooled.
+ lexer_ = &l;
+ target_ = nullptr;
+ scope_ = &s;
+
+ type tt;
+ token t (type::eos, false, 0, 0);
+ variable (t, tt, name, kind);
+ return t;
+ }
+
+ void parser::
+ clause (token& t, type& tt)
+ {
+ tracer trace ("parser::clause", &path_);
+
+ // clause() should always stop at a token that is at the beginning of
+ // the line (except for eof). That is, if something is called to parse
+ // a line, it should parse it until newline (or fail). This is important
+ // for if-else blocks, directory scopes, etc., that assume the } token
+ // they see is on the new line.
+ //
+ while (tt != type::eos)
+ {
+ // We always start with one or more names.
+ //
+ if (tt != type::name &&
+ tt != type::lcbrace && // Untyped name group: '{foo ...'
+ tt != type::dollar && // Variable expansion: '$foo ...'
+ tt != type::lparen && // Eval context: '(foo) ...'
+ tt != type::colon) // Empty name: ': ...'
+ break; // Something else. Let our caller handle that.
+
+ // See if this is one of the directives.
+ //
+ if (tt == type::name && keyword (t))
+ {
+ const string& n (t.value);
+
+ if (n == "print")
+ {
+ // @@ Is this the only place where it is valid? Probably also
+ // in var namespace.
+ //
+ print (t, tt);
+ continue;
+ }
+ else if (n == "source")
+ {
+ source (t, tt);
+ continue;
+ }
+ else if (n == "include")
+ {
+ include (t, tt);
+ continue;
+ }
+ else if (n == "import")
+ {
+ import (t, tt);
+ continue;
+ }
+ else if (n == "export")
+ {
+ export_ (t, tt);
+ continue;
+ }
+ else if (n == "using" ||
+ n == "using?")
+ {
+ using_ (t, tt);
+ continue;
+ }
+ else if (n == "define")
+ {
+ define (t, tt);
+ continue;
+ }
+ else if (n == "if" ||
+ n == "if!")
+ {
+ if_else (t, tt);
+ continue;
+ }
+ else if (n == "else" ||
+ n == "elif" ||
+ n == "elif!")
+ {
+ // Valid ones are handled in if_else().
+ //
+ fail (t) << n << " without if";
+ }
+ }
+
+ // ': foo' is equvalent to '{}: foo' and to 'dir{}: foo'.
+ //
+ const location nloc (get_location (t, &path_));
+ names_type ns (tt != type::colon
+ ? names (t, tt)
+ : names_type ({name ("dir", string ())}));
+
+ if (tt == type::colon)
+ {
+ // While '{}:' means empty name, '{$x}:' where x is empty list
+ // means empty list.
+ //
+ if (ns.empty ())
+ fail (t) << "target expected before :";
+
+ next (t, tt);
+
+ if (tt == type::newline)
+ {
+ // See if this is a directory/target scope.
+ //
+ if (peek () == type::lcbrace)
+ {
+ next (t, tt);
+
+ // Should be on its own line.
+ //
+ if (next (t, tt) != type::newline)
+ fail (t) << "expected newline after {";
+
+ // See if this is a directory or target scope. Different
+ // things can appear inside depending on which one it is.
+ //
+ bool dir (false);
+ for (const auto& n: ns)
+ {
+ // A name represents directory as an empty value.
+ //
+ if (n.directory ())
+ {
+ if (ns.size () != 1)
+ {
+ // @@ TODO: point to name (and above).
+ //
+ fail (nloc) << "multiple names in directory scope";
+ }
+
+ dir = true;
+ }
+ }
+
+ next (t, tt);
+
+ if (dir)
+ {
+ // Directory scope.
+ //
+ dir_path p (move (ns[0].dir)); // Steal.
+
+ // Relative scopes are opened relative to out, not src.
+ //
+ if (p.relative ())
+ p = scope_->out_path () / p;
+
+ p.normalize ();
+
+ 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;
+ root_ = ors;
+ }
+ else
+ {
+ // @@ TODO: target scope.
+ }
+
+ if (tt != type::rcbrace)
+ fail (t) << "expected } instead of " << t;
+
+ // Should be on its own line.
+ //
+ if (next (t, tt) == type::newline)
+ next (t, tt);
+ else if (tt != type::eos)
+ fail (t) << "expected newline after }";
+
+ continue;
+ }
+
+ // If this is not a scope, then it is a target without any
+ // prerequisites.
+ //
+ }
+
+ // Dependency declaration or scope/target-specific variable
+ // assignment.
+ //
+ if (tt == type::name ||
+ tt == type::lcbrace ||
+ tt == type::dollar ||
+ tt == type::lparen ||
+ tt == type::newline ||
+ tt == type::eos)
+ {
+ const location ploc (get_location (t, &path_));
+ names_type pns (tt != type::newline && tt != type::eos
+ ? names (t, tt)
+ : names_type ());
+
+ // Common target entering code used in both cases.
+ //
+ auto enter_target = [this, &nloc, &trace] (name&& tn) -> target&
+ {
+ const string* e;
+ const target_type* ti (scope_->find_target_type (tn, e));
+
+ if (ti == nullptr)
+ fail (nloc) << "unknown target type " << tn.type;
+
+ path& d (tn.dir);
+
+ if (d.empty ())
+ d = scope_->out_path (); // Already normalized.
+ else
+ {
+ if (d.relative ())
+ d = scope_->out_path () / d;
+
+ d.normalize ();
+ }
+
+ // Find or insert.
+ //
+ return targets.insert (
+ *ti, move (tn.dir), move (tn.value), e, trace).first;
+ };
+
+ // Scope/target-specific variable assignment.
+ //
+ if (tt == type::equal ||
+ tt == type::equal_plus ||
+ tt == type::plus_equal)
+ {
+ token at (t);
+ type att (tt);
+
+ string v (variable_name (move (pns), ploc));
+
+ // If we have multiple targets/scopes, then we save the value
+ // tokens when parsing the first one and then replay them for
+ // the subsequent. We have to do it this way because the value
+ // may contain variable expansions that would be sensitive to
+ // the target/scope context in which they are evaluated.
+ //
+ replay_guard rg (*this, ns.size () > 1);
+
+ for (name& n: ns)
+ {
+ if (n.qualified ())
+ fail (nloc) << "project name in scope/target " << n;
+
+ if (n.directory ())
+ {
+ // The same code as in directory scope handling code above.
+ //
+ dir_path p (move (n.dir));
+
+ if (p.relative ())
+ p = scope_->out_path () / p;
+
+ p.normalize ();
+
+ scope* ors (root_);
+ scope* ocs (scope_);
+ switch_scope (p);
+
+ variable (t, tt, v, att);
+
+ scope_ = ocs;
+ root_ = ors;
+ }
+ else
+ {
+ // Figure out if this is a target or type/pattern specific
+ // variable.
+ //
+ size_t p (n.value.find ('*'));
+
+ if (p == string::npos)
+ {
+ target* ot (target_);
+ target_ = &enter_target (move (n));
+ variable (t, tt, v, att);
+ target_ = ot;
+ }
+ else
+ {
+ // See tests/variable/type-pattern.
+ //
+ if (!n.dir.empty ())
+ fail (nloc) << "directory in target type/pattern " << n;
+
+ if (n.value.find ('*', p + 1) != string::npos)
+ fail (nloc) << "multiple wildcards in target type/pattern "
+ << n;
+
+ // Resolve target type. If none is specified, use the root
+ // of the hierarchy.
+ //
+ const target_type* ti (
+ n.untyped ()
+ ? &target::static_type
+ : scope_->find_target_type (n.type));
+
+ if (ti == nullptr)
+ fail (nloc) << "unknown target type " << n.type;
+
+ if (att == type::equal_plus)
+ fail (at) << "prepend to target type/pattern-specific "
+ << "variable " << v;
+
+ if (att == type::plus_equal)
+ fail (at) << "append to target type/pattern-specific "
+ << "variable " << v;
+
+ const auto& var (var_pool.find (v));
+
+ // Note: expand variables in the value in the context of
+ // the scope.
+ //
+ names_type vns (variable_value (t, tt, var));
+ value& val (scope_->target_vars[*ti][move (n.value)].assign (
+ var).first);
+ val.assign (move (vns), var);
+ }
+ }
+
+ rg.play (); // Replay.
+ }
+ }
+ // Dependency declaration.
+ //
+ else
+ {
+ // Prepare the prerequisite list.
+ //
+ target::prerequisites_type ps;
+ ps.reserve (pns.size ());
+
+ for (auto& pn: pns)
+ {
+ const string* e;
+ const target_type* ti (scope_->find_target_type (pn, e));
+
+ if (ti == nullptr)
+ fail (ploc) << "unknown target type " << pn.type;
+
+ pn.dir.normalize ();
+
+ // Find or insert.
+ //
+ prerequisite& p (
+ scope_->prerequisites.insert (
+ pn.proj,
+ *ti,
+ move (pn.dir),
+ move (pn.value),
+ e,
+ *scope_,
+ trace).first);
+
+ ps.emplace_back (p);
+ }
+
+ for (auto& tn: ns)
+ {
+ if (tn.qualified ())
+ fail (nloc) << "project name in target " << tn;
+
+ target& t (enter_target (move (tn)));
+
+ //@@ OPT: move if last/single target (common cases).
+ //
+ t.prerequisites.insert (t.prerequisites.end (),
+ ps.begin (),
+ ps.end ());
+
+ if (default_target_ == nullptr)
+ default_target_ = &t;
+ }
+ }
+
+ if (tt == type::newline)
+ next (t, tt);
+ else if (tt != type::eos)
+ fail (t) << "expected newline instead of " << t;
+
+ continue;
+ }
+
+ if (tt == type::eos)
+ continue;
+
+ fail (t) << "expected newline instead of " << t;
+ }
+
+ // Variable assignment.
+ //
+ if (tt == type::equal ||
+ tt == type::equal_plus ||
+ tt == type::plus_equal)
+ {
+ variable (t, tt, variable_name (move (ns), nloc), tt);
+
+ if (tt == type::newline)
+ next (t, tt);
+ else if (tt != type::eos)
+ fail (t) << "expected newline instead of " << t;
+
+ continue;
+ }
+
+ // Allow things like function calls that don't result in anything.
+ //
+ if (tt == type::newline && ns.empty ())
+ {
+ next (t, tt);
+ continue;
+ }
+
+ fail (t) << "unexpected " << t;
+ }
+ }
+
+ void parser::
+ source (token& t, type& tt)
+ {
+ tracer trace ("parser::source", &path_);
+
+ // The rest should be a list of buildfiles. Parse them as names
+ // to get variable expansion and directory prefixes.
+ //
+ mode (lexer_mode::value);
+ next (t, tt);
+ const location l (get_location (t, &path_));
+ names_type ns (tt != type::newline && tt != type::eos
+ ? names (t, tt)
+ : names_type ());
+
+ for (name& n: ns)
+ {
+ if (n.qualified () || n.empty () || n.value.empty ())
+ fail (l) << "expected buildfile instead of " << n;
+
+ // Construct the buildfile path.
+ //
+ path p (move (n.dir));
+ p /= path (move (n.value));
+
+ // If the path is relative then use the src directory corresponding
+ // to the current directory scope.
+ //
+ if (root_->src_path_ != nullptr && p.relative ())
+ p = src_out (scope_->out_path (), *root_) / p;
+
+ p.normalize ();
+
+ try
+ {
+ ifstream ifs (p.string ());
+
+ if (!ifs.is_open ())
+ fail (l) << "unable to open " << p;
+
+ ifs.exceptions (ifstream::failbit | ifstream::badbit);
+
+ level5 ([&]{trace (t) << "entering " << p;});
+
+ enter_buildfile (p);
+
+ const string* op (path_);
+ path_ = &path_pool.find (diag_relative (p)); // Relative to work.
+
+ lexer l (ifs, *path_);
+ lexer* ol (lexer_);
+ lexer_ = &l;
+
+ token t (type::eos, false, 0, 0);
+ type tt;
+ next (t, tt);
+ clause (t, tt);
+
+ if (tt != type::eos)
+ fail (t) << "unexpected " << t;
+
+ level5 ([&]{trace (t) << "leaving " << p;});
+
+ lexer_ = ol;
+ path_ = op;
+ }
+ catch (const ifstream::failure&)
+ {
+ fail (l) << "unable to read buildfile " << p;
+ }
+ }
+
+ if (tt == type::newline)
+ next (t, tt);
+ else if (tt != type::eos)
+ fail (t) << "expected newline instead of " << t;
+ }
+
+ void parser::
+ include (token& t, type& tt)
+ {
+ tracer trace ("parser::include", &path_);
+
+ if (root_->src_path_ == nullptr)
+ fail (t) << "inclusion during bootstrap";
+
+ // The rest should be a list of buildfiles. Parse them as names
+ // to get variable expansion and directory prefixes.
+ //
+ mode (lexer_mode::value);
+ next (t, tt);
+ const location l (get_location (t, &path_));
+ names_type ns (tt != type::newline && tt != type::eos
+ ? names (t, tt)
+ : names_type ());
+
+ for (name& n: ns)
+ {
+ if (n.qualified () || n.empty ())
+ fail (l) << "expected buildfile instead of " << n;
+
+ // Construct the buildfile path. If it is a directory, then append
+ // 'buildfile'.
+ //
+ path p (move (n.dir));
+ if (n.value.empty ())
+ p /= path ("buildfile");
+ else
+ {
+ bool d (path::traits::is_separator (n.value.back ())
+ || n.type == "dir");
+
+ p /= path (move (n.value));
+ if (d)
+ p /= path ("buildfile");
+ }
+
+ level6 ([&]{trace (l) << "relative path " << p;});
+
+ // Determine new out_base.
+ //
+ dir_path out_base;
+
+ if (p.relative ())
+ {
+ out_base = scope_->out_path () / p.directory ();
+ out_base.normalize ();
+ }
+ else
+ {
+ p.normalize ();
+
+ // Make sure the path is in this project. Include is only meant
+ // to be used for intra-project inclusion (plus amalgamation).
+ //
+ bool in_out (false);
+ if (!p.sub (root_->src_path ()) &&
+ !(in_out = p.sub (root_->out_path ())))
+ fail (l) << "out of project include " << p;
+
+ out_base = in_out
+ ? p.directory ()
+ : out_src (p.directory (), *root_);
+ }
+
+ // 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 (root_);
+ scope* ocs (scope_);
+ switch_scope (out_base);
+
+ // Use the new scope's src_base to get absolute buildfile path
+ // if it is relative.
+ //
+ if (p.relative ())
+ p = scope_->src_path () / p.leaf ();
+
+ level6 ([&]{trace (l) << "absolute path " << p;});
+
+ if (!root_->buildfiles.insert (p).second) // Note: may be "new" root.
+ {
+ level5 ([&]{trace (l) << "skipping already included " << p;});
+ scope_ = ocs;
+ root_ = ors;
+ continue;
+ }
+
+ try
+ {
+ ifstream ifs (p.string ());
+
+ if (!ifs.is_open ())
+ fail (l) << "unable to open " << p;
+
+ ifs.exceptions (ifstream::failbit | ifstream::badbit);
+
+ level5 ([&]{trace (t) << "entering " << p;});
+
+ enter_buildfile (p);
+
+ const string* op (path_);
+ path_ = &path_pool.find (diag_relative (p)); // Relative to work.
+
+ lexer l (ifs, *path_);
+ lexer* ol (lexer_);
+ lexer_ = &l;
+
+ target* odt (default_target_);
+ default_target_ = nullptr;
+
+ token t (type::eos, false, 0, 0);
+ type tt;
+ next (t, tt);
+ clause (t, tt);
+
+ if (tt != type::eos)
+ fail (t) << "unexpected " << t;
+
+ process_default_target (t);
+
+ level5 ([&]{trace (t) << "leaving " << p;});
+
+ default_target_ = odt;
+ lexer_ = ol;
+ path_ = op;
+ }
+ catch (const ifstream::failure&)
+ {
+ fail (l) << "unable to read buildfile " << p;
+ }
+
+ scope_ = ocs;
+ root_ = ors;
+ }
+
+ if (tt == type::newline)
+ next (t, tt);
+ else if (tt != type::eos)
+ fail (t) << "expected newline instead of " << t;
+ }
+
+ void parser::
+ import (token& t, type& tt)
+ {
+ tracer trace ("parser::import", &path_);
+
+ if (root_->src_path_ == nullptr)
+ fail (t) << "import during bootstrap";
+
+ next (t, tt);
+
+ // General import format:
+ //
+ // import [<var>=](<project>|<project>/<target>])+
+ //
+ value* val (nullptr);
+ const build2::variable* var (nullptr);
+
+ type at; // Assignment type.
+ if (tt == type::name)
+ {
+ at = peek ();
+
+ if (at == type::equal ||
+ at == type::equal_plus ||
+ at == type::plus_equal)
+ {
+ var = &var_pool.find (t.value);
+ val = at == type::equal
+ ? &scope_->assign (*var)
+ : &scope_->append (*var);
+ next (t, tt); // Consume =/=+/+=.
+ mode (lexer_mode::value);
+ next (t, tt);
+ }
+ }
+
+ // The rest should be a list of projects and/or targets. Parse
+ // them as names to get variable expansion and directory prefixes.
+ //
+ const location l (get_location (t, &path_));
+ names_type ns (tt != type::newline && tt != type::eos
+ ? names (t, tt)
+ : names_type ());
+
+ for (name& n: ns)
+ {
+ // build2::import() will check the name, if required.
+ //
+ names_type r (build2::import (*scope_, move (n), l));
+
+ if (val != nullptr)
+ {
+ if (at == type::equal)
+ val->assign (move (r), *var);
+ else if (at == type::equal_plus)
+ val->prepend (move (r), *var);
+ else
+ val->append (move (r), *var);
+ }
+ }
+
+ if (tt == type::newline)
+ next (t, tt);
+ else if (tt != type::eos)
+ fail (t) << "expected newline instead of " << t;
+ }
+
+ void parser::
+ export_ (token& t, type& tt)
+ {
+ tracer trace ("parser::export", &path_);
+
+ scope* ps (scope_->parent_scope ());
+
+ // This should be temp_scope.
+ //
+ 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.
+ // build2::import() will check the names, if required.
+ //
+ mode (lexer_mode::value);
+ next (t, tt);
+
+ if (tt != type::newline && tt != type::eos)
+ export_value_ = names (t, tt);
+
+ if (tt == type::newline)
+ next (t, tt);
+ else if (tt != type::eos)
+ fail (t) << "expected newline instead of " << t;
+ }
+
+ void parser::
+ using_ (token& t, type& tt)
+ {
+ tracer trace ("parser::using", &path_);
+
+ bool optional (t.value.back () == '?');
+
+ if (optional && boot_)
+ fail (t) << "optional module in bootstrap";
+
+ // The rest should be a list of module names. Parse them as names
+ // to get variable expansion, etc.
+ //
+ mode (lexer_mode::pairs, '@');
+ next (t, tt);
+ const location l (get_location (t, &path_));
+ names_type ns (tt != type::newline && tt != type::eos
+ ? names (t, tt)
+ : names_type ());
+
+ for (auto i (ns.begin ()); i != ns.end (); ++i)
+ {
+ string n, v;
+
+ if (!i->simple ())
+ fail (l) << "module name expected instead of " << *i;
+
+ n = move (i->value);
+
+ if (i->pair)
+ {
+ ++i;
+ if (!i->simple ())
+ fail (l) << "module version expected instead of " << *i;
+
+ v = move (i->value);
+ }
+
+ // Handle the special 'build' module.
+ //
+ if (n == "build")
+ {
+ if (!v.empty ())
+ {
+ unsigned int iv;
+ try {iv = to_version (v);}
+ catch (const invalid_argument& e)
+ {
+ fail (l) << "invalid version '" << v << "': " << e.what ();
+ }
+
+ if (iv > BUILD2_VERSION)
+ fail (l) << "build2 " << v << " required" <<
+ info << "running build2 " << BUILD2_VERSION_STR;
+ }
+ }
+ else
+ {
+ assert (v.empty ()); // Module versioning not yet implemented.
+
+ if (boot_)
+ boot_module (n, *root_, l);
+ else
+ load_module (optional, n, *root_, *scope_, l);
+ }
+ }
+
+ if (tt == type::newline)
+ next (t, tt);
+ else if (tt != type::eos)
+ fail (t) << "expected newline instead of " << t;
+ }
+
+ static target*
+ derived_factory (const target_type& t, dir_path d, string n, const string* e)
+ {
+ // Pass our type to the base factory so that it can detect that it is
+ // being called to construct a derived target. This can be used, for
+ // example, to decide whether to "link up" to the group.
+ //
+ target* r (t.base->factory (t, move (d), move (n), e));
+ r->derived_type = &t;
+ return r;
+ }
+
+ constexpr const char derived_ext_var[] = "extension";
+
+ void parser::
+ define (token& t, type& tt)
+ {
+ // define <derived>: <base>
+ //
+ // See tests/define.
+ //
+ if (next (t, tt) != type::name)
+ fail (t) << "expected name instead of " << t << " in target type "
+ << "definition";
+
+ string dn (move (t.value));
+ const location dnl (get_location (t, &path_));
+
+ if (next (t, tt) != type::colon)
+ fail (t) << "expected ':' instead of " << t << " in target type "
+ << "definition";
+
+ next (t, tt);
+
+ if (tt == type::name)
+ {
+ // Target.
+ //
+ const string& bn (t.value);
+ const target_type* bt (scope_->find_target_type (bn));
+
+ if (bt == nullptr)
+ fail (t) << "unknown target type " << bn;
+
+ unique_ptr<target_type> dt (new target_type (*bt));
+ dt->base = bt;
+ dt->factory = &derived_factory;
+
+ // Override extension derivation function: we most likely don't want
+ // to use the same default as our base (think cli: file).
+ //
+ dt->extension = &target_extension_var<derived_ext_var, nullptr>;
+
+ target_type& rdt (*dt); // Save a non-const reference to the object.
+
+ auto pr (scope_->target_types.emplace (dn, target_type_ref (move (dt))));
+
+ if (!pr.second)
+ fail (dnl) << "target type " << dn << " already define in this scope";
+
+ // Patch the alias name to use the map's key storage.
+ //
+ rdt.name = pr.first->first.c_str ();
+
+ next (t, tt); // Get newline.
+ }
+ else
+ fail (t) << "expected name instead of " << t << " in target type "
+ << "definition";
+
+ if (tt == type::newline)
+ next (t, tt);
+ else if (tt != type::eos)
+ fail (t) << "expected newline instead of " << t;
+ }
+
+ void parser::
+ if_else (token& t, type& tt)
+ {
+ // Handle the whole if-else chain. See tests/if-else.
+ //
+ bool taken (false); // One of the branches has been taken.
+
+ for (;;)
+ {
+ string k (move (t.value));
+ next (t, tt);
+
+ bool take (false); // Take this branch?
+
+ if (k != "else")
+ {
+ // Should we evaluate the expression if one of the branches has
+ // already been taken? On the one hand, evaluating it is a waste
+ // of time. On the other, it can be invalid and the only way for
+ // the user to know their buildfile is valid is to test every
+ // branch. There could also be side effects. We also have the same
+ // problem with ignored branch blocks except there evaluating it
+ // is not an option. So let's skip it.
+ //
+ if (taken)
+ skip_line (t, tt);
+ else
+ {
+ if (tt == type::newline || tt == type::eos)
+ fail (t) << "expected " << k << "-expression instead of " << t;
+
+ // Parse as names to get variable expansion, evaluation, etc.
+ //
+ const location nsl (get_location (t, &path_));
+ names_type ns (names (t, tt));
+
+ // Should evaluate to true or false.
+ //
+ if (ns.size () != 1 || !value_traits<bool>::assign (ns[0]))
+ fail (nsl) << "expected " << k << "-expression to evaluate to "
+ << "'true' or 'false' instead of '" << ns << "'";
+
+ bool e (ns[0].value == "true");
+ take = (k.back () == '!' ? !e : e);
+ }
+ }
+ else
+ take = !taken;
+
+ if (tt != type::newline)
+ fail (t) << "expected newline instead of " << t << " after " << k
+ << (k != "else" ? "-expression" : "");
+
+ if (next (t, tt) != type::lcbrace)
+ fail (t) << "expected { instead of " << t << " at the beginning of "
+ << k << "-block";
+
+ if (next (t, tt) != type::newline)
+ fail (t) << "expected newline after {";
+
+ next (t, tt);
+
+ if (take)
+ {
+ clause (t, tt);
+ taken = true;
+ }
+ else
+ skip_block (t, tt);
+
+ if (tt != type::rcbrace)
+ fail (t) << "expected } instead of " << t << " at the end of " << k
+ << "-block";
+
+ next (t, tt);
+
+ if (tt == type::newline)
+ next (t, tt);
+ else if (tt != type::eos)
+ fail (t) << "expected newline after }";
+
+ // See if we have another el* keyword.
+ //
+ if (k != "else" && tt == type::name && keyword (t))
+ {
+ const string& n (t.value);
+
+ if (n == "else" || n == "elif" || n == "elif!")
+ continue;
+ }
+
+ break;
+ }
+ }
+
+ void parser::
+ print (token& t, 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.
+ //
+ mode (lexer_mode::value);
+ next (t, tt);
+ names_type ns (tt != type::newline && tt != type::eos
+ ? names (t, tt)
+ : names_type ());
+
+ cout << ns << endl;
+
+ if (tt != type::eos)
+ next (t, tt); // Swallow newline.
+ }
+
+ string parser::
+ variable_name (names_type&& ns, const location& l)
+ {
+ // The list should contain a single, simple name.
+ //
+ if (ns.size () != 1 || !ns[0].simple () || ns[0].empty ())
+ fail (l) << "variable name expected instead of " << ns;
+
+ string& n (ns[0].value);
+
+ if (n.front () == '.') // Fully qualified name.
+ return string (n, 1, string::npos);
+ else
+ //@@ TODO: append namespace if any.
+ return move (n);
+ }
+
+ void parser::
+ variable (token& t, type& tt, string name, type kind)
+ {
+ const auto& var (var_pool.find (move (name)));
+ names_type vns (variable_value (t, tt, var));
+
+ if (kind == type::equal)
+ {
+ value& v (target_ != nullptr
+ ? target_->assign (var)
+ : scope_->assign (var));
+ v.assign (move (vns), var);
+ }
+ else
+ {
+ value& v (target_ != nullptr
+ ? target_->append (var)
+ : scope_->append (var));
+
+ if (kind == type::equal_plus)
+ v.prepend (move (vns), var);
+ else
+ v.append (move (vns), var);
+ }
+ }
+
+ names parser::
+ variable_value (token& t, type& tt, const variable_type& var)
+ {
+ if (var.pairs != '\0')
+ mode (lexer_mode::pairs, var.pairs);
+ else
+ mode (lexer_mode::value);
+
+ next (t, tt);
+ return (tt != type::newline && tt != type::eos
+ ? names (t, tt)
+ : names_type ());
+ }
+
+ parser::names_type parser::
+ eval (token& t, type& tt)
+ {
+ mode (lexer_mode::eval);
+ next (t, tt);
+
+ names_type ns (tt != type::rparen ? names (t, tt) : names_type ());
+
+ if (tt != type::rparen)
+ fail (t) << "expected ')' instead of " << t;
+
+ return ns;
+ }
+
+ // Parse names inside {} and handle the following "crosses" (i.e.,
+ // {a b}{x y}) if any. Return the number of names added to the list.
+ //
+ size_t parser::
+ names_trailer (token& t, type& tt,
+ names_type& ns,
+ size_t pair,
+ const string* pp,
+ const dir_path* dp,
+ const string* tp)
+ {
+ next (t, tt); // Get what's after '{'.
+
+ size_t count (ns.size ());
+ names (t, tt,
+ ns,
+ false,
+ (pair != 0
+ ? pair
+ : (ns.empty () || ns.back ().pair == '\0' ? 0 : ns.size ())),
+ pp, dp, tp);
+ count = ns.size () - count;
+
+ if (tt != type::rcbrace)
+ fail (t) << "expected } instead of " << t;
+
+ // See if we have a cross. See tests/names.
+ //
+ if (peek () == type::lcbrace && !peeked ().separated)
+ {
+ next (t, tt); // Get '{'.
+ const location loc (get_location (t, &path_));
+
+ names_type x; // Parse into a separate list of names.
+ names_trailer (t, tt, x, 0, nullptr, nullptr, nullptr);
+
+ if (size_t n = x.size ())
+ {
+ // Now cross the last 'count' names in 'ns' with 'x'. First we will
+ // allocate n - 1 additional sets of last 'count' names in 'ns'.
+ //
+ size_t b (ns.size () - count); // Start of 'count' names.
+ ns.reserve (ns.size () + count * (n - 1));
+ for (size_t i (0); i != n - 1; ++i)
+ for (size_t j (0); j != count; ++j)
+ ns.push_back (ns[b + j]);
+
+ // Now cross each name, this time including the first set.
+ //
+ for (size_t i (0); i != n; ++i)
+ {
+ for (size_t j (0); j != count; ++j)
+ {
+ name& l (ns[b + i * count + j]);
+ const name& r (x[i]);
+
+ // Move the project names.
+ //
+ if (r.proj != nullptr)
+ {
+ if (l.proj != nullptr)
+ fail (loc) << "nested project name " << *r.proj;
+
+ l.proj = r.proj;
+ }
+
+ // Merge directories.
+ //
+ if (!r.dir.empty ())
+ {
+ if (l.dir.empty ())
+ l.dir = move (r.dir);
+ else
+ l.dir /= r.dir;
+ }
+
+ // Figure out the type. As a first step, "promote" the lhs value
+ // to type.
+ //
+ if (!l.value.empty ())
+ {
+ if (!l.type.empty ())
+ fail (loc) << "nested type name " << l.value;
+
+ l.type.swap (l.value);
+ }
+
+ if (!r.type.empty ())
+ {
+ if (!l.type.empty ())
+ fail (loc) << "nested type name " << r.type;
+
+ l.type = move (r.type);
+ }
+
+ l.value = move (r.value);
+
+ // @@ TODO: need to handle pairs on lhs. I think all that needs
+ // to be done is skip pair's first elements. Maybe also check
+ // that there are no pairs on the rhs. There is just no easy
+ // way to enable the pairs mode to test it, yet.
+ }
+ }
+
+ count *= n;
+ }
+ }
+
+ return count;
+ }
+
+ void parser::
+ names (token& t, type& tt,
+ names_type& ns,
+ bool chunk,
+ size_t pair,
+ const string* pp,
+ const dir_path* dp,
+ const string* tp)
+ {
+ // If pair is not 0, then it is an index + 1 of the first half of
+ // the pair for which we are parsing the second halves, e.g.,
+ // a={b c d{e f} {}}.
+ //
+
+ // Buffer that is used to collect the complete name in case of
+ // an unseparated variable expansion or eval context, e.g.,
+ // 'foo$bar($baz)fox'. The idea is to concatenate all the
+ // individual parts in this buffer and then re-inject it into
+ // the loop as a single token.
+ //
+ string concat;
+
+ // Number of names in the last group. This is used to detect when
+ // we need to add an empty first pair element (e.g., {=y}) or when
+ // we have a for now unsupported multi-name LHS (e.g., {x y}=z).
+ //
+ size_t count (0);
+
+ for (bool first (true);; first = false)
+ {
+ // If the accumulating buffer is not empty, then we have two options:
+ // continue accumulating or inject. We inject if the next token is
+ // not a name, var expansion, or eval context or if it is separated.
+ //
+ if (!concat.empty () &&
+ ((tt != type::name &&
+ tt != type::dollar &&
+ tt != type::lparen) || peeked ().separated))
+ {
+ tt = type::name;
+ t = token (move (concat), true, false, t.line, t.column);
+ concat.clear ();
+ }
+ else if (!first)
+ {
+ // If we are chunking, stop at the next separated token. Unless
+ // current or next token is a pair separator, since we want the
+ // "x = y" pair to be parsed as a single chunk.
+ //
+ bool p (t.type == type::pair_separator); // Current token.
+
+ next (t, tt);
+
+ if (chunk && t.separated && (tt != type::pair_separator && !p))
+ break;
+ }
+
+ // Name.
+ //
+ if (tt == type::name)
+ {
+ string name (t.value); //@@ move?
+ tt = peek ();
+
+ // Should we accumulate? If the buffer is not empty, then
+ // we continue accumulating (the case where we are separated
+ // should have been handled by the injection code above). If
+ // the next token is a var expansion or eval context and it
+ // is not separated, then we need to start accumulating.
+ //
+ if (!concat.empty () || // Continue.
+ ((tt == type::dollar ||
+ tt == type::lparen) && !peeked ().separated)) // Start.
+ {
+ concat += name;
+ continue;
+ }
+
+ string::size_type p (name.find_last_of ("/%"));
+
+ // First take care of project. A project-qualified name is
+ // not very common, so we can afford some copying for the
+ // sake of simplicity.
+ //
+ const string* pp1 (pp);
+
+ if (p != string::npos)
+ {
+ bool last (name[p] == '%');
+ string::size_type p1 (last ? p : name.rfind ('%', p - 1));
+
+ if (p1 != string::npos)
+ {
+ string proj;
+ proj.swap (name);
+
+ // First fix the rest of the name.
+ //
+ name.assign (proj, p1 + 1, string::npos);
+ p = last ? string::npos : p - (p1 + 1);
+
+ // Now process the project name.
+ // @@ Validate it.
+ //
+ proj.resize (p1);
+
+ if (pp != nullptr)
+ fail (t) << "nested project name " << proj;
+
+ pp1 = &project_name_pool.find (proj);
+ }
+ }
+
+ string::size_type n (p != string::npos ? name.size () - 1 : 0);
+
+ // See if this is a type name, directory prefix, or both. That
+ // is, it is followed by an un-separated '{'.
+ //
+ if (tt == type::lcbrace && !peeked ().separated)
+ {
+ next (t, tt);
+
+ if (p != n && tp != nullptr)
+ fail (t) << "nested type name " << name;
+
+ dir_path d1;
+ const dir_path* dp1 (dp);
+
+ string t1;
+ const string* tp1 (tp);
+
+ if (p == string::npos) // type
+ tp1 = &name;
+ else if (p == n) // directory
+ {
+ if (dp == nullptr)
+ d1 = dir_path (name);
+ else
+ d1 = *dp / dir_path (name);
+
+ dp1 = &d1;
+ }
+ else // both
+ {
+ t1.assign (name, p + 1, n - p);
+
+ if (dp == nullptr)
+ d1 = dir_path (name, 0, p + 1);
+ else
+ d1 = *dp / dir_path (name, 0, p + 1);
+
+ dp1 = &d1;
+ tp1 = &t1;
+ }
+
+ count = names_trailer (t, tt, ns, pair, pp1, dp1, tp1);
+ tt = peek ();
+ continue;
+ }
+
+ // If we are a second half of a pair, add another first half
+ // unless this is the first instance.
+ //
+ if (pair != 0 && pair != ns.size ())
+ ns.push_back (ns[pair - 1]);
+
+ count = 1;
+
+ // If it ends with a directory separator, then it is a directory.
+ // Note that at this stage we don't treat '.' and '..' as special
+ // (unless they are specified with a directory separator) because
+ // then we would have ended up treating '.: ...' as a directory
+ // scope. Instead, this is handled higher up the processing chain,
+ // in target_types::find(). This would also mess up reversibility
+ // to simple name.
+ //
+ // @@ TODO: and not quoted
+ //
+ if (p == n)
+ {
+ // For reversibility to simple name, only treat it as a directory
+ // if the string is an exact representation.
+ //
+ if (p != 0 && name[p - 1] != '/') // Take care of the "//" case.
+ name.resize (p); // Strip trailing '/'.
+
+ dir_path dir (move (name), dir_path::exact);
+
+ if (!dir.empty ())
+ {
+ if (dp != nullptr)
+ dir = *dp / dir;
+
+ ns.emplace_back (pp1,
+ move (dir),
+ (tp != nullptr ? *tp : string ()),
+ string ());
+ continue;
+ }
+
+ // Add the trailing slash back and treat it as a simple name.
+ //
+ if (p != 0 && name[p - 1] != '/')
+ name.push_back ('/');
+ }
+
+ ns.emplace_back (pp1,
+ (dp != nullptr ? *dp : dir_path ()),
+ (tp != nullptr ? *tp : string ()),
+ move (name));
+ continue;
+ }
+
+ // Variable expansion/function call or eval context.
+ //
+ if (tt == type::dollar || tt == type::lparen)
+ {
+ // These two cases are pretty similar in that in both we
+ // pretty quickly end up with a list of names that we need
+ // to splice into the result.
+ //
+ names_type lv_data;
+ const names_type* plv;
+
+ location loc;
+ const char* what; // Variable or evaluation context.
+
+ if (tt == type::dollar)
+ {
+ // Switch to the variable name mode. We want to use this
+ // mode for $foo but not for $(foo). Since we don't know
+ // whether the next token is a paren or a name, we turn
+ // it on and switch to the eval mode if what we get next
+ // is a paren.
+ //
+ mode (lexer_mode::variable);
+ next (t, tt);
+ loc = get_location (t, &path_);
+
+ string n;
+ if (tt == type::name)
+ n = t.value;
+ else if (tt == type::lparen)
+ {
+ expire_mode ();
+ names_type ns (eval (t, tt));
+
+ // Make sure the result of evaluation is a single, simple name.
+ //
+ if (ns.size () != 1 || !ns.front ().simple ())
+ fail (loc) << "variable/function name expected instead of '"
+ << ns << "'";
+
+ n = move (ns.front ().value);
+ }
+ else
+ fail (t) << "variable/function name expected instead of " << t;
+
+ if (n.empty ())
+ fail (loc) << "empty variable/function name";
+
+ // Figure out whether this is a variable expansion or a function
+ // call.
+ //
+ tt = peek ();
+
+ if (tt == type::lparen)
+ {
+ next (t, tt); // Get '('.
+ names_type ns (eval (t, tt));
+
+ // Just a stub for now.
+ //
+ cout << n << "(" << ns << ")" << endl;
+
+ tt = peek ();
+
+ if (lv_data.empty ())
+ continue;
+
+ plv = &lv_data;
+ what = "function call";
+ }
+ else
+ {
+ // Process variable name.
+ //
+ if (n.front () == '.') // Fully qualified name.
+ n.erase (0, 1);
+ else
+ {
+ //@@ TODO: append namespace if any.
+ }
+
+ // Lookup.
+ //
+ const auto& var (var_pool.find (move (n)));
+ auto l (target_ != nullptr ? (*target_)[var] : (*scope_)[var]);
+
+ // Undefined/NULL namespace variables are not allowed.
+ //
+ if (!l && var.name.find ('.') != string::npos)
+ fail (loc) << "undefined/null namespace variable " << var.name;
+
+ if (!l || l->empty ())
+ continue;
+
+ plv = &l->data_;
+ what = "variable expansion";
+ }
+ }
+ else
+ {
+ loc = get_location (t, &path_);
+ lv_data = eval (t, tt);
+
+ tt = peek ();
+
+ if (lv_data.empty ())
+ continue;
+
+ plv = &lv_data;
+ what = "context evaluation";
+ }
+
+ // @@ Could move if (lv == &lv_data).
+ //
+ const names_type& lv (*plv);
+
+ // Should we accumulate? If the buffer is not empty, then
+ // we continue accumulating (the case where we are separated
+ // should have been handled by the injection code above). If
+ // the next token is a name or var expansion and it is not
+ // separated, then we need to start accumulating.
+ //
+ if (!concat.empty () || // Continue.
+ ((tt == type::name || // Start.
+ tt == type::dollar ||
+ tt == type::lparen) && !peeked ().separated))
+ {
+ // This should be a simple value or a simple directory. The
+ // token still points to the name (or closing paren).
+ //
+ if (lv.size () > 1)
+ fail (loc) << "concatenating " << what << " contains multiple "
+ << "values";
+
+ const name& n (lv[0]);
+
+ if (n.qualified ())
+ fail (loc) << "concatenating " << what << " contains project name";
+
+ if (n.typed ())
+ fail (loc) << "concatenating " << what << " contains type";
+
+ if (!n.dir.empty ())
+ {
+ if (!n.value.empty ())
+ fail (loc) << "concatenating " << what << " contains directory";
+
+ concat += n.dir.string ();
+ }
+ else
+ concat += n.value;
+ }
+ else
+ {
+ // Copy the names from the variable into the resulting name list
+ // while doing sensible things with the types and directories.
+ //
+ for (const name& n: lv)
+ {
+ const string* pp1 (pp);
+ const dir_path* dp1 (dp);
+ const string* tp1 (tp);
+
+ if (n.proj != 0)
+ {
+ if (pp == nullptr)
+ pp1 = n.proj;
+ else
+ fail (loc) << "nested project name " << *n.proj << " in "
+ << what;
+ }
+
+ dir_path d1;
+ if (!n.dir.empty ())
+ {
+ if (dp != nullptr)
+ {
+ if (n.dir.absolute ())
+ fail (loc) << "nested absolute directory " << n.dir
+ << " in " << what;
+
+ d1 = *dp / n.dir;
+ dp1 = &d1;
+ }
+ else
+ dp1 = &n.dir;
+ }
+
+ if (!n.type.empty ())
+ {
+ if (tp == nullptr)
+ tp1 = &n.type;
+ else
+ fail (loc) << "nested type name " << n.type << " in " << what;
+ }
+
+ // If we are a second half of a pair.
+ //
+ if (pair != 0)
+ {
+ // Check that there are no nested pairs.
+ //
+ if (n.pair != '\0')
+ fail (loc) << "nested pair in " << what;
+
+ // And add another first half unless this is the first instance.
+ //
+ if (pair != ns.size ())
+ ns.push_back (ns[pair - 1]);
+ }
+
+ ns.emplace_back (pp1,
+ (dp1 != nullptr ? *dp1 : dir_path ()),
+ (tp1 != nullptr ? *tp1 : string ()),
+ n.value);
+
+ ns.back ().pair = n.pair;
+ }
+
+ count = lv.size ();
+ }
+
+ continue;
+ }
+
+ // Untyped name group without a directory prefix, e.g., '{foo bar}'.
+ //
+ if (tt == type::lcbrace)
+ {
+ count = names_trailer (t, tt, ns, pair, pp, dp, tp);
+ tt = peek ();
+ continue;
+ }
+
+ // A pair separator (only in the pairs mode).
+ //
+ if (tt == type::pair_separator)
+ {
+ if (pair != 0)
+ fail (t) << "nested pair on the right hand side of a pair";
+
+ if (count > 1)
+ fail (t) << "multiple names on the left hand side of a pair";
+
+ if (count == 0)
+ {
+ // Empty LHS, (e.g., {=y}), create an empty name.
+ //
+ ns.emplace_back (pp,
+ (dp != nullptr ? *dp : dir_path ()),
+ (tp != nullptr ? *tp : string ()),
+ string ());
+ count = 1;
+ }
+
+ ns.back ().pair = t.pair;
+ tt = peek ();
+ continue;
+ }
+
+ if (!first)
+ break;
+
+ if (tt == type::rcbrace) // Empty name, e.g., dir{}.
+ {
+ // If we are a second half of a pair, add another first half
+ // unless this is the first instance.
+ //
+ if (pair != 0 && pair != ns.size ())
+ ns.push_back (ns[pair - 1]);
+
+ ns.emplace_back (pp,
+ (dp != nullptr ? *dp : dir_path ()),
+ (tp != nullptr ? *tp : string ()),
+ string ());
+ break;
+ }
+ else
+ // Our caller expected this to be a name.
+ //
+ fail (t) << "expected name instead of " << t;
+ }
+
+ // Handle the empty RHS in a pair, (e.g., {y=}).
+ //
+ if (!ns.empty () && ns.back ().pair != '\0')
+ {
+ ns.emplace_back (pp,
+ (dp != nullptr ? *dp : dir_path ()),
+ (tp != nullptr ? *tp : string ()),
+ string ());
+ }
+ }
+
+ void parser::
+ skip_line (token& t, type& tt)
+ {
+ for (; tt != type::newline && tt != type::eos; next (t, tt)) ;
+ }
+
+ void parser::
+ skip_block (token& t, type& tt)
+ {
+ // Skip until } or eos, keeping track of the {}-balance.
+ //
+ for (size_t b (0); tt != type::eos; )
+ {
+ if (tt == type::lcbrace || tt == type::rcbrace)
+ {
+ type ptt (peek ());
+ if (ptt == type::newline || ptt == type::eos) // Block { or }.
+ {
+ if (tt == type::lcbrace)
+ ++b;
+ else
+ {
+ if (b == 0)
+ break;
+
+ --b;
+ }
+ }
+ }
+
+ skip_line (t, tt);
+
+ if (tt != type::eos)
+ next (t, tt);
+ }
+ }
+
+ bool parser::
+ keyword (token& t)
+ {
+ assert (replay_ == replay::stop); // Can't be used in a replay.
+ assert (t.type == type::name);
+
+ // The goal here is to allow using keywords as variable names and
+ // target types without imposing ugly restrictions/decorators on
+ // keywords (e.g., '.using' or 'USING'). A name is considered a
+ // potential keyword if:
+ //
+ // - it is not quoted [so a keyword can always be escaped] and
+ // - next token is '\n' (or eos) or '(' [so if(...) will work] or
+ // - next token is separated and is not '=', '=+', or '+=' [which
+ // means a "directive trailer" can never start with one of them].
+ //
+ // See tests/keyword.
+ //
+ if (!t.quoted)
+ {
+ // We cannot peek at the whole token here since it might have to be
+ // lexed in a different mode. So peek at its first character.
+ //
+ pair<char, bool> p (lexer_->peek_char ());
+ char c (p.first);
+
+ return c == '\n' || c == '\0' || c == '(' ||
+ (p.second && c != '=' && c != '+');
+ }
+
+ return false;
+ }
+
+ // Buildspec parsing.
+ //
+
+ // Here is the problem: we "overload" '(' and ')' to mean operation
+ // application rather than the eval context. At the same time we want
+ // to use names() to parse names, get variable expansion/function calls,
+ // quoting, etc. We just need to disable the eval context. The way this
+ // is done has two parts: Firstly, we parse names in chunks and detect
+ // and handle the opening paren. In other words, a buildspec like
+ // 'clean (./)' is "chunked" as 'clean', '(', etc. While this is fairly
+ // straightforward, there is one snag: concatenating eval contexts, as
+ // in 'clean(./)'. Normally, this will be treated as a single chunk and
+ // we don't want that. So here comes the trick (or hack, if you like):
+ // we will make every opening paren token "separated" (i.e., as if it
+ // was proceeded by a space). This will disable concatenating eval. In
+ // fact, we will even go a step further and only do this if we are in
+ // the original pairs mode. This will allow us to still use eval
+ // contexts in buildspec, provided that we quote it: '"cle(an)"'. Note
+ // also that function calls still work as usual: '$filter (clean test)'.
+ // To disable a function call and make it instead a var that is expanded
+ // into operation name(s), we can use quoting: '"$ops"(./)'.
+ //
+ static void
+ paren_processor (token& t, const lexer& l)
+ {
+ if (t.type == type::lparen && l.mode () == lexer_mode::pairs)
+ t.separated = true;
+ }
+
+ buildspec parser::
+ parse_buildspec (istream& is, const std::string& name)
+ {
+ path_ = &name; // Note: caller pools.
+
+ lexer l (is, name, &paren_processor);
+ lexer_ = &l;
+ target_ = nullptr;
+ scope_ = root_ = global_scope;
+
+ // Turn on pairs recognition with '@' as the pair separator (e.g.,
+ // src_root/@out_root/exe{foo bar}).
+ //
+ mode (lexer_mode::pairs, '@');
+
+ token t (type::eos, false, 0, 0);
+ type tt;
+ next (t, tt);
+
+ return buildspec_clause (t, tt, type::eos);
+ }
+
+ static bool
+ opname (const name& n)
+ {
+ // First it has to be a non-empty simple name.
+ //
+ if (n.pair != '\0' || !n.simple () || n.empty ())
+ return false;
+
+ // C identifier.
+ //
+ for (size_t i (0); i != n.value.size (); ++i)
+ {
+ char c (n.value[i]);
+ if (c != '_' && !(i != 0 ? isalnum (c) : isalpha (c)))
+ return false;
+ }
+
+ return true;
+ }
+
+ buildspec parser::
+ buildspec_clause (token& t, type& tt, type tt_end)
+ {
+ buildspec bs;
+
+ while (tt != tt_end)
+ {
+ // We always start with one or more names. Eval context
+ // (lparen) only allowed if quoted.
+ //
+ if (tt != type::name &&
+ tt != type::lcbrace && // Untyped name group: '{foo ...'
+ tt != type::dollar && // Variable expansion: '$foo ...'
+ !(tt == type::lparen && mode () == lexer_mode::quoted) &&
+ tt != type::pair_separator) // Empty pair LHS: '@foo ...'
+ fail (t) << "operation or target expected instead of " << t;
+
+ const location l (get_location (t, &path_)); // Start of names.
+
+ // This call will parse the next chunk of output and produce
+ // zero or more names.
+ //
+ names_type ns (names (t, tt, true));
+
+ // What these names mean depends on what's next. If it is an
+ // opening paren, then they are operation/meta-operation names.
+ // Otherwise they are targets.
+ //
+ if (tt == type::lparen) // Peeked into by names().
+ {
+ if (ns.empty ())
+ fail (t) << "operation name expected before '('";
+
+ for (const name& n: ns)
+ if (!opname (n))
+ fail (l) << "operation name expected instead of '" << n << "'";
+
+ // Inside '(' and ')' we have another, nested, buildspec.
+ //
+ next (t, tt);
+ const location l (get_location (t, &path_)); // Start of nested names.
+ buildspec nbs (buildspec_clause (t, tt, type::rparen));
+
+ // Merge the nested buildspec into ours. But first determine
+ // if we are an operation or meta-operation and do some sanity
+ // checks.
+ //
+ bool meta (false);
+ for (const metaopspec& nms: nbs)
+ {
+ // We definitely shouldn't have any meta-operations.
+ //
+ if (!nms.name.empty ())
+ fail (l) << "nested meta-operation " << nms.name;
+
+ if (!meta)
+ {
+ // If we have any operations in the nested spec, then this
+ // mean that our names are meta-operation names.
+ //
+ for (const opspec& nos: nms)
+ {
+ if (!nos.name.empty ())
+ {
+ meta = true;
+ break;
+ }
+ }
+ }
+ }
+
+ // No nested meta-operations means we should have a single
+ // metaopspec object with empty meta-operation name.
+ //
+ assert (nbs.size () == 1);
+ const metaopspec& nmo (nbs.back ());
+
+ if (meta)
+ {
+ for (name& n: ns)
+ {
+ bs.push_back (nmo);
+ bs.back ().name = move (n.value);
+ }
+ }
+ else
+ {
+ // Since we are not a meta-operation, the nested buildspec
+ // should be just a bunch of targets.
+ //
+ assert (nmo.size () == 1);
+ const opspec& nos (nmo.back ());
+
+ if (bs.empty () || !bs.back ().name.empty ())
+ bs.push_back (metaopspec ()); // Empty (default) meta operation.
+
+ for (name& n: ns)
+ {
+ bs.back ().push_back (nos);
+ bs.back ().back ().name = move (n.value);
+ }
+ }
+
+ next (t, tt); // Done with '('.
+ }
+ else if (!ns.empty ())
+ {
+ // Group all the targets into a single operation. In other
+ // words, 'foo bar' is equivalent to 'update(foo bar)'.
+ //
+ if (bs.empty () || !bs.back ().name.empty ())
+ bs.push_back (metaopspec ()); // Empty (default) meta operation.
+
+ metaopspec& ms (bs.back ());
+
+ for (auto i (ns.begin ()), e (ns.end ()); i != e; ++i)
+ {
+ // @@ We may actually want to support this at some point.
+ //
+ if (i->qualified ())
+ fail (l) << "target name expected instead of " << *i;
+
+ if (opname (*i))
+ ms.push_back (opspec (move (i->value)));
+ else
+ {
+ // Do we have the src_base?
+ //
+ dir_path src_base;
+ if (i->pair != '\0')
+ {
+ if (i->typed ())
+ fail (l) << "expected target src_base instead of " << *i;
+
+ src_base = move (i->dir);
+
+ if (!i->value.empty ())
+ src_base /= dir_path (move (i->value));
+
+ ++i;
+ assert (i != e); // Got to have the second half of the pair.
+ }
+
+ if (ms.empty () || !ms.back ().name.empty ())
+ ms.push_back (opspec ()); // Empty (default) operation.
+
+ opspec& os (ms.back ());
+ os.emplace_back (move (src_base), move (*i));
+ }
+ }
+ }
+ }
+
+ 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_)
+ {
+ level5 ([&]{trace << "switching to root scope " << rs->out_path ();});
+ 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));
+ }
+
+ void parser::
+ process_default_target (token& t)
+ {
+ tracer trace ("parser::process_default_target", &path_);
+
+ // The logic is as follows: if we have an explicit current directory
+ // target, then that's the default target. Otherwise, we take the
+ // first target and use it as a prerequisite to create an implicit
+ // current directory target, effectively making it the default
+ // target via an alias. If there are no targets in this buildfile,
+ // then we don't do anything.
+ //
+ if (default_target_ == nullptr || // No targets in this buildfile.
+ targets.find (dir::static_type, // Explicit current dir target.
+ scope_->out_path (),
+ "",
+ nullptr,
+ trace) != targets.end ())
+ return;
+
+ target& dt (*default_target_);
+
+ level5 ([&]{trace (t) << "creating current directory alias for " << dt;});
+
+ target& ct (
+ targets.insert (
+ dir::static_type, scope_->out_path (), "", nullptr, trace).first);
+
+ prerequisite& p (
+ scope_->prerequisites.insert (
+ nullptr,
+ dt.type (),
+ dt.dir,
+ dt.name,
+ dt.ext,
+ *scope_, // Doesn't matter which scope since dir is absolute.
+ trace).first);
+
+ p.target = &dt;
+ ct.prerequisites.emplace_back (p);
+ }
+
+ void parser::
+ enter_buildfile (const path& p)
+ {
+ tracer trace ("parser::enter_buildfile", &path_);
+
+ const char* e (p.extension ());
+ targets.insert<buildfile> (
+ p.directory (),
+ p.leaf ().base ().string (),
+ &extension_pool.find (e == nullptr ? "" : e), // Always specified.
+ trace);
+ }
+
+ type parser::
+ next (token& t, type& tt)
+ {
+ if (peeked_)
+ {
+ t = move (peek_);
+ peeked_ = false;
+ }
+ else
+ t = (replay_ == replay::play ? replay_next () : lexer_->next ());
+
+ if (replay_ == replay::save)
+ replay_data_.push_back (t);
+
+ tt = t.type;
+ return tt;
+ }
+
+ type parser::
+ peek ()
+ {
+ if (!peeked_)
+ {
+ peek_ = (replay_ == replay::play ? replay_next () : lexer_->next ());
+ peeked_ = true;
+ }
+
+ return peek_.type;
+ }
+
+ static location
+ get_location (const token& t, const void* data)
+ {
+ assert (data != nullptr);
+ const string& p (**static_cast<const string* const*> (data));
+ return location (p.c_str (), t.line, t.column);
+ }
+}
diff --git a/build2/path-io b/build2/path-io
new file mode 100644
index 0000000..c41e1b9
--- /dev/null
+++ b/build2/path-io
@@ -0,0 +1,26 @@
+// file : build2/path-io -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_PATH_IO
+#define BUILD2_PATH_IO
+
+#include <iosfwd>
+
+#include <butl/path>
+
+// Custom path IO.
+//
+namespace build2
+{
+ using butl::path;
+ using butl::dir_path;
+
+ std::ostream&
+ operator<< (std::ostream&, const path&);
+
+ std::ostream&
+ operator<< (std::ostream&, const dir_path&);
+}
+
+#endif // BUILD2_PATH_IO
diff --git a/build2/path-io.cxx b/build2/path-io.cxx
new file mode 100644
index 0000000..0ee50b4
--- /dev/null
+++ b/build2/path-io.cxx
@@ -0,0 +1,39 @@
+// file : build2/path-io.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/path-io>
+
+#include <ostream>
+
+#include <build2/context>
+#include <build2/diagnostics>
+
+using namespace std;
+
+namespace build2
+{
+ ostream&
+ operator<< (ostream& os, const path& p)
+ {
+ return os << (relative (os) ? diag_relative (p) : p.string ());
+ }
+
+ ostream&
+ operator<< (ostream& os, const dir_path& d)
+ {
+ if (relative (os))
+ os << diag_relative (d);
+ else
+ {
+ const string& s (d.string ());
+
+ // Print the directory with trailing '/'.
+ //
+ if (!s.empty ())
+ os << s << (dir_path::traits::is_separator (s.back ()) ? "" : "/");
+ }
+
+ return os;
+ }
+}
diff --git a/build2/prerequisite b/build2/prerequisite
new file mode 100644
index 0000000..0a5f51c
--- /dev/null
+++ b/build2/prerequisite
@@ -0,0 +1,129 @@
+// file : build2/prerequisite -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_PREREQUISITE
+#define BUILD2_PREREQUISITE
+
+#include <set>
+#include <string>
+#include <iosfwd>
+#include <utility> // move
+#include <cassert>
+#include <functional> // reference_wrapper
+
+#include <build2/types>
+#include <build2/target-key>
+#include <build2/utility> // extension_pool
+#include <build2/diagnostics>
+
+namespace build2
+{
+ class scope;
+ class target;
+
+ // Light-weight (by being shallow-pointing) prerequisite key, similar
+ // to (and based on) target key.
+ //
+ class prerequisite_key
+ {
+ public:
+ typedef build2::scope scope_type;
+
+ mutable const std::string* proj; // Can be NULL, from project_name_pool.
+ target_key tk;
+ mutable scope_type* scope; // Can be NULL if tk.dir is absolute.
+ };
+
+ inline bool
+ operator< (const prerequisite_key& x, const prerequisite_key& y)
+ {
+ assert (x.scope == y.scope);
+
+ // Can compare project name pointers since they are from project_name_pool.
+ //
+ return x.proj < y.proj || (x.proj == y.proj && x.tk < y.tk);
+ }
+
+ std::ostream&
+ operator<< (std::ostream&, const prerequisite_key&);
+
+ class prerequisite
+ {
+ public:
+ typedef build2::target target_type;
+ typedef build2::target_type target_type_type;
+ typedef build2::scope scope_type;
+
+ prerequisite (const std::string* p,
+ const target_type_type& t,
+ dir_path d,
+ std::string n,
+ const std::string* e,
+ scope_type& s)
+ : proj (p),
+ type (t),
+ dir (std::move (d)),
+ name (std::move (n)),
+ ext (e),
+ scope (s),
+ target (nullptr) {}
+
+ public:
+ const std::string* proj; // NULL if not project-qualified.
+ const target_type_type& type;
+ const dir_path dir; // Normalized absolute or relative (to scope).
+ const std::string name;
+ const std::string* ext; // NULL if unspecified.
+ scope_type& scope;
+ target_type* target; // NULL if not yet resolved. Note that this should
+ // always be the "primary target", not a member of
+ // a target group.
+ prerequisite_key
+ key () const
+ {
+ return prerequisite_key {proj, {&type, &dir, &name, &ext}, &scope};
+ }
+
+ public:
+ // Prerequisite (target) type.
+ //
+ template <typename T>
+ bool
+ is_a () const {return type.is_a<T> ();}
+ };
+
+ inline bool
+ operator< (const prerequisite& x, const prerequisite& y)
+ {
+ return x.key () < y.key ();
+ }
+
+ inline std::ostream&
+ operator<< (std::ostream& os, const prerequisite& p)
+ {
+ return os << p.key ();
+ }
+
+ // Set of prerequisites in a scope.
+ //
+ struct prerequisite_set: std::set<prerequisite>
+ {
+ std::pair<prerequisite&, bool>
+ insert (const std::string* proj,
+ const target_type&,
+ dir_path dir,
+ std::string name,
+ const std::string* ext,
+ scope&,
+ tracer&);
+
+ std::pair<prerequisite&, bool>
+ insert (const std::string* proj, const target_key& tk, scope& s, tracer& t)
+ {
+ return insert (proj, *tk.type, *tk.dir, *tk.name, *tk.ext, s, t);
+ }
+ };
+}
+
+#endif // BUILD2_PREREQUISITE
diff --git a/build2/prerequisite.cxx b/build2/prerequisite.cxx
new file mode 100644
index 0000000..6a31071
--- /dev/null
+++ b/build2/prerequisite.cxx
@@ -0,0 +1,82 @@
+// file : build2/prerequisite.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/prerequisite>
+
+#include <ostream>
+
+#include <build2/scope>
+#include <build2/target> // target_type
+#include <build2/context>
+#include <build2/diagnostics>
+
+using namespace std;
+
+namespace build2
+{
+ // prerequisite_key
+ //
+ ostream&
+ operator<< (ostream& os, const prerequisite_key& pk)
+ {
+ if (pk.proj != nullptr)
+ os << *pk.proj << '%';
+
+ // Don't print scope if we are project-qualified or the
+ // prerequisite's directory is absolute. In both these
+ // cases the scope is not used to resolve it to target.
+ //
+ else if (!pk.tk.dir->absolute ())
+ {
+ string s (diag_relative (pk.scope->out_path (), false));
+
+ if (!s.empty ())
+ os << s << ':';
+ }
+
+ return os << pk.tk;
+ }
+
+ // prerequisite_set
+ //
+ auto prerequisite_set::
+ insert (const std::string* proj,
+ const target_type& tt,
+ dir_path dir,
+ std::string name,
+ const std::string* ext,
+ scope& s,
+ tracer& trace) -> pair<prerequisite&, bool>
+ {
+ //@@ OPT: would be nice to somehow first check if this prerequisite is
+ // already in the set before allocating a new instance.
+
+ // Find or insert.
+ //
+ auto r (emplace (proj, tt, move (dir), move (name), ext, s));
+ prerequisite& p (const_cast<prerequisite&> (*r.first));
+
+ // Update extension if the existing prerequisite has it unspecified.
+ //
+ if (p.ext != ext)
+ {
+ level5 ([&]{
+ diag_record r (trace);
+ r << "assuming prerequisite " << p << " is the same as the "
+ << "one with ";
+ if (ext == nullptr)
+ r << "unspecified extension";
+ else if (ext->empty ())
+ r << "no extension";
+ else
+ r << "extension " << *ext;
+ });
+
+ if (ext != nullptr)
+ p.ext = ext;
+ }
+
+ return pair<prerequisite&, bool> (p, r.second);
+ }
+}
diff --git a/build2/rule b/build2/rule
new file mode 100644
index 0000000..3d0782f
--- /dev/null
+++ b/build2/rule
@@ -0,0 +1,135 @@
+// file : build2/rule -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_RULE
+#define BUILD2_RULE
+
+#include <string>
+#include <cstddef> // nullptr_t
+
+#include <build2/types>
+#include <build2/target>
+#include <build2/operation>
+
+namespace build2
+{
+ class match_result
+ {
+ public:
+ typedef build2::target target_type;
+ typedef build2::prerequisite prerequisite_type;
+
+ // Can contain neither (both are NULL), one of, or both. If both
+ // are NULL, then it is a "no match" indicator.
+ //
+ // Note that if the "payload" is stored in *value instead of
+ // prerequisite, then target must not be NULL.
+ //
+ union
+ {
+ prerequisite_type* prerequisite;
+
+ bool bvalue;
+ void* pvalue;
+ const void* cpvalue;
+ };
+
+ target_type* target;
+
+ action recipe_action = action (); // Used as recipe's action if set.
+
+ match_result (std::nullptr_t v = nullptr): prerequisite (v), target (v) {}
+ match_result (prerequisite_type& p): prerequisite (&p), target (nullptr) {}
+ match_result (prerequisite_type* p): prerequisite (p), target (nullptr) {}
+ match_result (target_type& t): prerequisite (nullptr), target (&t) {}
+ match_result (target_type* t): prerequisite (nullptr), target (t) {}
+ match_result (const prerequisite_member& pm)
+ : prerequisite (&pm.prerequisite.get ()), target (pm.target) {}
+
+ match_result (target_type& t, bool v): bvalue (v), target (&t) {}
+ match_result (target_type& t, void* v): pvalue (v), target (&t) {}
+ match_result (target_type& t, const void* v): cpvalue (v), target (&t) {}
+ match_result (target_type& t, std::nullptr_t v): pvalue (v), target (&t) {}
+
+ explicit
+ operator bool () const
+ {
+ return target != nullptr || prerequisite != nullptr;
+ }
+ };
+
+ class rule
+ {
+ public:
+ virtual match_result
+ match (action, target&, const std::string& hint) const = 0;
+
+ virtual recipe
+ apply (action, target&, const match_result&) const = 0;
+ };
+
+ // Fallback rule that on update verifies that the file exists and is
+ // not older than any of its prerequisites.
+ //
+ class file_rule: public rule
+ {
+ public:
+ virtual match_result
+ match (action, target&, const std::string& hint) const;
+
+ virtual recipe
+ apply (action, target&, const match_result&) const;
+
+ static target_state
+ perform_update (action, target&);
+
+ static file_rule instance;
+ };
+
+ class alias_rule: public rule
+ {
+ public:
+ virtual match_result
+ match (action, target&, const std::string& hint) const;
+
+ virtual recipe
+ apply (action, target&, const match_result&) const;
+
+ static alias_rule instance;
+ };
+
+ class fsdir_rule: public rule
+ {
+ public:
+ virtual match_result
+ match (action, target&, const std::string& hint) const;
+
+ virtual recipe
+ apply (action, target&, const match_result&) const;
+
+ static target_state
+ perform_update (action, target&);
+
+ static target_state
+ perform_clean (action, target&);
+
+ static fsdir_rule instance;
+ };
+
+ // Fallback rule that always matches and does nothing.
+ //
+ class fallback_rule: public build2::rule
+ {
+ public:
+ virtual match_result
+ match (action, target& t, const std::string&) const {return t;}
+
+ virtual recipe
+ apply (action, target&, const match_result&) const {return noop_recipe;}
+
+ static fallback_rule instance;
+ };
+}
+
+#endif // BUILD2_RULE
diff --git a/build2/rule-map b/build2/rule-map
new file mode 100644
index 0000000..f86ef16
--- /dev/null
+++ b/build2/rule-map
@@ -0,0 +1,115 @@
+// file : build2/rule-map -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_RULE_MAP
+#define BUILD2_RULE_MAP
+
+#include <map>
+#include <vector>
+#include <string>
+#include <memory> // unique_ptr
+#include <functional> // reference_wrapper
+
+#include <butl/prefix-map>
+
+#include <build2/types>
+#include <build2/operation>
+
+namespace build2
+{
+ class rule;
+
+ using target_type_rule_map = std::map<
+ const target_type*,
+ butl::prefix_map<std::string, // Rule hint.
+ std::reference_wrapper<rule>, '.'>>;
+
+ // This is an "indexed map" with operation_id being the index. Entry
+ // with id 0 is a wildcard.
+ //
+ class operation_rule_map
+ {
+ public:
+ template <typename T>
+ void
+ insert (operation_id oid, const char* hint, rule& r)
+ {
+ // 3 is the number of builtin operations.
+ //
+ if (oid >= map_.size ())
+ map_.resize ((oid < 3 ? 3 : oid) + 1);
+
+ map_[oid][&T::static_type].emplace (hint, r);
+ }
+
+ // Return NULL if not found.
+ //
+ const target_type_rule_map*
+ operator[] (operation_id oid) const
+ {
+ return map_.size () > oid ? &map_[oid] : nullptr;
+ }
+
+ bool
+ empty () const {return map_.empty ();}
+
+ private:
+ std::vector<target_type_rule_map> map_;
+ };
+
+ // This is another indexed map but this time meta_operation_id is the
+ // index. The implementation is different, however: here we use a linked
+ // list with the first, statically-allocated node corresponding to the
+ // perform meta-operation. The idea is to try and get away with a dynamic
+ // allocation for the common cases since most rules will be registered
+ // for perform, at least on non-root scopes.
+ //
+ class rule_map
+ {
+ public:
+
+ template <typename T>
+ void
+ insert (action_id a, const char* hint, rule& r)
+ {
+ insert<T> (a >> 4, a & 0x0F, hint, r);
+ }
+
+ template <typename T>
+ void
+ insert (meta_operation_id mid, operation_id oid, const char* hint, rule& r)
+ {
+ if (mid_ == mid)
+ map_.insert<T> (oid, hint, r);
+ else
+ {
+ if (next_ == nullptr)
+ next_.reset (new rule_map (mid));
+
+ next_->insert<T> (mid, oid, hint, r);
+ }
+ }
+
+ // Return NULL if not found.
+ //
+ const operation_rule_map*
+ operator[] (meta_operation_id mid) const
+ {
+ return mid == mid_ ? &map_ : next_ == nullptr ? nullptr : (*next_)[mid];
+ }
+
+ 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_;
+ std::unique_ptr<rule_map> next_;
+ };
+}
+
+#endif // BUILD2_RULE_MAP
diff --git a/build2/rule.cxx b/build2/rule.cxx
new file mode 100644
index 0000000..34fb3b4
--- /dev/null
+++ b/build2/rule.cxx
@@ -0,0 +1,249 @@
+// file : build2/rule.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/rule>
+
+#include <utility> // move()
+#include <system_error>
+
+#include <butl/filesystem>
+
+#include <build2/scope>
+#include <build2/target>
+#include <build2/algorithm>
+#include <build2/diagnostics>
+#include <build2/context>
+
+using namespace std;
+using namespace butl;
+
+namespace build2
+{
+ // file_rule
+ //
+ // Note that this rule is special. It is the last, fallback rule. If
+ // it doesn't match, then no other rule can possibly match and we have
+ // an error. It also cannot be ambigious with any other rule. As a
+ // result the below implementation bends or ignores quite a few rules
+ // that normal implementations should follow. So you probably shouldn't
+ // use it as a guide to implement your own, normal, rules.
+ //
+ match_result file_rule::
+ match (action a, target& t, const string&) const
+ {
+ tracer trace ("file_rule::match");
+
+ // While strictly speaking we should check for the file's existence
+ // for every action (because that's the condition for us matching),
+ // for some actions this is clearly a waste. Say, perform_clean: we
+ // are not doing anything for this action so not checking if the file
+ // exists seems harmless. So the overall guideline seems to be this:
+ // if we don't do anything for the action (other than performing it
+ // on the prerequisites), then we match.
+ //
+ switch (a)
+ {
+ case perform_update_id:
+ {
+ path_target& pt (dynamic_cast<path_target&> (t));
+
+ // Assign the path. While normally we shouldn't do this in match(),
+ // no other rule should ever be ambiguous with the fallback one.
+ //
+ if (pt.path ().empty ())
+ pt.derive_path ();
+
+ // We cannot just call pt.mtime() since we haven't matched yet.
+ //
+ timestamp ts (file_mtime (pt.path ()));
+ pt.mtime (ts);
+
+ if (ts != timestamp_nonexistent)
+ return t;
+
+ level4 ([&]{trace << "no existing file for target " << t;});
+ return nullptr;
+ }
+ default:
+ return t;
+ }
+ }
+
+ recipe file_rule::
+ apply (action a, target& t, const match_result&) const
+ {
+ // Update triggers the update of this target's prerequisites
+ // so it would seem natural that we should also trigger their
+ // cleanup. However, this possibility is rather theoretical
+ // since such an update would render this target out of date
+ // which in turn would lead to an error. So until we see a
+ // real use-case for this functionality, we simply ignore
+ // the clean operation.
+ //
+ if (a.operation () == clean_id)
+ return noop_recipe;
+
+ // If we have no prerequisites, then this means this file
+ // is up to date. Return noop_recipe which will also cause
+ // the target's state to be set to unchanged. This is an
+ // important optimization on which quite a few places that
+ // deal with predominantly static content rely.
+ //
+ if (!t.has_prerequisites ())
+ return noop_recipe;
+
+ // Search and match all the prerequisites.
+ //
+ search_and_match_prerequisites (a, t);
+ return a == perform_update_id ? &perform_update : default_recipe;
+ }
+
+ target_state file_rule::
+ perform_update (action a, target& t)
+ {
+ // Make sure the target is not older than any of its prerequisites.
+ //
+ timestamp mt (dynamic_cast<path_target&> (t).mtime ());
+
+ for (target* pt: t.prerequisite_targets)
+ {
+ target_state ts (execute (a, *pt));
+
+ // If this is an mtime-based target, then compare timestamps.
+ //
+ if (auto mpt = dynamic_cast<const mtime_target*> (pt))
+ {
+ timestamp mp (mpt->mtime ());
+
+ if (mt < mp)
+ fail << "no recipe to " << diag_do (a, t) <<
+ info << "prerequisite " << *pt << " is ahead of " << t
+ << " by " << (mp - mt);
+ }
+ else
+ {
+ // Otherwise we assume the prerequisite is newer if it was changed.
+ //
+ if (ts == target_state::changed)
+ fail << "no recipe to " << diag_do (a, t) <<
+ info << "prerequisite " << *pt << " is ahead of " << t
+ << " because it was updated";
+ }
+ }
+
+ return target_state::unchanged;
+ }
+
+ file_rule file_rule::instance;
+
+ // alias_rule
+ //
+ match_result alias_rule::
+ match (action, target& t, const string&) const
+ {
+ return t;
+ }
+
+ recipe alias_rule::
+ apply (action a, target& t, const match_result&) const
+ {
+ search_and_match_prerequisites (a, t);
+ return default_recipe;
+ }
+
+ alias_rule alias_rule::instance;
+
+ // fsdir_rule
+ //
+ match_result fsdir_rule::
+ match (action, target& t, const string&) const
+ {
+ return t;
+ }
+
+ recipe fsdir_rule::
+ apply (action a, target& t, const match_result&) const
+ {
+ // Inject dependency on the parent directory. Note that we
+ // don't do it for clean since we shouldn't be removing it.
+ //
+ if (a.operation () != clean_id)
+ inject_parent_fsdir (a, t);
+
+ search_and_match_prerequisites (a, t);
+
+ switch (a)
+ {
+ case perform_update_id: return &perform_update;
+ case perform_clean_id: return &perform_clean;
+ default: assert (false); return default_recipe;
+ }
+ }
+
+ target_state fsdir_rule::
+ perform_update (action a, target& t)
+ {
+ target_state ts (target_state::unchanged);
+
+ // First update prerequisites (e.g. create parent directories)
+ // then create this directory.
+ //
+ if (!t.prerequisite_targets.empty ())
+ ts = execute_prerequisites (a, t);
+
+ const dir_path& d (t.dir); // Everything is in t.dir.
+
+ // Generally, it is probably correct to assume that in the majority
+ // of cases the directory will already exist. If so, then we are
+ // going to get better performance by first checking if it indeed
+ // exists. See try_mkdir() for details.
+ //
+ if (!dir_exists (d))
+ {
+ if (verb >= 2)
+ text << "mkdir " << d;
+ else if (verb)
+ text << "mkdir " << t;
+
+ try
+ {
+ try_mkdir (d);
+ }
+ catch (const system_error& e)
+ {
+ fail << "unable to create directory " << d << ": " << e.what ();
+ }
+
+ ts |= target_state::changed;
+ }
+
+ return ts;
+ }
+
+ target_state fsdir_rule::
+ perform_clean (action a, target& t)
+ {
+ // The reverse order of update: first delete this directory,
+ // then clean prerequisites (e.g., delete parent directories).
+ //
+ // Don't fail if we couldn't remove the directory because it
+ // is not empty (or is current working directory). In this
+ // case rmdir() will issue a warning when appropriate.
+ //
+ target_state ts (rmdir (t.dir, t)
+ ? target_state::changed
+ : target_state::unchanged);
+
+ if (!t.prerequisite_targets.empty ())
+ ts |= reverse_execute_prerequisites (a, t);
+
+ return ts;
+ }
+
+ fsdir_rule fsdir_rule::instance;
+
+ // fallback_rule
+ //
+ fallback_rule fallback_rule::instance;
+}
diff --git a/build2/scope b/build2/scope
new file mode 100644
index 0000000..830436c
--- /dev/null
+++ b/build2/scope
@@ -0,0 +1,312 @@
+// file : build2/scope -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_SCOPE
+#define BUILD2_SCOPE
+
+#include <functional> // function
+#include <unordered_set>
+#include <unordered_map>
+
+#include <butl/path-map>
+
+#include <build2/types>
+#include <build2/utility>
+
+#include <build2/module>
+#include <build2/variable>
+#include <build2/prerequisite>
+#include <build2/target-type>
+#include <build2/rule-map>
+#include <build2/operation>
+
+namespace build2
+{
+ class scope
+ {
+ public:
+ // Absolute and normalized.
+ //
+ const dir_path&
+ out_path () const {return *out_path_;}
+
+ const dir_path&
+ src_path () const {return *src_path_;}
+
+ // These are pointers to the keys in scope_map.
+ //
+ const dir_path* out_path_ {nullptr};
+ const dir_path* src_path_ {nullptr};
+
+ bool
+ root () const {return root_ == this;}
+
+ scope*
+ parent_scope () const {return parent_;}
+
+ // Root scope of this scope or NULL if this scope is not (yet)
+ // in any (known) project. Note that if the scope itself is
+ // root, then this function return this. To get to the outer
+ // root, query the root scope of the parent.
+ //
+ scope*
+ root_scope () const {return root_;}
+
+ // Root scope of a strong amalgamation of this scope or NULL if
+ // this scope is not (yet) in any (known) project. If there is
+ // no strong amalgamation, then this function returns the root
+ // scope of the project (in other words, in this case a project
+ // is treated as its own strong amalgamation).
+ //
+ scope*
+ strong_scope () const
+ {
+ return root_ != nullptr
+ ? root_->strong_ != nullptr ? root_->strong_ : root_
+ : nullptr;
+ }
+
+ // Root scope of the outermost amalgamation or NULL if this scope is not
+ // (yet) in any (known) project. If there is no amalgamation, then this
+ // function returns the root scope of the project (in other words, in this
+ // case a project is treated as its own amalgamation).
+ //
+ scope*
+ weak_scope () const
+ {
+ scope* r (root_);
+ if (r != nullptr)
+ for (; r->parent_->root_ != nullptr; r = r->parent_->root_) ;
+ return r;
+ }
+
+ // Variables.
+ //
+ public:
+ variable_map vars;
+
+ // Lookup, including in outer scopes. If you only want to lookup
+ // in this scope, do it on the the variables map directly.
+ //
+ build2::lookup<const value>
+ operator[] (const variable& var) const
+ {
+ return lookup (nullptr, nullptr, var);
+ }
+
+ build2::lookup<const value>
+ operator[] (const std::string& name) const
+ {
+ return operator[] (var_pool.find (name));
+ }
+
+ // As above, but includes target type/pattern-specific variables.
+ //
+ build2::lookup<const value>
+ lookup (const target_key& tk, const variable& var) const
+ {
+ return lookup (tk.type, tk.name, var);
+ }
+
+ build2::lookup<const value>
+ lookup (const target_key& tk, const string& var) const
+ {
+ return lookup (tk, var_pool.find (var));
+ }
+
+ build2::lookup<const value>
+ lookup (const target_type& tt,
+ const string& name,
+ const variable& var) const
+ {
+ return lookup (&tt, &name, var);
+ }
+
+ build2::lookup<const value>
+ lookup (const target_type& tt, const string& name, const string& var) const
+ {
+ return lookup (tt, name, var_pool.find (var));
+ }
+
+ build2::lookup<const value>
+ lookup (const target_type*, const string* name, const variable&) const;
+
+ // Return a value suitable for assignment (or append if you only
+ // want to append to the value from this scope). If the variable
+ // does not exist in this scope's map, then a new one with the
+ // NULL value is added and returned. Otherwise the existing value
+ // is returned.
+ //
+ value&
+ assign (const variable& var) {return vars.assign (var).first.get ();}
+
+ value&
+ assign (const std::string& name) {return vars.assign (name).first.get ();}
+
+ // Return a value suitable for appending. If the variable does not
+ // exist in this scope's map, then outer scopes are searched for
+ // the same variable. If found then a new variable with the found
+ // value is added to this scope and returned. Otherwise this
+ // function proceeds as assign().
+ //
+ value&
+ append (const variable&);
+
+ value&
+ append (const std::string& name)
+ {
+ return append (var_pool.find (name));
+ }
+
+ // Target type/pattern-specific variables.
+ //
+ variable_type_map target_vars;
+
+ // Prerequisite cache.
+ //
+ public:
+ prerequisite_set prerequisites;
+
+ // Meta/operations supported by this project (set on the root
+ // scope only).
+ //
+ build2::meta_operations meta_operations;
+ build2::operations operations;
+
+ typedef build2::path path_type;
+
+ // Set of buildfiles already loaded for this scope. The included
+ // buildfiles are checked against the project's root scope while
+ // imported -- against the global scope (global_scope).
+ //
+ std::unordered_set<path_type> buildfiles;
+
+ // Target types.
+ //
+ public:
+ target_type_map target_types;
+
+ const target_type*
+ find_target_type (const string&, const scope** = nullptr) const;
+
+ // Given a name, figure out its type, taking into account extensions,
+ // special names (e.g., '.' and '..'), or anything else that might be
+ // relevant. Also process the name (in place) by extracting the
+ // extension, adjusting dir/value, etc., (note that the dir is not
+ // necessarily normalized). Return NULL if not found.
+ //
+ const target_type*
+ find_target_type (name&, const string*& ext) const;
+
+ // Rules.
+ //
+ public:
+ rule_map rules;
+
+ // Modules.
+ //
+ 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;
+
+ // These two from <build2/file> set strong_.
+ //
+ friend void create_bootstrap_outer (scope&);
+ friend scope& create_bootstrap_inner (scope&, const dir_path&);
+
+ scope () = default;
+
+ scope* parent_;
+ scope* root_;
+ scope* strong_ = nullptr; // Only set on root sopes.
+ // NULL means no strong amalgamtion.
+ };
+
+ // Temporary scope. The idea is to be able to create a temporary
+ // scope in order not to change the variables in the current scope.
+ // Such a scope is not entered in to the scope map. As a result it
+ // can only be used as a temporary set of variables. In particular,
+ // defining targets/prerequisites directly in such a scope will surely
+ // end up badly. Defining any nested scopes will be as if defining
+ // such a scope in the parent (since path() returns parent's path).
+ //
+ class temp_scope: public scope
+ {
+ public:
+ temp_scope (scope& p)
+ {
+ 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.
+ }
+ };
+
+ 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. If the passed scope pointer is not NULL,
+ // then insert this scope instead of a new one.
+ //
+ iterator
+ insert (const dir_path&, scope*, bool parent, bool root);
+
+ // Find the most qualified scope that encompasses this path.
+ //
+ scope&
+ find (const dir_path&) const;
+
+ scope&
+ 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
+ // directory (i.e., the calling code does not know what it is dealing
+ // with), so let's use the whole path.
+ //
+ 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;
+ extern scope* global_scope;
+}
+
+#endif // BUILD2_SCOPE
diff --git a/build2/scope.cxx b/build2/scope.cxx
new file mode 100644
index 0000000..4d95a95
--- /dev/null
+++ b/build2/scope.cxx
@@ -0,0 +1,317 @@
+// file : build2/scope.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/scope>
+
+#include <build2/target>
+
+using namespace std;
+
+namespace build2
+{
+ // scope
+ //
+ lookup<const value> scope::
+ lookup (const target_type* tt, const string* name, const variable& var) const
+ {
+ using result = build2::lookup<const value>;
+
+ for (const scope* s (this); s != nullptr; )
+ {
+ if (tt != nullptr && !s->target_vars.empty ())
+ {
+ if (auto l = s->target_vars.lookup (*tt, *name, var))
+ return l;
+ }
+
+ if (auto r = s->vars.find (var))
+ return result (r, &s->vars);
+
+ switch (var.visibility)
+ {
+ case variable_visibility::scope:
+ s = nullptr;
+ break;
+ case variable_visibility::project:
+ s = s->root () ? nullptr : s->parent_scope ();
+ break;
+ case variable_visibility::normal:
+ s = s->parent_scope ();
+ break;
+ }
+ }
+
+ return result ();
+ }
+
+ value& scope::
+ append (const variable& var)
+ {
+ auto l (operator[] (var));
+
+ if (l && l.belongs (*this)) // Existing variable in this scope.
+ return const_cast<value&> (*l);
+
+ value& r (assign (var));
+
+ if (l)
+ r = *l; // Copy value from the outer scope.
+
+ return r;
+ }
+
+ const target_type* scope::
+ find_target_type (const string& tt, const scope** rs) const
+ {
+ // Search scopes outwards, stopping at the project root.
+ //
+ for (const scope* s (this);
+ s != nullptr;
+ s = s->root () ? global_scope : s->parent_scope ())
+ {
+ if (s->target_types.empty ())
+ continue;
+
+ auto i (s->target_types.find (tt));
+
+ if (i != s->target_types.end ())
+ {
+ if (rs != nullptr)
+ *rs = s;
+
+ return &i->second.get ();
+ }
+ }
+
+ return nullptr;
+ }
+
+ static const string dir_tt ("dir");
+ static const string file_tt ("file");
+
+ const target_type* scope::
+ find_target_type (name& n, const string*& ext) const
+ {
+ ext = nullptr;
+
+ string& v (n.value);
+
+ // First determine the target type.
+ //
+ const string* tt;
+ if (n.untyped ())
+ {
+ // Empty name or '.' and '..' signify a directory.
+ //
+ if (v.empty () || v == "." || v == "..")
+ tt = &dir_tt;
+ else
+ //@@ TODO: derive type from extension.
+ //
+ tt = &file_tt;
+ }
+ else
+ tt = &n.type;
+
+ const target_type* r (find_target_type (*tt));
+
+ if (r == nullptr)
+ return r;
+
+ // Directories require special name processing. If we find that more
+ // targets deviate, then we should make this target-type-specific.
+ //
+ if (r->is_a<dir> () || r->is_a<fsdir> ())
+ {
+ // The canonical representation of a directory name is with empty
+ // value.
+ //
+ if (!v.empty ())
+ {
+ n.dir /= dir_path (v); // Move name value to dir.
+ v.clear ();
+ }
+ }
+ else
+ {
+ // Split the path into its directory part (if any) the name part,
+ // and the extension (if any). We cannot assume the name part is
+ // a valid filesystem name so we will have to do the splitting
+ // manually.
+ //
+ path::size_type i (path::traits::rfind_separator (v));
+
+ if (i != string::npos)
+ {
+ n.dir /= dir_path (v, i != 0 ? i : 1); // Special case: "/".
+ v = string (v, i + 1, string::npos);
+ }
+
+ // Extract the extension.
+ //
+ string::size_type j (path::traits::find_extension (v));
+
+ if (j != string::npos)
+ {
+ ext = &extension_pool.find (v.c_str () + j + 1);
+ v.resize (j);
+ }
+ }
+
+ return r;
+ }
+
+ // scope_map
+ //
+ scope_map scopes;
+ scope* global_scope;
+
+ auto scope_map::
+ insert (const dir_path& k, scope* ns, bool parent, bool root) -> iterator
+ {
+ 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)
+ {
+ assert (ps->out_path_ == nullptr || ps->src_path_ == nullptr);
+
+ 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.
+ //
+ 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)
+ {
+ 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.
+ //
+ if (map_.size () > 1)
+ {
+ // 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 = &find (k.directory ());
+ }
+
+ s.parent_ = p;
+ s.root_ = root ? &s : (p != nullptr ? p->root_ : nullptr);
+ }
+ else if (root && !s.root ())
+ {
+ // 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;
+ }
+
+ s.root_ = &s;
+ }
+ }
+ else
+ assert (s.parent_ != nullptr);
+
+ return er.first;
+ }
+
+ // Find the most qualified scope that encompasses this path.
+ //
+ scope& scope_map::
+ 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 (map_.find (k)), e (map_.end ());
+
+ if (i != e)
+ return *i->second;
+
+ for (dir_path d (k.directory ());; d = d.directory ())
+ {
+ auto i (map_.find (d));
+
+ 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/build2/search b/build2/search
new file mode 100644
index 0000000..f5db9eb
--- /dev/null
+++ b/build2/search
@@ -0,0 +1,31 @@
+// file : build2/search -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_SEARCH
+#define BUILD2_SEARCH
+
+#include <build2/types>
+
+namespace build2
+{
+ class target;
+ class prerequisite_key;
+
+ // Search for an existing target in this prerequisite's scope.
+ //
+ target*
+ search_existing_target (const prerequisite_key&);
+
+ // Search for an existing file in the specified list of search paths.
+ //
+ target*
+ search_existing_file (const prerequisite_key&, const dir_paths&);
+
+ // Create a new target in this prerequisite's scope.
+ //
+ target&
+ create_new_target (const prerequisite_key&);
+}
+
+#endif // BUILD2_SEARCH
diff --git a/build2/search.cxx b/build2/search.cxx
new file mode 100644
index 0000000..58384f2
--- /dev/null
+++ b/build2/search.cxx
@@ -0,0 +1,171 @@
+// file : build2/search.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/search>
+
+#include <utility> // move()
+#include <cassert>
+
+#include <butl/filesystem>
+
+#include <build2/scope>
+#include <build2/target>
+#include <build2/prerequisite>
+#include <build2/diagnostics>
+
+using namespace std;
+using namespace butl;
+
+namespace build2
+{
+ target*
+ search_existing_target (const prerequisite_key& pk)
+ {
+ tracer trace ("search_existing_target");
+
+ const target_key& tk (pk.tk);
+
+ // Look for an existing target in this directory scope.
+ //
+ dir_path d;
+ if (tk.dir->absolute ())
+ d = *tk.dir; // Already normalized.
+ else
+ {
+ d = pk.scope->out_path ();
+
+ if (!tk.dir->empty ())
+ {
+ d /= *tk.dir;
+ d.normalize ();
+ }
+ }
+
+ auto i (targets.find (*tk.type, d, *tk.name, *tk.ext, trace));
+
+ if (i == targets.end ())
+ return 0;
+
+ target& t (**i);
+
+ level5 ([&]{trace << "existing target " << t << " for prerequisite "
+ << pk;});
+
+ return &t;
+ }
+
+ target*
+ search_existing_file (const prerequisite_key& cpk, const dir_paths& sp)
+ {
+ tracer trace ("search_existing_file");
+
+ prerequisite_key pk (cpk); // Make a copy so we can update extension.
+ target_key& tk (pk.tk);
+ assert (tk.dir->relative ());
+
+ // Figure out the extension. Pretty similar logic to file::derive_path().
+ //
+ const string* ext (*tk.ext);
+
+ if (ext == nullptr)
+ {
+ if (auto f = tk.type->extension)
+ {
+ ext = &f (tk, *pk.scope); // Already from the pool.
+ tk.ext = &ext;
+ }
+ else
+ {
+ // What should we do here, fail or say we didn't find anything?
+ // Current think is that if the target type didn't provide the
+ // default extension, then it doesn't want us to search for an
+ // existing file (of course, if the user specified the extension
+ // explicitly, we will still do so). But let me know what you
+ // think.
+ //
+ //fail << "no default extension for prerequisite " << pk;
+ level4 ([&]{trace << "no existing file found for prerequisite "
+ << pk;});
+ return nullptr;
+ }
+ }
+
+ // Go over paths looking for a file.
+ //
+ for (const dir_path& d: sp)
+ {
+ path f (d / *tk.dir / path (*tk.name));
+ f.normalize ();
+
+ if (!ext->empty ())
+ {
+ f += '.';
+ f += *ext;
+ }
+
+ timestamp mt (file_mtime (f));
+
+ if (mt == timestamp_nonexistent)
+ continue;
+
+ level5 ([&]{trace << "found existing file " << f << " for prerequisite "
+ << pk;});
+
+ // Find or insert. Note: using our updated extension.
+ //
+ auto r (targets.insert (*tk.type, f.directory (), *tk.name, ext, trace));
+
+ // Has to be a file_target.
+ //
+ file& t (dynamic_cast<file&> (r.first));
+
+ level5 ([&]{trace << (r.second ? "new" : "existing") << " target "
+ << t << " for prerequisite " << pk;});
+
+ if (t.path ().empty ())
+ t.path (move (f));
+
+ t.mtime (mt);
+ return &t;
+ }
+
+ level4 ([&]{trace << "no existing file found for prerequisite " << pk;});
+ return nullptr;
+ }
+
+ target&
+ create_new_target (const prerequisite_key& pk)
+ {
+ tracer trace ("create_new_target");
+
+ const target_key& tk (pk.tk);
+
+ // We default to the target in this directory scope.
+ //
+ dir_path d;
+ if (tk.dir->absolute ())
+ d = *tk.dir; // Already normalized.
+ else
+ {
+ d = pk.scope->out_path ();
+
+ if (!tk.dir->empty ())
+ {
+ d /= *tk.dir;
+ d.normalize ();
+ }
+ }
+
+ // Find or insert.
+ //
+ auto r (targets.insert (*tk.type, move (d), *tk.name, *tk.ext, trace));
+ assert (r.second);
+
+ target& t (r.first);
+
+ level5 ([&]{trace << "new target " << t << " for prerequisite " << pk;});
+
+ return t;
+ }
+}
diff --git a/build2/spec b/build2/spec
new file mode 100644
index 0000000..fc981ed
--- /dev/null
+++ b/build2/spec
@@ -0,0 +1,61 @@
+// file : build2/spec -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_SPEC
+#define BUILD2_SPEC
+
+#include <string>
+#include <vector>
+#include <iosfwd>
+#include <utility> // move()
+
+#include <build2/types>
+
+namespace build2
+{
+ struct targetspec
+ {
+ typedef build2::name name_type;
+
+ explicit
+ targetspec (name_type n): name (std::move (n)) {}
+ targetspec (dir_path sb, name_type n)
+ : src_base (std::move (sb)), name (std::move (n)) {}
+
+ dir_path src_base;
+ name_type name;
+ };
+
+ struct opspec: std::vector<targetspec>
+ {
+ opspec () = default;
+ opspec (std::string n): name (std::move (n)) {}
+
+ std::string name;
+ };
+
+ struct metaopspec: std::vector<opspec>
+ {
+ metaopspec () = default;
+ metaopspec (std::string n): name (std::move (n)) {}
+
+ std::string name;
+ };
+
+ typedef std::vector<metaopspec> buildspec;
+
+ std::ostream&
+ operator<< (std::ostream&, const targetspec&);
+
+ std::ostream&
+ operator<< (std::ostream&, const opspec&);
+
+ std::ostream&
+ operator<< (std::ostream&, const metaopspec&);
+
+ std::ostream&
+ operator<< (std::ostream&, const buildspec&);
+}
+
+#endif // BUILD2_SPEC
diff --git a/build2/spec.cxx b/build2/spec.cxx
new file mode 100644
index 0000000..d9a4e12
--- /dev/null
+++ b/build2/spec.cxx
@@ -0,0 +1,81 @@
+// file : build2/spec.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/spec>
+
+#include <ostream>
+
+#include <build2/context>
+#include <build2/diagnostics>
+
+using namespace std;
+
+namespace build2
+{
+ ostream&
+ operator<< (ostream& os, const targetspec& s)
+ {
+ if (!s.src_base.empty ())
+ {
+ string d (diag_relative (s.src_base, false));
+
+ if (!d.empty ())
+ os << d << '@';
+ }
+
+ os << s.name;
+ return os;
+ }
+
+ ostream&
+ operator<< (ostream& os, const opspec& s)
+ {
+ bool hn (!s.name.empty ());
+ bool ht (!s.empty ());
+
+ //os << s.name;
+ os << (hn ? "\"" : "") << s.name << (hn ? "\"" : "");
+
+ if (hn && ht)
+ os << '(';
+
+ for (auto b (s.begin ()), i (b); i != s.end (); ++i)
+ os << (i != b ? " " : "") << *i;
+
+ if (hn && ht)
+ os << ')';
+
+ return os;
+ }
+
+ ostream&
+ operator<< (ostream& os, const metaopspec& s)
+ {
+ bool hn (!s.name.empty ());
+ bool ho (!s.empty ());
+
+ //os << s.name;
+ os << (hn ? "\'" : "") << s.name << (hn ? "\'" : "");
+
+ if (hn && ho)
+ os << '(';
+
+ for (auto b (s.begin ()), i (b); i != s.end (); ++i)
+ os << (i != b ? " " : "") << *i;
+
+ if (hn && ho)
+ os << ')';
+
+ return os;
+ }
+
+ ostream&
+ operator<< (ostream& os, const buildspec& s)
+ {
+ for (auto b (s.begin ()), i (b); i != s.end (); ++i)
+ os << (i != b ? " " : "") << *i;
+
+ return os;
+ }
+}
diff --git a/build2/target b/build2/target
new file mode 100644
index 0000000..19bec9e
--- /dev/null
+++ b/build2/target
@@ -0,0 +1,1084 @@
+// file : build2/target -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_TARGET
+#define BUILD2_TARGET
+
+#include <map>
+#include <string>
+#include <vector>
+#include <memory> // unique_ptr
+#include <cstddef> // size_t
+#include <cstdint> // uint8_t
+#include <functional> // reference_wrapper
+#include <ostream>
+#include <cassert>
+#include <utility> // move(), forward(), declval()
+#include <iterator>
+#include <type_traits>
+
+#include <butl/utility> // reverse_iterate()
+#include <butl/multi-index> // map_iterator_adapter
+
+#include <build2/types>
+#include <build2/utility>
+
+#include <build2/scope>
+#include <build2/variable>
+#include <build2/operation>
+#include <build2/target-type>
+#include <build2/target-key>
+#include <build2/prerequisite>
+
+namespace build2
+{
+ class scope;
+ class target;
+
+ target&
+ search (prerequisite&); // From <build2/algorithm>.
+
+ // Target state.
+ //
+ enum class target_state: std::uint8_t
+ {
+ // The order of the enumerators is arranged so that their integral
+ // values indicate whether one "overrides" the other in the merge
+ // operator (see below).
+ //
+ unknown,
+ unchanged,
+ postponed,
+ changed,
+ failed,
+ group // Target's state is the group's state.
+ };
+
+ std::ostream&
+ operator<< (std::ostream&, target_state);
+
+ inline target_state&
+ operator |= (target_state& l, target_state r)
+ {
+ if (static_cast<std::uint8_t> (r) > static_cast<std::uint8_t> (l))
+ l = r;
+
+ return l;
+ }
+
+ // Recipe.
+ //
+ // The returned target state should be changed, unchanged, or
+ // postponed, though you shouldn't be returning postponed
+ // directly. If there is an error, then the recipe should
+ // throw rather than returning failed.
+ //
+ // The return value of the recipe is used to update the target
+ // state except if the state was manually set by the recipe to
+ // target_state::group. Note that in this case the returned by
+ // the recipe value is still used as the resulting target state
+ // so it should match the group's state.
+ //
+ using recipe_function = target_state (action, target&);
+ using recipe = std::function<recipe_function>;
+
+ // Commonly-used recipes. The default recipe executes the action on
+ // all the prerequisites in a loop, skipping ignored. Specifically,
+ // for actions with the "first" execution mode, it calls
+ // execute_prerequisites() while for those with the "last" mode --
+ // reverse_execute_prerequisites(); see <build2/operation>,
+ // <build2/algorithm> for details. The group recipe call's the group's
+ // recipe.
+ //
+ extern const recipe empty_recipe;
+ extern const recipe noop_recipe;
+ extern const recipe default_recipe;
+ extern const recipe group_recipe;
+
+ target_state
+ noop_action (action, target&); // Defined in <build2/algorithm>.
+
+ target_state
+ group_action (action, target&); // Defined in <build2/algorithm>.
+
+ // Prerequisite references as used in the target::prerequisites list
+ // below.
+ //
+ struct prerequisite_ref: std::reference_wrapper<prerequisite>
+ {
+ typedef std::reference_wrapper<prerequisite> base;
+
+ using base::base;
+
+ // Return true if this reference belongs to the target's prerequisite
+ // list. Note that this test only works if you use references to
+ // the container elements and the container hasn't been resized
+ // since such a reference was obtained. Normally this function is
+ // used when iterating over a combined prerequisites range (see
+ // group_prerequisites below).
+ //
+ bool
+ belongs (const target&) const;
+ };
+
+ // A view of target group members.
+ //
+ struct group_view
+ {
+ target* const* members; // NULL means not yet known.
+ std::size_t count;
+ };
+
+ // Target.
+ //
+ class target
+ {
+ public:
+ typedef build2::action action_type;
+
+ virtual
+ ~target () = default;
+
+ target (const target&) = delete;
+ target& operator= (const target&) = delete;
+
+ target (dir_path d, std::string n, const std::string* e)
+ : dir (std::move (d)), name (std::move (n)), ext (e) {}
+
+ // Reset the target before matching a rule for it. The
+ // default implementation clears prerequisite_targets.
+ //
+ virtual void
+ reset (action_type);
+
+ const dir_path dir; // Absolute and normalized.
+ const std::string name;
+ const std::string* ext; // Extension, NULL means unspecified,
+ // empty means no extension.
+
+ // Target group to which this target belongs, if any. Note that
+ // we assume that the group and all its members are in the same
+ // scope (for example, in variable lookup). We also don't support
+ // nested groups.
+ //
+ // The semantics of the interaction between the group and its
+ // members and what it means to, say, update the group, is
+ // unspecified and determined by the group's type. In particular,
+ // a group can be created out of member types that have no idea
+ // they are part of this group (e.g., cli.cxx{}).
+ //
+ // Normally, however, there are two kinds of groups: "alternatives"
+ // and "combination". In an alternatives group, normally one of the
+ // members is selected when the group is mentioned as a prerequisite
+ // with, perhaps, an exception for special rules, like aliases, where
+ // it makes more sense to treat the group as a whole. In this case we
+ // say that the rule "semantically recognizes" the group and picks
+ // some of its members.
+ //
+ // Updating an alternatives group as a whole can mean updating some
+ // subset of its members (e.g., lib{}). Or the group may not support
+ // this at all (e.g., obj{}).
+ //
+ // In a combination group, when a group is updated, normally all
+ // members are updates (and usually with a single command), though
+ // there could be some members that are omitted, depending on the
+ // configuration (e.g., an inline file not/being generated). When
+ // a combination group is mentioned as a prerequisite, the rule
+ // is usually interested in the individual members rather than
+ // the whole group. For example, a C++ compile rule would like to
+ // "see" the ?xx{} members when it gets a cli.cxx{} group.
+ //
+ // Which brings us to the group iteration mode. The target type
+ // contains a member called see_through that indicates whether the
+ // default iteration mode for the group should be "see through";
+ // that is, whether we see the members or the group itself. For
+ // the iteration support itself, see the *_prerequisite_members()
+ // machinery below.
+ //
+ target* group {nullptr};
+
+ // You should not call this function directly; rather use
+ // resolve_group_members() from <build2/algorithm>.
+ //
+ virtual group_view
+ group_members (action_type) const;
+
+ target_key
+ key () const {return target_key {&type (), &dir, &name, &ext};}
+
+ // Scoping.
+ //
+ public:
+ // Most qualified scope that contains this target.
+ //
+ scope&
+ base_scope () const;
+
+ // Root scope of a project that contains this target. Note that
+ // a target can be out of any (known) project root in which case
+ // this function asserts. If you need to detect this situation,
+ // then use base_scope().root_scope() expression instead.
+ //
+ scope&
+ root_scope () const;
+
+ // Root scope of a strong amalgamation that contains this target.
+ // The same notes as to root_scope() apply.
+ //
+ scope&
+ strong_scope () const {return *root_scope ().strong_scope ();}
+
+ // Root scope of the outermost amalgamation that contains this target.
+ // The same notes as to root_scope() apply.
+ //
+ scope&
+ weak_scope () const {return *root_scope ().weak_scope ();}
+
+
+ bool
+ in (const scope& s) const
+ {
+ return
+ (s.out_path_ != nullptr && dir.sub (*s.out_path_)) ||
+ (s.src_path_ != nullptr && dir.sub (*s.src_path_));
+ }
+
+ // Prerequisites.
+ //
+ public:
+ typedef std::vector<prerequisite_ref> prerequisites_type;
+ prerequisites_type prerequisites;
+
+ // Targets to which prerequisites resolve for this recipe. Note
+ // that unlike prerequisite::target, these can be resolved to
+ // group members. NULL means the target should be skipped (or
+ // the rule may simply not add such a target to the list).
+ //
+ // Note also that it is possible the target can vary from
+ // action to action, just like recipes. We don't need to keep
+ // track of the action here since the targets will be updated
+ // if the recipe is updated, normally as part of rule::apply().
+ //
+ typedef std::vector<target*> prerequisite_targets_type;
+ prerequisite_targets_type prerequisite_targets;
+
+ // Check if there are any prerequisites, taking into account
+ // group prerequisites.
+ //
+ bool
+ has_prerequisites () const
+ {
+ return !prerequisites.empty () ||
+ (group != nullptr && !group->prerequisites.empty ());
+ }
+
+ // Target-specific variables.
+ //
+ public:
+ variable_map vars;
+
+ // Lookup, including in groups to which this target belongs and
+ // then in outer scopes (including target type/pattern-specific
+ // variables). If you only want to lookup in this target, do it
+ // on the variable map directly.
+ //
+ lookup<const value>
+ operator[] (const variable&) const;
+
+ lookup<const value>
+ operator[] (const std::string& name) const
+ {
+ return operator[] (var_pool.find (name));
+ }
+
+ // Return a value suitable for assignment. See class scope for
+ // details.
+ //
+ value&
+ assign (const variable& var) {return vars.assign (var).first;}
+
+ value&
+ assign (const std::string& name) {return vars.assign (name).first;}
+
+ // Return a value suitable for appending. See class scope for
+ // details.
+ //
+ value&
+ append (const variable&);
+
+ value&
+ append (const std::string& name)
+ {
+ return append (var_pool.find (name));
+ }
+
+ public:
+ target_state raw_state;
+
+ target_state
+ state () const
+ {
+ return raw_state != target_state::group ? raw_state : group->raw_state;
+ }
+
+ // Number of direct targets that depend on this target in the current
+ // action. It is incremented during the match phase and then decremented
+ // during execution, before running the recipe. As a result, the recipe
+ // can detect the last chance (i.e., last dependent) to execute the
+ // command (see also the first/last execution modes in <operation>).
+ //
+ // Note that setting a new recipe (which happens when we match the rule
+ // and which in turn is triggered by the first dependent) clears this
+ // counter. However, if the previous action was the same as the current,
+ // then the existing recipe is reused. In this case, however, the counter
+ // should have been decremented to 0 naturally, as part of the previous
+ // action execution.
+ //
+ std::size_t dependents;
+
+ public:
+ action_type action; // Action this recipe is for.
+
+ public:
+ typedef build2::recipe recipe_type;
+
+ const recipe_type&
+ recipe (action_type a) const {return a > action ? empty_recipe : recipe_;}
+
+ void
+ recipe (action_type, recipe_type);
+
+ // Target type info.
+ //
+ public:
+ template <typename T>
+ T*
+ is_a () {return dynamic_cast<T*> (this);}
+
+ template <typename T>
+ const T*
+ is_a () const {return dynamic_cast<const T*> (this);}
+
+ // Dynamic derivation to support define.
+ //
+ const target_type* derived_type = nullptr;
+
+ const target_type&
+ type () const
+ {
+ return derived_type != nullptr ? *derived_type : dynamic_type ();
+ }
+
+ virtual const target_type& dynamic_type () const = 0;
+ static const target_type static_type;
+
+ private:
+ recipe_type recipe_;
+ };
+
+ std::ostream&
+ operator<< (std::ostream&, const target&);
+
+ // A "range" that presents the prerequisites of a group and one of
+ // its members as one continuous sequence, or, in other words, as
+ // if they were in a single container. The group's prerequisites
+ // come first followed by the member's. If you need to see them
+ // in the other direction, iterate in reverse, for example:
+ //
+ // for (prerequisite_ref& pr: group_prerequisites (t))
+ //
+ // for (prerequisite_ref& pr: reverse_iterate (group_prerequisites (t))
+ //
+ // Note that in this case the individual elements of each list will
+ // also be traversed in reverse, but that's what you usually want,
+ // anyway.
+ //
+ class group_prerequisites
+ {
+ public:
+ typedef target::prerequisites_type prerequisites_type;
+
+ explicit
+ group_prerequisites (target& t): t_ (t) {}
+
+ struct iterator
+ {
+ typedef prerequisites_type::iterator base_iterator;
+
+ typedef base_iterator::value_type value_type;
+ typedef base_iterator::pointer pointer;
+ typedef base_iterator::reference reference;
+ typedef base_iterator::difference_type difference_type;
+ typedef std::bidirectional_iterator_tag iterator_category;
+
+ iterator () {}
+ iterator (target* t, prerequisites_type* c, base_iterator i)
+ : t_ (t), c_ (c), i_ (i) {}
+
+ iterator&
+ operator++ ()
+ {
+ if (++i_ == c_->end () && c_ != &t_->prerequisites)
+ {
+ c_ = &t_->prerequisites;
+ i_ = c_->begin ();
+ }
+ return *this;
+ }
+
+ iterator
+ operator++ (int) {iterator r (*this); operator++ (); return r;}
+
+ iterator&
+ operator-- ()
+ {
+ if (i_ == c_->begin () && c_ == &t_->prerequisites)
+ {
+ c_ = &t_->group->prerequisites;
+ i_ = c_->end ();
+ }
+
+ --i_;
+ return *this;
+ }
+
+ iterator
+ operator-- (int) {iterator r (*this); operator-- (); return r;}
+
+ reference operator* () const {return *i_;}
+ pointer operator-> () const {return i_.operator -> ();}
+
+ friend bool
+ operator== (const iterator& x, const iterator& y)
+ {
+ return x.t_ == y.t_ && x.c_ == y.c_ && x.i_ == y.i_;
+ }
+
+ friend bool
+ operator!= (const iterator& x, const iterator& y) {return !(x == y);}
+
+ private:
+ target* t_ {nullptr};
+ prerequisites_type* c_ {nullptr};
+ base_iterator i_;
+ };
+
+ typedef std::reverse_iterator<iterator> reverse_iterator;
+
+ iterator
+ begin () const
+ {
+ auto& c ((t_.group != nullptr && !t_.group->prerequisites.empty ()
+ ? *t_.group : t_).prerequisites);
+ return iterator (&t_, &c, c.begin ());
+ }
+
+ iterator
+ end () const
+ {
+ auto& c (t_.prerequisites);
+ return iterator (&t_, &c, c.end ());
+ }
+
+ reverse_iterator
+ rbegin () const {return reverse_iterator (end ());}
+
+ reverse_iterator
+ rend () const {return reverse_iterator (begin ());}
+
+ std::size_t
+ size () const
+ {
+ return t_.prerequisites.size () +
+ (t_.group != nullptr ? t_.group->prerequisites.size () : 0);
+ }
+
+ private:
+ target& t_;
+ };
+
+ // A member of a prerequisite. If 'target' is NULL, then this is the
+ // prerequisite itself. Otherwise, it is its member. In this case
+ // 'prerequisite' still refers to the prerequisite.
+ //
+ struct prerequisite_member
+ {
+ typedef build2::target target_type;
+ typedef build2::prerequisite prerequisite_type;
+
+ prerequisite_ref& prerequisite;
+ target_type* target;
+
+ template <typename T>
+ bool
+ is_a () const
+ {
+ return target != nullptr
+ ? target->is_a<T> () != nullptr
+ : prerequisite.get ().is_a<T> ();
+ }
+
+ prerequisite_key
+ key () const
+ {
+ return target != nullptr
+ ? prerequisite_key {prerequisite.get ().proj, target->key (), nullptr}
+ : prerequisite.get ().key ();
+ }
+
+ const build2::target_type&
+ type () const
+ {
+ return target != nullptr ? target->type () : prerequisite.get ().type;
+ }
+
+ const std::string&
+ name () const
+ {
+ return target != nullptr ? target->name : prerequisite.get ().name;
+ }
+
+ const std::string*
+ proj () const
+ {
+ // Target cannot be project-qualified.
+ //
+ return target != nullptr ? nullptr : prerequisite.get ().proj;
+ }
+
+ target_type&
+ search () const
+ {
+ return target != nullptr ? *target : build2::search (prerequisite);
+ }
+
+ prerequisite_type&
+ as_prerequisite (tracer&) const;
+ };
+
+ inline std::ostream&
+ operator<< (std::ostream& os, const prerequisite_member& pm)
+ {
+ return os << pm.key ();
+ }
+
+ // A "range" that presents a sequence of prerequisites (e.g., from
+ // group_prerequisites()) as a sequence of prerequisite_member's. For
+ // each group prerequisite you will "see" either the prerequisite
+ // itself or all its members, depending on the default iteration
+ // mode of the target group type. You can skip the rest of the
+ // group members with leave_group() and you can force iteration
+ // over the members with enter_group(). Usage:
+ //
+ // for (prerequisite_member pm: prerequisite_members (a, ...))
+ //
+ // Where ... can be:
+ //
+ // t.prerequisites
+ // reverse_iterate(t.prerequisites)
+ // group_prerequisites (t)
+ // reverse_iterate (group_prerequisites (t))
+ //
+ // But use shortcuts instead:
+ //
+ // prerequisite_members (a, t)
+ // reverse_prerequisite_members (a, t)
+ // group_prerequisite_members (a, t)
+ // reverse_group_prerequisite_members (a, t)
+ //
+ template <typename T>
+ class prerequisite_members_range;
+
+ template <typename T>
+ inline prerequisite_members_range<T>
+ prerequisite_members (action a, T&& x, bool members = true)
+ {
+ return prerequisite_members_range<T> (a, std::forward<T> (x), members);
+ }
+
+ template <typename T>
+ class prerequisite_members_range
+ {
+ public:
+ prerequisite_members_range (action a, T&& r, bool m)
+ : a_ (a), members_ (m), r_ (std::forward<T> (r)), e_ (r_.end ()) {}
+
+ using base_iterator = decltype (std::declval<T> ().begin ());
+
+ struct iterator
+ {
+ typedef prerequisite_member value_type;
+ typedef const value_type* pointer;
+ typedef const value_type& reference;
+ typedef typename base_iterator::difference_type difference_type;
+ typedef std::forward_iterator_tag iterator_category;
+
+ iterator (): r_ (nullptr) {}
+ iterator (const prerequisite_members_range* r, const base_iterator& i)
+ : r_ (r), i_ (i), g_ {nullptr, 0}
+ {
+ if (r_->members_ && i_ != r_->e_ && i_->get ().type.see_through)
+ {
+ bool r (switch_members ());
+ assert (r); // Group could not be resolved.
+ }
+ }
+
+ iterator& operator++ ();
+ iterator operator++ (int) {iterator r (*this); operator++ (); return r;}
+
+ // Skip iterating over the rest of this group's members, if any.
+ // Note that the only valid operation after this call is to
+ // increment the iterator.
+ //
+ void
+ leave_group ()
+ {
+ // Pretend we are on the last member of some group.
+ //
+ j_ = 0;
+ g_.count = 1;
+ }
+
+ // Iterate over this group's members. Return false if the member
+ // information is not available. Similar to leave_group(), you
+ // should increment the iterator after calling this function
+ // (provided it returned true).
+ //
+ bool
+ enter_group ()
+ {
+ bool r (switch_members ());
+ if (r)
+ --j_; // Compensate for the increment that will follow.
+ return r;
+ }
+
+ value_type operator* () const
+ {
+ return value_type {*i_, g_.count != 0 ? g_.members[j_ - 1] : nullptr};
+ }
+
+ pointer operator-> () const
+ {
+ static_assert (
+ std::is_trivially_destructible<prerequisite_member>::value,
+ "prerequisite_member is not trivially destructible");
+
+ return new (&m_)
+ value_type {*i_, g_.count != 0 ? g_.members[j_ - 1] : nullptr};
+ }
+
+ friend bool
+ operator== (const iterator& x, const iterator& y)
+ {
+ return x.i_ == y.i_ &&
+ x.g_.count == y.g_.count &&
+ (x.g_.count == 0 || x.j_ == y.j_);
+ }
+
+ friend bool
+ operator!= (const iterator& x, const iterator& y) {return !(x == y);}
+
+ private:
+ bool
+ switch_members ();
+
+ private:
+ const prerequisite_members_range* r_;
+ base_iterator i_;
+ group_view g_;
+ std::size_t j_; // 1-based index, to support enter_group().
+ mutable std::aligned_storage<sizeof (prerequisite_member),
+ alignof (prerequisite_member)>::type m_;
+ };
+
+ iterator
+ begin () const {return iterator (this, r_.begin ());}
+
+ iterator
+ end () const {return iterator (this, e_);}
+
+ private:
+ action a_;
+ bool members_; // Go into group members by default?
+ T r_;
+ base_iterator e_;
+ };
+
+ // prerequisite_members(t.prerequisites)
+ //
+ inline auto
+ prerequisite_members (action a, target& t, bool members = true)
+ {
+ return prerequisite_members (a, t.prerequisites, members);
+ }
+
+ // prerequisite_members(reverse_iterate(t.prerequisites))
+ //
+ inline auto
+ reverse_prerequisite_members (action a, target& t, bool members = true)
+ {
+ return prerequisite_members (
+ a, butl::reverse_iterate (t.prerequisites), members);
+ }
+
+ // prerequisite_members(group_prerequisites (t))
+ //
+ inline auto
+ group_prerequisite_members (action a, target& t, bool members = true)
+ {
+ return prerequisite_members (a, group_prerequisites (t), members);
+ }
+
+ // prerequisite_members(reverse_iterate (group_prerequisites (t)))
+ //
+ inline auto
+ reverse_group_prerequisite_members (action a, target& t, bool members = true)
+ {
+ return prerequisite_members (
+ a, butl::reverse_iterate (group_prerequisites (t)), members);
+ }
+
+ //
+ //
+ struct target_set
+ {
+ typedef std::map<target_key, std::unique_ptr<target>> map;
+ typedef butl::map_iterator_adapter<map::const_iterator> iterator;
+
+ iterator
+ find (const target_key& k, tracer& trace) const;
+
+ iterator
+ find (const target_type& type,
+ const dir_path& dir,
+ const std::string& name,
+ const std::string* ext,
+ tracer& trace) const
+ {
+ return find (target_key {&type, &dir, &name, &ext}, trace);
+ }
+
+ // As above but ignore the extension and return the target or
+ // nullptr instead of the iterator.
+ //
+ template <typename T>
+ T*
+ find (const dir_path& dir, const std::string& name) const
+ {
+ const std::string* e (nullptr);
+ auto i (map_.find (target_key {&T::static_type, &dir, &name, &e}));
+ return i != map_.end () ? static_cast<T*> (i->second.get ()) : nullptr;
+ }
+
+ iterator begin () const {return map_.begin ();}
+ iterator end () const {return map_.end ();}
+
+ std::pair<target&, bool>
+ insert (const target_type&,
+ dir_path dir,
+ std::string name,
+ const std::string* ext,
+ tracer&);
+
+ template <typename T>
+ T&
+ insert (const dir_path& dir,
+ const std::string& name,
+ const std::string* ext,
+ tracer& t)
+ {
+ return static_cast<T&> (
+ insert (T::static_type, dir, name, ext, t).first);
+ }
+
+ template <typename T>
+ T&
+ insert (const dir_path& dir, const std::string& name, tracer& t)
+ {
+ return static_cast<T&> (
+ insert (T::static_type, dir, name, nullptr, t).first);
+ }
+
+ void
+ clear () {map_.clear ();}
+
+ private:
+ map map_;
+ };
+
+ extern target_set targets;
+
+ // Modification time-based target.
+ //
+ class mtime_target: public target
+ {
+ public:
+ using target::target;
+
+ // Generally, modification time for a target can only be queried
+ // after a rule has been matched since that's where the path is
+ // normally gets assigned. Normally, however, it would make sense
+ // to first execute the rule to get the "updated" timestamp.
+ //
+ // The rule for groups that utilize the group state is as follows:
+ // if it has any members that are mtime_targets, then the group
+ // should be mtime_target and the members get the mtime from it.
+ //
+ timestamp
+ mtime () const
+ {
+ const mtime_target* t (raw_state == target_state::group
+ ? static_cast<const mtime_target*> (group)
+ : this);
+
+ if (t->mtime_ == timestamp_unknown)
+ t->mtime_ = t->load_mtime ();
+
+ return t->mtime_;
+ }
+
+ void
+ mtime (timestamp mt)
+ {
+ // While we can cache the mtime at any time, it may be ignored
+ // if the target state is group (see the mtime() accessor).
+ //
+ mtime_ = mt;
+ }
+
+ protected:
+ virtual timestamp
+ load_mtime () const = 0;
+
+ public:
+ static const target_type static_type;
+
+ private:
+ mutable timestamp mtime_ {timestamp_unknown};
+ };
+
+ // Filesystem path-based target.
+ //
+ class path_target: public mtime_target
+ {
+ public:
+ using mtime_target::mtime_target;
+
+ typedef build2::path path_type;
+
+ const path_type&
+ path () const {return path_;}
+
+ void
+ path (path_type p) {assert (path_.empty ()); path_ = std::move (p);}
+
+ // Derive a path from target's dir, name, and, if specified, ext.
+ // If ext is not specified, then use default_ext and also update
+ // the target's extension (this becomes important if later we need
+ // to reliably determine whether this file has an extension; think
+ // hxx{foo.bar.} and hxx.ext is empty).
+ //
+ // If name_prefix is not NULL, add it before the name part and after
+ // the directory. Similarly, if name_suffix is not NULL, add it after
+ // the name part and before the extension.
+ //
+ // Finally, if the path was already assigned to this target, then
+ // this function verifies that the two are the same.
+ //
+ void
+ derive_path (const char* default_ext = nullptr,
+ const char* name_prefix = nullptr,
+ const char* name_suffix = nullptr);
+
+ public:
+ static const target_type static_type;
+
+ private:
+ path_type path_;
+ };
+
+ // File target.
+ //
+ class file: public path_target
+ {
+ public:
+ using path_target::path_target;
+
+ protected:
+ // Note that it is final in order to be consistent with file_rule,
+ // search_existing_file().
+ //
+ virtual timestamp
+ load_mtime () const final;
+
+ public:
+ static const target_type static_type;
+ virtual const target_type& dynamic_type () const {return static_type;}
+ };
+
+ // Alias target. It represents a list of targets (its prerequisites)
+ // as a single "name".
+ //
+ class alias: public target
+ {
+ public:
+ using target::target;
+
+ public:
+ static const target_type static_type;
+ virtual const target_type& dynamic_type () const {return static_type;}
+ };
+
+ // Directory target. Note that this is not a filesystem directory
+ // but rather an alias target with the directory name. For actual
+ // filesystem directory (creation), see fsdir.
+ //
+ class dir: public alias
+ {
+ public:
+ using alias::alias;
+
+ public:
+ static const target_type static_type;
+ virtual const target_type& dynamic_type () const {return static_type;}
+ };
+
+ // While a filesystem directory is mtime-based, the semantics is
+ // not very useful in our case. In particular, if another target
+ // depends on fsdir{}, then all that's desired is the creation of
+ // the directory if it doesn't already exist. In particular, we
+ // don't want to update the target just because some unrelated
+ // entry was created in that directory.
+ //
+ class fsdir: public target
+ {
+ public:
+ using target::target;
+
+ public:
+ static const target_type static_type;
+ virtual const target_type& dynamic_type () const {return static_type;}
+ };
+
+ class buildfile: public file
+ {
+ public:
+ using file::file;
+
+ public:
+ static const target_type static_type;
+ virtual const target_type& dynamic_type () const {return static_type;}
+ };
+
+ // Common documentation file targets.
+ //
+ // @@ Maybe these should be in the built-in doc module?
+ //
+ class doc: public file
+ {
+ public:
+ using file::file;
+
+ public:
+ static const target_type static_type;
+ virtual const target_type& dynamic_type () const {return static_type;}
+ };
+
+ // The problem with man pages is this: different platforms have
+ // different sets of sections. What seems to be the "sane" set
+ // is 1-9 (Linux and BSDs). SysV (e.g., Solaris) instead maps
+ // 8 to 1M (system administration). The section determines two
+ // things: the directory where the page is installed (e.g.,
+ // /usr/share/man/man1) as well as the extension of the file
+ // (e.g., test.1). Note also that there could be sub-sections,
+ // e.g., 1p (for POSIX). Such a page would still go into man1
+ // but will have the .1p extension (at least that's what happens
+ // on Linux). The challenge is to somehow handle this in a
+ // portable manner. So here is the plan:
+ //
+ // First of all, we have the man{} target type which can be used
+ // for a custom man page. That is, you can have any extension and
+ // install it anywhere you please:
+ //
+ // man{foo.X}: install = man/manX
+ //
+ // Then we have man1..9{} target types which model the "sane"
+ // section set and that would be automatically installed into
+ // correct locations on other platforms. In other words, the
+ // idea is that you should be able to have the foo.8 file,
+ // write man8{foo} and have it installed as man1m/foo.1m on
+ // some SysV host.
+ //
+ // Re-mapping the installation directory is easy: to help with
+ // that we have assigned install.man1..9 directory names. The
+ // messy part is to change the extension. It seems the only
+ // way to do that would be to have special logic for man pages
+ // in the generic install rule. @@ This is still a TODO.
+ //
+ // Note that handling subsections with man1..9{} is easy, we
+ // simply specify the extension explicitly, e.g., man{foo.1p}.
+ //
+ class man: public doc
+ {
+ public:
+ using doc::doc;
+
+ public:
+ static const target_type static_type;
+ virtual const target_type& dynamic_type () const {return static_type;}
+ };
+
+ class man1: public man
+ {
+ public:
+ using man::man;
+
+ public:
+ static const target_type static_type;
+ virtual const target_type& dynamic_type () const {return static_type;}
+ };
+
+ // Common implementation of the target factory, extension, and
+ // search functions.
+ //
+ template <typename T>
+ target*
+ target_factory (const target_type&, dir_path d, string n, const string* e)
+ {
+ return new T (move (d), move (n), e);
+ }
+
+ // Return fixed target extension.
+ //
+ template <const char* ext>
+ const std::string&
+ target_extension_fix (const target_key&, scope&);
+
+ // Get the extension from the variable or use the default if none set.
+ // Issue diagnostics and fail if the default is NULL.
+ //
+ template <const char* var, const char* def>
+ const std::string&
+ target_extension_var (const target_key&, scope&);
+
+ // The default behavior, that is, look for an existing target in the
+ // prerequisite's directory scope.
+ //
+ target*
+ search_target (const prerequisite_key&);
+
+ // First look for an existing target as above. If not found, then look
+ // for an existing file in the target-type-specific list of paths.
+ //
+ target*
+ search_file (const prerequisite_key&);
+
+}
+
+#include <build2/target.ixx>
+#include <build2/target.txx>
+
+#endif // BUILD2_TARGET
diff --git a/build2/target-key b/build2/target-key
new file mode 100644
index 0000000..cd8e270
--- /dev/null
+++ b/build2/target-key
@@ -0,0 +1,55 @@
+// file : build2/target-key -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_TARGET_KEY
+#define BUILD2_TARGET_KEY
+
+#include <map>
+#include <string>
+#include <ostream>
+#include <functional> // reference_wrapper
+
+#include <butl/utility> // compare_c_string
+
+#include <build2/types>
+#include <build2/target-type>
+
+namespace build2
+{
+ // Light-weight (by being shallow-pointing) target key.
+ //
+ class target_key
+ {
+ public:
+ mutable const target_type* type;
+ mutable const dir_path* dir;
+ mutable const std::string* name;
+ mutable const std::string* const* ext; // Note: only *ext can be NULL.
+
+ friend bool
+ operator< (const target_key& x, const target_key& y)
+ {
+ const target_type* xt (x.type);
+ const target_type* yt (y.type);
+
+ //@@ TODO: use compare() to compare once.
+
+ // Unspecified and specified extension are assumed equal. The
+ // extension strings are from the pool, so we can just compare
+ // pointers.
+ //
+ return
+ (xt < yt) ||
+ (xt == yt && *x.name < *y.name) ||
+ (xt == yt && *x.name == *y.name && *x.dir < *y.dir) ||
+ (xt == yt && *x.name == *y.name && *x.dir == *y.dir &&
+ *x.ext != nullptr && *y.ext != nullptr && **x.ext < **y.ext);
+ }
+ };
+
+ std::ostream&
+ operator<< (std::ostream&, const target_key&); // Defined in target.cxx
+}
+
+#endif // BUILD2_TARGET_KEY
diff --git a/build2/target-type b/build2/target-type
new file mode 100644
index 0000000..52a45f4
--- /dev/null
+++ b/build2/target-type
@@ -0,0 +1,97 @@
+// file : build2/target-type -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_TARGET_TYPE
+#define BUILD2_TARGET_TYPE
+
+#include <map>
+#include <string>
+#include <ostream>
+
+#include <build2/types>
+
+namespace build2
+{
+ class scope;
+ class target;
+ class target_key;
+ class prerequisite_key;
+
+ // Target type.
+ //
+ // Note that we assume there is always a single instance of this class
+ // for any target type. As a result, we can use address comparison to
+ // determine if two target types are the same.
+ //
+ //
+ struct target_type
+ {
+ const char* name;
+ const target_type* base;
+ target* (*factory) (const target_type&, dir_path, string, const string*);
+ const string& (*extension) (const target_key&, scope&);
+ target* (*search) (const prerequisite_key&);
+ bool see_through; // A group with the default "see through" semantics.
+
+ bool
+ is_a (const target_type&) const; // Defined in target.cxx
+
+ template <typename T>
+ bool
+ is_a () const {return is_a (T::static_type);}
+ };
+
+ inline bool
+ operator< (const target_type& x, const target_type& y) {return &x < &y;}
+
+ inline bool
+ operator== (const target_type& x, const target_type& y) {return &x == &y;}
+
+ inline bool
+ operator!= (const target_type& x, const target_type& y) {return &x != &y;}
+
+ inline std::ostream&
+ operator<< (std::ostream& os, const target_type& tt) {return os << tt.name;}
+
+ // Target type map.
+ //
+ struct target_type_ref
+ {
+ // Like reference_wrapper except it sometimes deletes the target type.
+ //
+ explicit
+ target_type_ref (const target_type& r): p_ (&r), d_ (false) {}
+
+ explicit
+ target_type_ref (unique_ptr<target_type>&& p)
+ : p_ (p.release ()), d_ (true) {}
+
+ target_type_ref (target_type_ref&& r)
+ : p_ (r.p_), d_ (r.d_) {r.p_ = nullptr;}
+
+ ~target_type_ref () {if (p_ != nullptr && d_) delete p_;}
+
+ explicit operator const target_type& () const {return *p_;}
+ const target_type& get () const {return *p_;}
+
+ private:
+ const target_type* p_;
+ bool d_;
+ };
+
+ using target_type_map_base = std::map<string, target_type_ref>;
+
+ class target_type_map: public target_type_map_base
+ {
+ public:
+ void
+ insert (const target_type& tt) {emplace (tt.name, target_type_ref (tt));}
+
+ template <typename T>
+ void
+ insert () {insert (T::static_type);}
+ };
+}
+
+#endif // BUILD2_TARGET_TYPE
diff --git a/build2/target.cxx b/build2/target.cxx
new file mode 100644
index 0000000..3932466
--- /dev/null
+++ b/build2/target.cxx
@@ -0,0 +1,537 @@
+// file : build2/target.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/target>
+
+#include <cassert>
+
+#include <butl/filesystem>
+
+#include <build2/scope>
+#include <build2/search>
+#include <build2/algorithm>
+#include <build2/diagnostics>
+
+using namespace std;
+
+namespace build2
+{
+ // target_type
+ //
+ bool target_type::
+ is_a (const target_type& tt) const
+ {
+ for (const target_type* p (this); p != nullptr; p = p->base)
+ if (*p == tt)
+ return true;
+
+ return false;
+ }
+
+ // target_state
+ //
+ static const char* target_state_[] = {
+ "unknown", "unchanged", "postponed", "changed", "failed", "group"};
+
+ ostream&
+ operator<< (ostream& os, target_state ts)
+ {
+ return os << target_state_[static_cast<uint8_t> (ts)];
+ }
+
+ // recipe
+ //
+ const recipe empty_recipe;
+ const recipe noop_recipe (&noop_action);
+ const recipe default_recipe (&default_action);
+ const recipe group_recipe (&group_action);
+
+ // target
+ //
+
+ void target::
+ recipe (action_type a, recipe_type r)
+ {
+ assert (a > action || !recipe_);
+
+ bool override (a == action && recipe_); // See action::operator<.
+
+ // Only noop_recipe can be overridden.
+ //
+ if (override)
+ {
+ recipe_function** f (recipe_.target<recipe_function*> ());
+ assert (f != nullptr && *f == &noop_action);
+ }
+
+ action = a;
+ recipe_ = std::move (r);
+
+ // Also reset the target state. If this is a noop recipe, then
+ // mark the target unchanged so that we don't waste time executing
+ // the recipe.
+ //
+ raw_state = target_state::unknown;
+
+ if (recipe_function** f = recipe_.target<recipe_function*> ())
+ {
+ if (*f == &noop_action)
+ raw_state = target_state::unchanged;
+ }
+
+ // This one is tricky: we don't want to reset the dependents count
+ // if we are merely overriding with a "stronger" recipe.
+ //
+ if (!override)
+ dependents = 0;
+ }
+
+ void target::
+ reset (action_type)
+ {
+ prerequisite_targets.clear ();
+ }
+
+ group_view target::
+ group_members (action_type) const
+ {
+ assert (false); // Not a group or doesn't expose its members.
+ return group_view {nullptr, 0};
+ }
+
+ scope& target::
+ base_scope () const
+ {
+ return scopes.find (dir);
+ }
+
+ scope& target::
+ root_scope () const
+ {
+ // This is tricky to cache so we do the lookup for now.
+ //
+ scope* r (scopes.find (dir).root_scope ());
+ assert (r != nullptr);
+ return *r;
+ }
+
+ lookup<const value> target::
+ operator[] (const variable& var) const
+ {
+ using result = lookup<const value>;
+
+ if (auto p = vars.find (var))
+ return result (p, &vars);
+
+ if (group != nullptr)
+ {
+ if (auto p = group->vars.find (var))
+ return result (p, &group->vars);
+ }
+
+ // We cannot simply delegate to scope's lookup() since we also need
+ // to check the group.
+ //
+ for (const scope* s (&base_scope ()); s != nullptr; )
+ {
+ if (!s->target_vars.empty ())
+ {
+ if (auto l = s->target_vars.lookup (type (), name, var))
+ return l;
+
+ if (group != nullptr)
+ {
+ if (auto l = s->target_vars.lookup (group->type (), group->name, var))
+ return l;
+ }
+ }
+
+ if (auto r = s->vars.find (var))
+ return result (r, &s->vars);
+
+ switch (var.visibility)
+ {
+ case variable_visibility::scope:
+ s = nullptr;
+ break;
+ case variable_visibility::project:
+ s = s->root () ? nullptr : s->parent_scope ();
+ break;
+ case variable_visibility::normal:
+ s = s->parent_scope ();
+ break;
+ }
+ }
+
+ return result ();
+ }
+
+ value& target::
+ append (const variable& var)
+ {
+ auto l (operator[] (var));
+
+ if (l && l.belongs (*this)) // Existing variable in this target.
+ return const_cast<value&> (*l);
+
+ value& r (assign (var));
+
+ if (l)
+ r = *l; // Copy value from the outer scope.
+
+ return r;
+ }
+
+ ostream&
+ operator<< (ostream& os, const target& t)
+ {
+ return os << target_key {&t.type (), &t.dir, &t.name, &t.ext};
+ }
+
+ // target_set
+ //
+ target_set targets;
+
+ auto target_set::
+ find (const target_key& k, tracer& trace) const -> iterator
+ {
+ iterator i (map_.find (k));
+
+ if (i != end ())
+ {
+ target& t (**i);
+
+ // Update the extension if the existing target has it unspecified.
+ //
+ const string* ext (*k.ext);
+ if (t.ext != ext)
+ {
+ level5 ([&]{
+ diag_record r (trace);
+ r << "assuming target " << t << " is the same as the one with ";
+ if (ext == nullptr)
+ r << "unspecified extension";
+ else if (ext->empty ())
+ r << "no extension";
+ else
+ r << "extension " << *ext;
+ });
+
+ if (ext != nullptr)
+ t.ext = ext;
+ }
+ }
+
+ return i;
+ }
+
+ pair<target&, bool> target_set::
+ insert (const target_type& tt,
+ dir_path dir,
+ string name,
+ const string* ext,
+ tracer& trace)
+ {
+ iterator i (find (target_key {&tt, &dir, &name, &ext}, trace));
+ bool r (i == end ());
+
+ if (r)
+ {
+ unique_ptr<target> pt (tt.factory (tt, move (dir), move (name), ext));
+ i = map_.emplace (
+ make_pair (target_key {&tt, &pt->dir, &pt->name, &pt->ext},
+ move (pt))).first;
+ }
+
+ return pair<target&, bool> (**i, r);
+ }
+
+ ostream&
+ operator<< (ostream& os, const target_key& k)
+ {
+ // If the name is empty, then we want to print the directory
+ // inside {}, e.g., dir{bar/}, not bar/dir{}.
+ //
+ bool n (!k.name->empty ());
+ string d (diag_relative (*k.dir, false));
+
+ if (n)
+ os << d;
+
+ os << k.type->name << '{';
+
+ if (n)
+ {
+ os << *k.name;
+
+ if (*k.ext != nullptr && !(*k.ext)->empty ())
+ os << '.' << **k.ext;
+ }
+ else
+ os << d;
+
+ os << '}';
+
+ return os;
+ }
+
+ // path_target
+ //
+ void path_target::
+ derive_path (const char* de, const char* np, const char* ns)
+ {
+ string n;
+
+ if (np != nullptr)
+ n += np;
+
+ n += name;
+
+ if (ns != nullptr)
+ n += ns;
+
+ // Update the extension.
+ //
+ // See also search_existing_file() if updating anything here.
+ //
+ if (ext == nullptr)
+ {
+ // If provided by the caller, then use that.
+ //
+ if (de != nullptr)
+ ext = &extension_pool.find (de);
+ //
+ // Otherwis see if the target type has function that will
+ // give us the default extension.
+ //
+ else if (auto f = type ().extension)
+ ext = &f (key (), base_scope ()); // Already from the pool.
+ else
+ fail << "no default extension for target " << *this;
+ }
+
+ // Add the extension.
+ //
+ if (!ext->empty ())
+ {
+ n += '.';
+ n += *ext;
+ }
+
+ path_type p (dir / path_type (move (n)));
+ const path_type& ep (path ());
+
+ if (ep.empty ())
+ path (p);
+ else if (p != ep)
+ fail << "path mismatch for target " << *this <<
+ info << "assigned '" << ep << "'" <<
+ info << "derived '" << p << "'";
+ }
+
+ // file_target
+ //
+ timestamp file::
+ load_mtime () const
+ {
+ const path_type& f (path ());
+ assert (!f.empty ());
+ return file_mtime (f);
+ }
+
+ // Search functions.
+ //
+
+ target*
+ search_target (const prerequisite_key& pk)
+ {
+ // The default behavior is to look for an existing target in the
+ // prerequisite's directory scope.
+ //
+ return search_existing_target (pk);
+ }
+
+ target*
+ search_file (const prerequisite_key& pk)
+ {
+ // First see if there is an existing target.
+ //
+ if (target* t = search_existing_target (pk))
+ return t;
+
+ // Then look for an existing file in this target-type-specific
+ // list of paths (@@ TODO: comes from the variable).
+ //
+ if (pk.tk.dir->relative ())
+ {
+ dir_paths sp;
+ sp.push_back (pk.scope->src_path ()); // src_base
+ return search_existing_file (pk, sp);
+ }
+ else
+ return nullptr;
+ }
+
+ static target*
+ search_alias (const prerequisite_key& pk)
+ {
+ // For an alias we don't want to silently create a target since it
+ // will do nothing and it most likely not what the user intended.
+ //
+ target* t (search_existing_target (pk));
+
+ if (t == nullptr)
+ fail << "no explicit target for prerequisite " << pk;
+
+ return t;
+ }
+
+ // type info
+ //
+
+ const target_type target::static_type
+ {
+ "target",
+ nullptr,
+ nullptr,
+ nullptr,
+ &search_target,
+ false
+ };
+
+ const target_type mtime_target::static_type
+ {
+ "mtime_target",
+ &target::static_type,
+ nullptr,
+ nullptr,
+ &search_target,
+ false
+ };
+
+ const target_type path_target::static_type
+ {
+ "path_target",
+ &mtime_target::static_type,
+ nullptr,
+ nullptr,
+ &search_target,
+ false
+ };
+
+ template <typename T>
+ static target*
+ file_factory (const target_type&, dir_path d, string n, const string* e)
+ {
+ // The file target type doesn't imply any extension. So if one
+ // wasn't specified, set it to empty rather than unspecified.
+ // In other words, we always treat file{foo} as file{foo.}.
+ //
+ return new T (move (d),
+ move (n),
+ (e != nullptr ? e : &extension_pool.find ("")));
+ }
+
+ constexpr const char file_ext_var[] = "extension";
+ constexpr const char file_ext_def[] = "";
+
+ const target_type file::static_type
+ {
+ "file",
+ &path_target::static_type,
+ &file_factory<file>,
+ &target_extension_var<file_ext_var, file_ext_def>,
+ &search_file,
+ false
+ };
+
+ const target_type alias::static_type
+ {
+ "alias",
+ &target::static_type,
+ &target_factory<alias>,
+ nullptr, // Should never need.
+ &search_alias,
+ false
+ };
+
+ const target_type dir::static_type
+ {
+ "dir",
+ &alias::static_type,
+ &target_factory<dir>,
+ nullptr, // Should never need.
+ &search_alias,
+ false
+ };
+
+ const target_type fsdir::static_type
+ {
+ "fsdir",
+ &target::static_type,
+ &target_factory<fsdir>,
+ nullptr, // Should never need.
+ &search_target,
+ false
+ };
+
+ static const std::string&
+ buildfile_target_extension (const target_key& tk, scope&)
+ {
+ // If the name is special 'buildfile', then there is no extension,
+ // otherwise it is .build.
+ //
+ return extension_pool.find (*tk.name == "buildfile" ? "" : "build");
+ }
+
+ const target_type buildfile::static_type
+ {
+ "buildfile",
+ &file::static_type,
+ &file_factory<buildfile>,
+ &buildfile_target_extension,
+ &search_file,
+ false
+ };
+
+ const target_type doc::static_type
+ {
+ "doc",
+ &file::static_type,
+ &file_factory<doc>,
+ &target_extension_var<file_ext_var, file_ext_def>, // Same as file.
+ &search_file,
+ false
+ };
+
+ static target*
+ man_factory (const target_type&, dir_path d, string n, const string* e)
+ {
+ if (e == nullptr)
+ fail << "man target '" << n << "' must include extension (man section)";
+
+ return new man (move (d), move (n), e);
+ }
+
+ const target_type man::static_type
+ {
+ "man",
+ &doc::static_type,
+ &man_factory,
+ nullptr, // Should be specified explicitly.
+ &search_file,
+ false
+ };
+
+ constexpr const char man1_ext[] = "1";
+ const target_type man1::static_type
+ {
+ "man1",
+ &man::static_type,
+ &file_factory<man1>,
+ &target_extension_fix<man1_ext>,
+ &search_file,
+ false
+ };
+}
diff --git a/build2/target.ixx b/build2/target.ixx
new file mode 100644
index 0000000..25c33ba
--- /dev/null
+++ b/build2/target.ixx
@@ -0,0 +1,85 @@
+// file : build2/target.ixx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+namespace build2
+{
+ // prerequisite_ref
+ //
+ inline bool prerequisite_ref::
+ belongs (const target& t) const
+ {
+ const auto& p (t.prerequisites);
+ return !(p.empty () || this < &p.front () || this > &p.back ());
+ }
+
+ // prerequisite_member
+ //
+ inline prerequisite& prerequisite_member::
+ as_prerequisite (tracer& trace) const
+ {
+ if (target == nullptr)
+ return prerequisite;
+
+ // The use of the group's prerequisite scope is debatable.
+ //
+ scope& s (prerequisite.get ().scope);
+ return s.prerequisites.insert (nullptr, key ().tk, s, trace).first;
+ }
+
+ // prerequisite_members
+ //
+ group_view
+ resolve_group_members (action, target&); // <build2/algorithm>
+
+ template <typename T>
+ inline auto prerequisite_members_range<T>::iterator::
+ operator++ () -> iterator&
+ {
+ if (g_.count != 0)
+ {
+ if (++j_ <= g_.count)
+ return *this;
+
+ // Switch back to prerequisite iteration mode.
+ //
+ g_.count = 0;
+ }
+
+ ++i_;
+
+ // Switch to member iteration mode.
+ //
+ if (r_->members_ && i_ != r_->e_ && i_->get ().type.see_through)
+ {
+ bool r (switch_members ());
+ assert (r); // Group could not be resolved.
+ }
+
+ return *this;
+ }
+
+ template <typename T>
+ inline bool prerequisite_members_range<T>::iterator::
+ switch_members ()
+ {
+ do
+ {
+ g_ = resolve_group_members (r_->a_, search (*i_));
+
+ // If members are not know, iterate over the group as itself.
+ //
+ if (g_.members == nullptr)
+ {
+ g_.count = 0;
+ return false;
+ }
+ }
+ while (g_.count == 0 && // Skip empty groups.
+ ++i_ != r_->e_ &&
+ i_->get ().type.see_through);
+
+ j_ = 1; // Start from the first group member.
+ return true;
+ }
+}
diff --git a/build2/target.txx b/build2/target.txx
new file mode 100644
index 0000000..3209720
--- /dev/null
+++ b/build2/target.txx
@@ -0,0 +1,58 @@
+// file : build2/target.txx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/scope>
+#include <build2/context> // extension_pool
+#include <build2/diagnostics>
+#include <build2/prerequisite>
+
+namespace build2
+{
+ template <const char* ext>
+ const string&
+ target_extension_fix (const target_key&, scope&)
+ {
+ return extension_pool.find (ext);
+ }
+
+ template <const char* var, const char* def>
+ const string&
+ target_extension_var (const target_key& tk, scope& s)
+ {
+ // Include target type/pattern-specific variables.
+ //
+ if (auto l = s.lookup (tk, var))
+ {
+ // Help the user here and strip leading '.' from the extension.
+ //
+ const string& e (as<string> (*l));
+ return extension_pool.find (
+ !e.empty () && e.front () == '.' ? string (e, 1) : e);
+ }
+
+ if (def != nullptr)
+ return extension_pool.find (def);
+
+ {
+ diag_record dr;
+ dr << error << "no default extension in variable '" << var << "'"
+ << info << "required to derive file name for ";
+
+ // This is a bit hacky: we may be dealing with a target (see
+ // file::derive_path()) or prerequsite (see search_existing_file()).
+ // So we are going to check if dir is absolute. If it is, then
+ // we assume this is a target, otherwise -- prerequsite.
+ //
+ if (tk.dir->absolute ())
+ dr << "target " << tk;
+ else
+ dr << "prerequisite " << prerequisite_key {nullptr, tk, &s};
+
+ dr << info << "perhaps you forgot to add "
+ << tk.type->name << "{*}: " << var << " = ...";
+ }
+
+ throw failed ();
+ }
+}
diff --git a/build2/test/module b/build2/test/module
new file mode 100644
index 0000000..76fd9e1
--- /dev/null
+++ b/build2/test/module
@@ -0,0 +1,24 @@
+// file : build2/test/module -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_TEST_MODULE
+#define BUILD2_TEST_MODULE
+
+#include <build2/types>
+#include <build2/module>
+
+namespace build2
+{
+ namespace test
+ {
+ extern "C" void
+ test_boot (scope&, const location&, unique_ptr<module>&);
+
+ extern "C" bool
+ test_init (
+ scope&, scope&, const location&, unique_ptr<module>&, bool, bool);
+ }
+}
+
+#endif // BUILD2_TEST_MODULE
diff --git a/build2/test/module.cxx b/build2/test/module.cxx
new file mode 100644
index 0000000..133849a
--- /dev/null
+++ b/build2/test/module.cxx
@@ -0,0 +1,88 @@
+// file : build2/test/module.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/test/module>
+
+#include <build2/scope>
+#include <build2/target>
+#include <build2/rule>
+#include <build2/diagnostics>
+
+#include <build2/test/operation>
+#include <build2/test/rule>
+
+using namespace std;
+using namespace butl;
+
+namespace build2
+{
+ namespace test
+ {
+ static rule rule_;
+
+ extern "C" void
+ test_boot (scope& root, const location&, unique_ptr<module>&)
+ {
+ tracer trace ("test::boot");
+
+ level5 ([&]{trace << "for " << root.out_path ();});
+
+ // Register the test operation.
+ //
+ root.operations.insert (test_id, test);
+ }
+
+ extern "C" bool
+ test_init (scope& root,
+ scope&,
+ const location& l,
+ unique_ptr<module>&,
+ bool first,
+ bool)
+ {
+ tracer trace ("test::init");
+
+ if (!first)
+ {
+ warn (l) << "multiple test module initializations";
+ return true;
+ }
+
+ const dir_path& out_root (root.out_path ());
+ level5 ([&]{trace << "for " << out_root;});
+
+ // Enter module variables.
+ //
+ {
+ auto& v (var_pool);
+
+ v.find ("test", bool_type);
+ v.find ("test.input", name_type);
+ v.find ("test.output", name_type);
+ v.find ("test.roundtrip", name_type);
+ v.find ("test.options", strings_type);
+ v.find ("test.arguments", strings_type);
+ }
+
+ // Register rules.
+ //
+ {
+ auto& r (root.rules);
+
+ // Register our test running rule.
+ //
+ r.insert<target> (perform_test_id, "test", rule_);
+
+ // Register our rule for the dist meta-operation. We need
+ // to do this because we have "ad-hoc prerequisites" (test
+ // input/output files) that need to be entered into the
+ // target list.
+ //
+ r.insert<target> (dist_id, test_id, "test", rule_);
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/build2/test/operation b/build2/test/operation
new file mode 100644
index 0000000..f924d4c
--- /dev/null
+++ b/build2/test/operation
@@ -0,0 +1,18 @@
+// file : build2/test/operation -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_TEST_OPERATION
+#define BUILD2_TEST_OPERATION
+
+#include <build2/operation>
+
+namespace build2
+{
+ namespace test
+ {
+ extern operation_info test;
+ }
+}
+
+#endif // BUILD2_TEST_OPERATION
diff --git a/build2/test/operation.cxx b/build2/test/operation.cxx
new file mode 100644
index 0000000..2bae629
--- /dev/null
+++ b/build2/test/operation.cxx
@@ -0,0 +1,32 @@
+// file : build2/test/operation.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/test/operation>
+
+using namespace std;
+using namespace butl;
+
+namespace build2
+{
+ namespace test
+ {
+ static operation_id
+ test_pre (meta_operation_id mo)
+ {
+ // Run update as a pre-operation, unless we are disfiguring.
+ //
+ return mo != disfigure_id ? update_id : 0;
+ }
+
+ operation_info test {
+ "test",
+ "test",
+ "testing",
+ "has nothing to test", // We cannot "be tested".
+ execution_mode::first,
+ &test_pre,
+ nullptr
+ };
+ }
+}
diff --git a/build2/test/rule b/build2/test/rule
new file mode 100644
index 0000000..1102876
--- /dev/null
+++ b/build2/test/rule
@@ -0,0 +1,30 @@
+// file : build2/test/rule -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_TEST_RULE
+#define BUILD2_TEST_RULE
+
+#include <build2/rule>
+#include <build2/operation>
+
+namespace build2
+{
+ namespace test
+ {
+ class rule: public build2::rule
+ {
+ public:
+ virtual match_result
+ match (action, target&, const std::string&) const;
+
+ virtual recipe
+ apply (action, target&, const match_result&) const;
+
+ static target_state
+ perform_test (action, target&);
+ };
+ }
+}
+
+#endif // BUILD2_TEST_RULE
diff --git a/build2/test/rule.cxx b/build2/test/rule.cxx
new file mode 100644
index 0000000..abf9fcf
--- /dev/null
+++ b/build2/test/rule.cxx
@@ -0,0 +1,439 @@
+// file : build2/test/rule.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/test/rule>
+
+#include <butl/process>
+
+#include <build2/scope>
+#include <build2/target>
+#include <build2/algorithm>
+#include <build2/diagnostics>
+
+#include <build2/config/utility> // add_options()
+
+using namespace std;
+using namespace butl;
+
+namespace build2
+{
+ namespace test
+ {
+ match_result rule::
+ match (action a, target& t, const std::string&) const
+ {
+ // First determine if this is a test. This is controlled by
+ // the test target variable and text.<tt> scope variables.
+ // Also, it feels redundant to specify, say, "test = true"
+ // and "test.output = test.out" -- the latter already says
+ // this is a test. So take care of that as well.
+ //
+ bool r (false);
+ lookup<const value> l;
+
+ // @@ This logic doesn't take into account target type/pattern-
+ // specific variables.
+ //
+ // @@ Perhaps a find_any(<list-of-vars>)?
+ //
+ for (auto p (t.vars.find_namespace ("test"));
+ p.first != p.second;
+ ++p.first)
+ {
+ const variable& var (p.first->first);
+ const value& val (p.first->second);
+
+ // If we have test, then always use that.
+ //
+ if (var.name == "test")
+ {
+ l = lookup<const value> (val, t);
+ break;
+ }
+
+ // Otherwise check for variables that would indicate this
+ // is a test.
+ //
+ if (var.name == "test.input" ||
+ var.name == "test.output" ||
+ var.name == "test.roundtrip" ||
+ var.name == "test.options" ||
+ var.name == "test.arguments")
+ {
+ r = true;
+ break;
+ }
+ }
+
+ if (!r)
+ {
+ // See if there is a scope variable.
+ //
+ if (!l.defined ())
+ l = t.base_scope ()[
+ var_pool.find (string("test.") + t.type ().name, bool_type)];
+
+ r = l && as<bool> (*l);
+ }
+
+ // If this is the update pre-operation, then all we really need to
+ // do is say we are not a match and the standard matching machinery
+ // will (hopefully) find the rule to update this target.
+ //
+ // There is one thing that compilates this simple approach: test
+ // input/output. While normally they will be existing (in src_base)
+ // files, they could also be auto-generated. In fact, they could
+ // only be needed for testing, which means the normall update won't
+ // even know about them (nor clean, for that matter; this is why we
+ // need cleantest).
+ //
+ // To make generated input/output work we will have to cause their
+ // update ourselves. I other words, we may have to do some actual
+ // work for (update, test), and not simply "guide" (update, 0) as
+ // to which targets need updating. For how exactly we are going to
+ // do it, see apply() below.
+ //
+ match_result mr (t, r);
+
+ // If this is the update pre-operation, change the recipe action
+ // to (update, 0) (i.e., "unconditional update").
+ //
+ if (r && a.operation () == update_id)
+ mr.recipe_action = action (a.meta_operation (), update_id);
+
+ return mr;
+ }
+
+ recipe rule::
+ apply (action a, target& t, const match_result& mr) const
+ {
+ tracer trace ("test::rule::apply");
+
+ if (!mr.bvalue) // Not a test.
+ return noop_recipe;
+
+ // Ok, if we are here, then this means:
+ //
+ // 1. This target is a test.
+ // 2. The action is either
+ // a. (perform, test, 0) or
+ // b. (*, update, install)
+ //
+ // In both cases, the next step is to see if we have test.{input,
+ // output,roundtrip}.
+ //
+
+ // First check the target-specific vars since they override any
+ // scope ones.
+ //
+ auto il (t.vars["test.input"]);
+ auto ol (t.vars["test.output"]);
+ auto rl (t.vars["test.roundtrip"]);
+ auto al (t.vars["test.arguments"]); // Should be input or arguments.
+
+ if (al)
+ {
+ if (il)
+ fail << "both test.input and test.arguments specified for "
+ << "target " << t;
+
+ if (rl)
+ fail << "both test.roundtrip and test.arguments specified for "
+ << "target " << t;
+ }
+
+ scope& bs (t.base_scope ());
+
+ if (!il && !ol && !rl)
+ {
+ string n ("test.");
+ n += t.type ().name;
+
+ const variable& in (var_pool.find (n + ".input", name_type));
+ const variable& on (var_pool.find (n + ".output", name_type));
+ const variable& rn (var_pool.find (n + ".roundtrip", name_type));
+
+ // We should only keep value(s) that were specified together
+ // in the innermost scope.
+ //
+ // @@ Shouldn't we stop at project root?
+ //
+ for (scope* s (&bs); s != nullptr; s = s->parent_scope ())
+ {
+ ol = s->vars[on];
+
+ if (!al) // Not overriden at target level by test.arguments?
+ {
+ il = s->vars[in];
+ rl = s->vars[rn];
+ }
+
+ if (il || ol || rl)
+ break;
+ }
+ }
+
+ const name* in;
+ const name* on;
+
+ // Reduce the roundtrip case to input/output.
+ //
+ if (rl)
+ {
+ if (il || ol)
+ fail << "both test.roundtrip and test.input/output specified "
+ << "for target " << t;
+
+ in = on = &as<name> (*rl);
+ }
+ else
+ {
+ in = il ? &as<name> (*il) : nullptr;
+ on = ol ? &as<name> (*ol) : nullptr;
+ }
+
+ // Resolve them to targets, which normally would be existing files
+ // but could also be targets that need updating.
+ //
+ target* it (in != nullptr ? &search (*in, bs) : nullptr);
+ target* ot (on != nullptr ? in == on ? it : &search (*on, bs) : nullptr);
+
+ if (a.operation () == update_id)
+ {
+ // First see if input/output are existing, up-to-date files. This
+ // is a common case optimization.
+ //
+ if (it != nullptr)
+ {
+ build2::match (a, *it);
+
+ if (it->state () == target_state::unchanged)
+ {
+ unmatch (a, *it);
+ it = nullptr;
+ }
+ }
+
+ if (ot != nullptr)
+ {
+ if (in != on)
+ {
+ build2::match (a, *ot);
+
+ if (ot->state () == target_state::unchanged)
+ {
+ unmatch (a, *ot);
+ ot = nullptr;
+ }
+ }
+ else
+ ot = it;
+ }
+
+
+ // Find the "real" update rule, that is, the rule that would
+ // have been found if we signalled that we do not match from
+ // match() above.
+ //
+ recipe d (match_delegate (a, t).first);
+
+ // If we have no input/output that needs updating, then simply
+ // redirect to it.
+ //
+ if (it == nullptr && ot == nullptr)
+ return d;
+
+ // Ok, time to handle the worst case scenario: we need to
+ // cause update of input/output targets and also delegate
+ // to the real update.
+ //
+ return [it, ot, dr = move (d)] (action a, target& t) -> target_state
+ {
+ // Do the general update first.
+ //
+ target_state r (execute_delegate (dr, a, t));
+
+ if (it != nullptr)
+ r |= execute (a, *it);
+
+ if (ot != nullptr)
+ r |= execute (a, *ot);
+
+ return r;
+ };
+ }
+ else
+ {
+ // Cache the targets in our prerequsite targets lists where they
+ // can be found by perform_test(). If we have either or both,
+ // then the first entry is input and the second -- output (either
+ // can be NULL).
+ //
+ if (it != nullptr || ot != nullptr)
+ {
+ auto& pts (t.prerequisite_targets);
+ pts.resize (2, nullptr);
+ pts[0] = it;
+ pts[1] = ot;
+ }
+
+ return &perform_test;
+ }
+ }
+
+ static void
+ add_arguments (cstrings& args, const target& t, const char* n)
+ {
+ string var ("test.");
+ var += n;
+
+ auto l (t.vars[var]);
+
+ if (!l)
+ {
+ var.resize (5);
+ var += t.type ().name;
+ var += '.';
+ var += n;
+ l = t.base_scope ()[var_pool.find (var, strings_type)];
+ }
+
+ if (l)
+ config::append_options (args, as<strings> (*l));
+ }
+
+ // The format of args shall be:
+ //
+ // name1 arg arg ... nullptr
+ // name2 arg arg ... nullptr
+ // ...
+ // nameN arg arg ... nullptr nullptr
+ //
+ static bool
+ run_test (target& t,
+ diag_record& dr,
+ char const** args,
+ process* prev = nullptr)
+ {
+ // Find the next process, if any.
+ //
+ char const** next (args);
+ for (next++; *next != nullptr; next++) ;
+ next++;
+
+ // Redirect stdout to a pipe unless we are last, in which
+ // case redirect it to stderr.
+ //
+ int out (*next == nullptr ? 2 : -1);
+ bool pr, wr;
+
+ try
+ {
+ if (prev == nullptr)
+ {
+ // First process.
+ //
+ process p (args, 0, out);
+ pr = *next == nullptr || run_test (t, dr, next, &p);
+ wr = p.wait ();
+ }
+ else
+ {
+ // Next process.
+ //
+ process p (args, *prev, out);
+ pr = *next == nullptr || run_test (t, dr, next, &p);
+ wr = p.wait ();
+ }
+ }
+ catch (const process_error& e)
+ {
+ error << "unable to execute " << args[0] << ": " << e.what ();
+
+ if (e.child ())
+ exit (1);
+
+ throw failed ();
+ }
+
+ if (!wr)
+ {
+ if (pr) // First failure?
+ dr << fail << "test " << t << " failed"; // Multi test: test 1.
+
+ dr << error << "non-zero exit status: ";
+ print_process (dr, args);
+ }
+
+ return pr && wr;
+ }
+
+ target_state rule::
+ perform_test (action, target& t)
+ {
+ // @@ Would be nice to print what signal/core was dumped.
+ //
+ // @@ Doesn't have to be a file target if we have test.cmd.
+ //
+
+ file& ft (static_cast<file&> (t));
+ assert (!ft.path ().empty ()); // Should have been assigned by update.
+
+ cstrings args {ft.path ().string ().c_str ()};
+
+ // Do we have options?
+ //
+ add_arguments (args, t, "options");
+
+ // Do we have input?
+ //
+ auto& pts (t.prerequisite_targets);
+ if (pts.size () != 0 && pts[0] != nullptr)
+ {
+ file& it (static_cast<file&> (*pts[0]));
+ assert (!it.path ().empty ()); // Should have been assigned by update.
+ args.push_back (it.path ().string ().c_str ());
+ }
+ // Maybe arguments then?
+ //
+ else
+ add_arguments (args, t, "arguments");
+
+ args.push_back (nullptr);
+
+ // Do we have output?
+ //
+ if (pts.size () != 0 && pts[1] != nullptr)
+ {
+ file& ot (static_cast<file&> (*pts[1]));
+ assert (!ot.path ().empty ()); // Should have been assigned by update.
+
+ args.push_back ("diff");
+ args.push_back ("-u");
+ args.push_back (ot.path ().string ().c_str ());
+ args.push_back ("-");
+ args.push_back (nullptr);
+ }
+
+ args.push_back (nullptr); // Second.
+
+ if (verb >= 2)
+ print_process (args);
+ else if (verb)
+ text << "test " << t;
+
+ {
+ diag_record dr;
+
+ if (!run_test (t, dr, args.data ()))
+ {
+ dr << info << "test command line: ";
+ print_process (dr, args);
+ }
+ }
+
+ return target_state::changed;
+ }
+ }
+}
diff --git a/build2/token b/build2/token
new file mode 100644
index 0000000..55ab1ee
--- /dev/null
+++ b/build2/token
@@ -0,0 +1,73 @@
+// file : build2/token -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_TOKEN
+#define BUILD2_TOKEN
+
+#include <string>
+#include <iosfwd>
+#include <cstddef> // size_t
+#include <cstdint> // uint64_t
+#include <utility> // move
+
+namespace build2
+{
+ enum class token_type
+ {
+ eos,
+ name,
+ newline,
+ pair_separator,
+ colon,
+ lcbrace,
+ rcbrace,
+ equal,
+ equal_plus,
+ plus_equal,
+ dollar,
+ lparen,
+ rparen
+ };
+
+ class token
+ {
+ public:
+ token_type type;
+ bool separated; // Whitespace-separated from the previous token.
+ bool quoted; // Name (or some part of it) was quoted.
+
+ char pair; // Only valid for pair_separator.
+ std::string value; // Only valid for name.
+
+ std::uint64_t line;
+ std::uint64_t column;
+
+ public:
+ token (token_type t, bool s, std::uint64_t l, std::uint64_t c)
+ : type (t), separated (s), quoted (false), line (l), column (c) {}
+
+ token (char p, bool s, std::uint64_t l, std::uint64_t c)
+ : type (token_type::pair_separator),
+ separated (s),
+ quoted (false),
+ pair (p),
+ line (l),
+ column (c) {}
+
+ token (std::string n, bool s, bool q, std::uint64_t l, std::uint64_t c)
+ : type (token_type::name),
+ separated (s),
+ quoted (q),
+ value (std::move (n)),
+ line (l),
+ column (c) {}
+ };
+
+ // Output the token value in a format suitable for diagnostics.
+ //
+ std::ostream&
+ operator<< (std::ostream&, const token&);
+}
+
+#endif // BUILD2_TOKEN
diff --git a/build2/token.cxx b/build2/token.cxx
new file mode 100644
index 0000000..b14fc00
--- /dev/null
+++ b/build2/token.cxx
@@ -0,0 +1,35 @@
+// file : build2/token.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/token>
+
+#include <ostream>
+
+using namespace std;
+
+namespace build2
+{
+ ostream&
+ operator<< (ostream& os, const token& t)
+ {
+ switch (t.type)
+ {
+ case token_type::eos: os << "<end-of-file>"; break;
+ case token_type::newline: os << "<newline>"; break;
+ case token_type::pair_separator: os << "<pair separator>"; break;
+ case token_type::colon: os << ":"; break;
+ case token_type::lcbrace: os << "{"; break;
+ case token_type::rcbrace: os << "}"; break;
+ case token_type::equal: os << "="; break;
+ case token_type::equal_plus: os << "=+"; break;
+ case token_type::plus_equal: os << "+="; break;
+ case token_type::dollar: os << "$"; break;
+ case token_type::lparen: os << "("; break;
+ case token_type::rparen: os << ")"; break;
+ case token_type::name: os << t.value; break;
+ }
+
+ return os;
+ }
+}
diff --git a/build2/types b/build2/types
new file mode 100644
index 0000000..da96805
--- /dev/null
+++ b/build2/types
@@ -0,0 +1,55 @@
+// file : build2/types -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_TYPES
+#define BUILD2_TYPES
+
+#include <vector>
+#include <string>
+#include <utility> // pair
+#include <memory> // unique_ptr, shared_ptr
+#include <cstddef> // size_t
+#include <functional> // reference_wrapper
+
+#include <butl/path>
+#include <butl/timestamp>
+
+#include <build2/name>
+
+namespace build2
+{
+ // Commonly-used types.
+ //
+ using std::pair;
+ using std::size_t;
+ using std::string;
+ using std::unique_ptr;
+ using std::shared_ptr;
+ using std::reference_wrapper;
+ using std::vector;
+
+ using strings = vector<string>;
+ using cstrings = vector<const char*>;
+
+ // <butl/path>
+ //
+ using butl::path;
+ using butl::dir_path;
+ using butl::basic_path;
+ using butl::invalid_path;
+
+ using paths = std::vector<path>;
+ using dir_paths = std::vector<dir_path>;
+
+ // <butl/timestamp>
+ //
+ using butl::system_clock;
+ using butl::timestamp;
+ using butl::duration;
+ using butl::timestamp_unknown;
+ using butl::timestamp_nonexistent;
+ using butl::operator<<;
+}
+
+#endif // BUILD2_TYPES
diff --git a/build2/utility b/build2/utility
new file mode 100644
index 0000000..a460bab
--- /dev/null
+++ b/build2/utility
@@ -0,0 +1,85 @@
+// file : build2/utility -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_UTILITY
+#define BUILD2_UTILITY
+
+#include <utility> // move(), make_pair()
+#include <cassert> // assert()
+#include <exception> // uncaught_exception()
+#include <unordered_set>
+
+#include <build2/types>
+
+namespace build2
+{
+ using std::move;
+ using std::make_pair;
+
+ // Empty string and path.
+ //
+ extern const std::string empty_string;
+ extern const path empty_path;
+ extern const dir_path empty_dir_path;
+
+ // Parse version string in the X.Y.Z[-{a|b}N] to a version integer in the
+ // AABBCCDD form as describe in <build2/version>. Throw invalid_argument
+ // if the passed string is not a valid version.
+ //
+ unsigned int
+ to_version (const string&);
+
+ // Call a function if there is an exception.
+ //
+
+ // Means we are in the body of a destructor that is being called
+ // as part of the exception stack unwindining. Used to compensate
+ // for the deficiencies of uncaught_exception() until C++17
+ // uncaught_exceptions() becomes available.
+ //
+ // @@ MT: will have to be TLS.
+ //
+ extern bool exception_unwinding_dtor;
+
+ template <typename F>
+ struct exception_guard;
+
+ template <typename F>
+ inline exception_guard<F>
+ make_exception_guard (F f)
+ {
+ return exception_guard<F> (move (f));
+ }
+
+ template <typename F>
+ struct exception_guard
+ {
+ exception_guard (F f): f_ (move (f)) {}
+ ~exception_guard ()
+ {
+ if (std::uncaught_exception ())
+ {
+ exception_unwinding_dtor = true;
+ f_ ();
+ exception_unwinding_dtor = false;
+ }
+ }
+
+ private:
+ F f_;
+ };
+
+ // Pools (@@ perhaps move into a separate header).
+ //
+ struct string_pool: std::unordered_set<std::string>
+ {
+ const std::string&
+ find (const char* s) {return *emplace (s).first;}
+
+ const std::string&
+ find (const std::string& s) {return *emplace (s).first;}
+ };
+}
+
+#endif // BUILD2_UTILITY
diff --git a/build2/utility.cxx b/build2/utility.cxx
new file mode 100644
index 0000000..fcecb5b
--- /dev/null
+++ b/build2/utility.cxx
@@ -0,0 +1,94 @@
+// file : build2/utility.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/utility>
+
+#include <cstdlib> // strtol()
+
+using namespace std;
+
+namespace build2
+{
+ const string empty_string;
+ const path empty_path;
+ const dir_path empty_dir_path;
+
+ unsigned int
+ to_version (const string& s)
+ {
+ // See tests/version.
+ //
+
+ auto parse = [&s] (size_t& p, const char* m, long min = 0, long max = 99)
+ -> unsigned int
+ {
+ if (s[p] == '-' || s[p] == '+') // stoi() allows these.
+ throw invalid_argument (m);
+
+ const char* b (s.c_str () + p);
+ char* e;
+ long r (strtol (b, &e, 10));
+
+ if (b == e || r < min || r > max)
+ throw invalid_argument (m);
+
+ p = e - s.c_str ();
+ return static_cast<unsigned int> (r);
+ };
+
+ auto bail = [] (const char* m) {throw invalid_argument (m);};
+
+ unsigned int ma, mi, bf, ab (0);
+
+ size_t p (0), n (s.size ());
+ ma = parse (p, "invalid major version");
+
+ if (p >= n || s[p] != '.')
+ bail ("'.' expected after major version");
+
+ mi = parse (++p, "invalid minor version");
+
+ if (p >= n || s[p] != '.')
+ bail ("'.' expected after minor version");
+
+ bf = parse (++p, "invalid bugfix version");
+
+ if (p < n)
+ {
+ if (s[p] != '-')
+ bail ("'-' expected after bugfix version");
+
+ char k (s[++p]);
+
+ if (k != 'a' && k != 'b')
+ bail ("'a' or 'b' expected in release component");
+
+ ab = parse (++p, "invalid release component", 1, 49);
+
+ if (p != n)
+ bail ("junk after release component");
+
+ if (k == 'b')
+ ab += 50;
+ }
+
+ // AABBCCDD
+ unsigned int r (ma * 1000000U +
+ mi * 10000U +
+ bf * 100U);
+
+ if (ab != 0)
+ {
+ if (r == 0)
+ bail ("0.0.0 version with release component");
+
+ r -= 100;
+ r += ab;
+ }
+
+ return r;
+ }
+
+ bool exception_unwinding_dtor = false;
+}
diff --git a/build2/variable b/build2/variable
new file mode 100644
index 0000000..6304979
--- /dev/null
+++ b/build2/variable
@@ -0,0 +1,831 @@
+// file : build2/variable -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_VARIABLE
+#define BUILD2_VARIABLE
+
+#include <map>
+#include <vector>
+#include <cstddef> // nullptr_t
+#include <utility> // pair, make_pair()
+#include <iterator>
+#include <functional> // hash
+#include <type_traits> // conditional, is_reference, remove_reference, etc.
+#include <unordered_set>
+
+#include <butl/prefix-map>
+
+#include <build2/types>
+#include <build2/utility>
+
+#include <build2/target-type>
+
+namespace build2
+{
+ struct variable;
+
+ // If assign is NULL, then the value is assigned as is. If append
+ // is NULL, then the names are appended to the end of the value
+ // and assign is called, if not NULL. Variable is provided primarily
+ // for diagnostics. Return true if the resulting value is not empty.
+ //
+ struct value_type
+ {
+ const char* name;
+ bool (*const assign) (names&, const variable&);
+ bool (*const append) (names&, names, const variable&);
+ };
+
+ enum class variable_visibility
+ {
+ scope, // This scope (no outer scopes).
+ project, // This project (no outer projects).
+ normal // All outer scopes.
+ };
+
+ // variable
+ //
+ // The two variables are considered the same if they have the same name.
+ //
+ struct variable
+ {
+ std::string name;
+ const value_type* type; // If NULL, then not (yet) typed.
+ variable_visibility visibility;
+ char pairs; // Pair symbold or '\0' if not used.
+ };
+
+ inline bool
+ operator== (const variable& x, const variable& y) {return x.name == y.name;}
+
+ typedef std::reference_wrapper<const variable> variable_cref;
+
+ // value
+ //
+ class value
+ {
+ public:
+ // By default we create NULL value.
+ //
+ explicit value (const value_type* t = nullptr)
+ : type (t), state_ (state_type::null) {}
+
+ value (value&&) = default;
+
+ value&
+ operator= (std::nullptr_t)
+ {
+ data_.clear ();
+ state_ = state_type::null;
+ return *this;
+ }
+
+ value&
+ operator= (value&&);
+
+ value&
+ operator= (const value& v)
+ {
+ if (&v != this)
+ *this = value (v);
+ return *this;
+ }
+
+ value&
+ operator= (reference_wrapper<value> v) {return *this = v.get ();}
+
+ value&
+ operator= (reference_wrapper<const value> v) {return *this = v.get ();}
+
+ value&
+ append (value, const variable&); // Aka operator+=().
+
+ value&
+ prepend (value, const variable&); // Aka operator=+().
+
+ // Forwarded to the representation type's assign()/append() (see below).
+ //
+ template <typename T> value& operator= (T);
+ value& operator= (const char* v) {return *this = std::string (v);}
+
+ template <typename T> value& operator+= (T);
+ value& operator+= (const char* v) {return *this += std::string (v);}
+
+ private:
+ explicit value (const value&) = default;
+
+ public:
+ const value_type* type; // NULL means this value is not (yet) typed.
+
+ bool null () const {return state_ == state_type::null;}
+ bool empty () const {return state_ == state_type::empty;}
+
+ explicit operator bool () const {return !null ();}
+ bool operator== (std::nullptr_t) const {return null ();}
+ bool operator!= (std::nullptr_t) const {return !null ();}
+
+ // Raw data read interface.
+ //
+ using const_iterator = names::const_iterator;
+
+ const_iterator begin () const {return data_.begin ();}
+ const_iterator end () const {return data_.end ();}
+
+ // Raw data write interface. Note that it triggers callbacks for
+ // typed values. Variable is passed for diagnostics.
+ //
+ void
+ assign (names, const variable&);
+
+ void
+ append (names, const variable&);
+
+ void
+ prepend (names, const variable&);
+
+ public:
+ // Don't use directly except in the implementation of representation
+ // types, in which case take care to update state.
+ //
+ enum class state_type {null, empty, filled} state_;
+ names data_;
+ };
+
+ //@@ Right now we assume that for each value type each value has a
+ // unique representation. This is currently not the case for map.
+ //
+ inline bool
+ operator== (const value& x, const value& y)
+ {
+ return x.state_ == y.state_ && x.data_ == y.data_;
+ }
+
+ inline bool
+ operator!= (const value& x, const value& y) {return !(x == y);}
+
+ // lookup
+ //
+ // A variable can be undefined, NULL, or contain a (potentially
+ // empty) value.
+ //
+ struct variable_map;
+
+ template <typename V>
+ struct lookup
+ {
+ V* value;
+ const variable_map* vars;
+
+ bool
+ defined () const {return value != nullptr;}
+
+ // Note: returns true if defined and not NULL.
+ //
+ explicit operator bool () const {return defined () && !value->null ();}
+
+ V& operator* () const {return *value;}
+ V* operator-> () const {return value;}
+
+ // Return true if this value belongs to the specified scope or target.
+ // Note that it can also be a target type/pattern-specific value.
+ //
+ template <typename T>
+ bool
+ belongs (const T& x) const {return vars == &x.vars;}
+
+ lookup (): value (nullptr), vars (nullptr) {}
+ lookup (V* v, const variable_map* vs)
+ : value (v), vars (v != nullptr ? vs : nullptr) {}
+
+ template <typename T>
+ lookup (V& v, const T& x): lookup (&v, &x.vars) {}
+ };
+
+ // Representation types.
+ //
+ template <typename T> struct value_traits;
+
+ // Assign value type to the value.
+ //
+ template <typename T>
+ void assign (value&, const variable&);
+ void assign (value&, const value_type*, const variable&);
+
+ template <typename T> typename value_traits<T>::type as (value&);
+ template <typename T> typename value_traits<T>::const_type as (const value&);
+
+ // "Assign" simple value type to the value stored in name. Return false
+ // if the value is not valid for this type.
+ //
+ template <typename T> bool assign (name&);
+
+ template <typename T> typename value_traits<T>::type as (name&);
+ template <typename T> typename value_traits<T>::const_type as (const name&);
+
+ // bool
+ //
+ template <typename D>
+ struct bool_value
+ {
+ explicit
+ operator bool () const {return d->value[0] == 't';}
+
+ bool_value&
+ operator= (bool v)
+ {
+ d->value = v ? "true" : "false";
+ return *this;
+ }
+
+ bool_value&
+ operator+= (bool v)
+ {
+ if (!*this && v)
+ d->value = "true";
+ return *this;
+ }
+
+ // Implementation details.
+ //
+ public:
+ explicit bool_value (D& d): d (&d) {}
+
+ bool_value (const bool_value&) = delete;
+ bool_value& operator= (const bool_value&) = delete; // Rebind or deep?
+
+ bool_value (bool_value&&) = default;
+ bool_value& operator= (bool_value&&) = delete;
+
+ D* d; // name
+ };
+
+ template <>
+ struct value_traits<bool>
+ {
+ using type = bool_value<name>;
+ using const_type = bool_value<const name>;
+
+ static type as (name& n) {return type (n);}
+ static const_type as (const name& n) {return const_type (n);}
+
+ static type as (value&);
+ static const_type as (const value&);
+
+ static bool assign (name&);
+ static void assign (value&, bool);
+ static void append (value&, bool);
+
+ static const build2::value_type value_type;
+ };
+
+ extern const value_type* bool_type;
+
+ // string
+ //
+ template <>
+ struct value_traits<std::string>
+ {
+ using type = std::string&;
+ using const_type = const std::string&;
+
+ static type as (name& n) {return n.value;}
+ static const_type as (const name& n) {return n.value;}
+
+ static type as (value&);
+ static const_type as (const value&);
+
+ static bool assign (name&);
+ static void assign (value&, std::string);
+ static void append (value&, std::string);
+
+ static const build2::value_type value_type;
+ };
+
+ extern const value_type* string_type;
+
+ // dir_path
+ //
+ template <>
+ struct value_traits<dir_path>
+ {
+ using type = dir_path&;
+ using const_type = const dir_path&;
+
+ static type as (name& n) {return n.dir;}
+ static const_type as (const name& n) {return n.dir;}
+
+ static type as (value&);
+ static const_type as (const value&);
+
+ static bool assign (name&);
+ static void assign (value&, dir_path);
+ static void append (value&, dir_path);
+
+ static const build2::value_type value_type;
+ };
+
+ extern const value_type* dir_path_type;
+
+ // name
+ //
+ template <>
+ struct value_traits<name>
+ {
+ using type = name&;
+ using const_type = const name&;
+
+ static type as (name& n) {return n;}
+ static const_type as (const name& n) {return n;}
+
+ static type as (value&);
+ static const_type as (const value&);
+
+ static bool assign (name&) {return true;}
+ static void assign (value&, name);
+ static void append (value&, name) = delete;
+
+ static const build2::value_type value_type;
+ };
+
+ extern const value_type* name_type;
+
+ // vector<T>
+ //
+ template <typename T, typename D>
+ struct vector_value
+ {
+ using size_type = typename D::size_type;
+
+ using value_type = typename value_traits<T>::type;
+ using const_value_type = typename value_traits<T>::const_type;
+
+ template <typename I, typename V, typename R>
+ struct iterator_impl: I
+ {
+ using value_type = V;
+ using pointer = value_type*;
+ using reference = R;
+ using difference_type = typename I::difference_type;
+
+ iterator_impl () = default;
+ iterator_impl (const I& i): I (i) {}
+
+ // Note: operator->() is unavailable if R is a value.
+ //
+ reference operator* () const {return as<T> (I::operator* ());}
+ pointer operator-> () const {return &as<T> (I::operator* ());}
+ reference operator[] (difference_type n) const
+ {
+ return as<T> (I::operator[] (n));
+ }
+ };
+
+ // Make iterator the same as const_iterator if our data type is const.
+ //
+ using const_iterator =
+ iterator_impl<names::const_iterator, const T, const_value_type>;
+ using iterator = typename std::conditional<
+ std::is_const<D>::value,
+ const_iterator,
+ iterator_impl<names::iterator, T, value_type>>::type;
+
+ public:
+ vector_value&
+ operator= (std::vector<T> v) {assign (std::move (v)); return *this;}
+
+ vector_value&
+ assign (std::vector<T>);
+
+ template <typename D1>
+ vector_value&
+ assign (const vector_value<T, D1>&);
+
+ template <typename D1>
+ vector_value&
+ append (const vector_value<T, D1>&);
+
+ public:
+ bool empty () const {return d->empty ();}
+ size_type size () const {return d->size ();}
+
+ value_type operator[] (size_type i) {return as<T> ((*d)[i]);}
+ const_value_type operator[] (size_type i) const {return as<T> ((*d)[i]);}
+
+ iterator begin () {return iterator (d->begin ());}
+ iterator end () {return iterator (d->end ());}
+
+ const_iterator begin () const {return const_iterator (d->begin ());}
+ const_iterator end () const {return const_iterator (d->end ());}
+
+ const_iterator cbegin () const {return const_iterator (d->begin ());}
+ const_iterator cend () const {return const_iterator (d->end ());}
+
+ // Implementation details.
+ //
+ public:
+ explicit vector_value (D& d): d (&d) {}
+
+ vector_value (const vector_value&) = delete;
+ vector_value& operator= (const vector_value&) = delete; // Rebind or deep?
+
+ vector_value (vector_value&&) = default;
+ vector_value& operator= (vector_value&&) = default; //@@ TMP
+
+ explicit vector_value (std::nullptr_t): d (nullptr) {} //@@ TMP
+
+ D* d; // names
+ };
+
+ template <typename T>
+ struct value_traits<std::vector<T>>
+ {
+ using type = vector_value<T, names>;
+ using const_type = vector_value<T, const names>;
+
+ static type as (value&);
+ static const_type as (const value&);
+
+ template <typename V> static void assign (value&, V);
+ template <typename V> static void append (value&, V);
+
+ static const std::string type_name;
+ static const build2::value_type value_type;
+ };
+
+ template <typename T, typename D>
+ struct value_traits<vector_value<T, D>>: value_traits<std::vector<T>> {};
+
+ using strings_value = vector_value<std::string, names>;
+ using const_strings_value = vector_value<std::string, const names>;
+
+ extern const value_type* strings_type; // vector<string> aka strings
+ extern const value_type* dir_paths_type; // vector<dir_path> aka dir_paths
+ extern const value_type* names_type; // vector<name> aka names
+
+ // map<K, V>
+ //
+ template <typename K, typename V, typename D>
+ struct map_value
+ {
+ template <typename F, typename S>
+ struct pair
+ {
+ using first_type = typename std::conditional<
+ std::is_reference<F>::value,
+ std::reference_wrapper<typename std::remove_reference<F>::type>,
+ F>::type;
+
+ using second_type = typename std::conditional<
+ std::is_reference<S>::value,
+ std::reference_wrapper<typename std::remove_reference<S>::type>,
+ S>::type;
+
+ first_type first;
+ second_type second;
+ };
+
+ template <typename I, typename T>
+ struct iterator_impl
+ {
+ using value_type = T;
+ using pointer = value_type*;
+ using reference = value_type; // Note: value.
+ using difference_type = typename I::difference_type;
+ using iterator_category = std::bidirectional_iterator_tag;
+
+ iterator_impl () = default;
+ iterator_impl (const I& i): i_ (i) {}
+
+ pointer operator-> () const = delete;
+ reference operator* () const
+ {
+ return value_type {as<K> (*i_), as<V> (*(i_ + 1))};
+ }
+
+ iterator_impl& operator++ () {i_ += 2; return *this;}
+ iterator_impl operator++ (int) {auto r (*this); operator++ (); return r;}
+
+ iterator_impl& operator-- () {i_ -= 2; return *this;}
+ iterator_impl operator-- (int) {auto r (*this); operator-- (); return r;}
+
+ bool operator== (const iterator_impl& y) const {return i_ == y.i_;}
+ bool operator!= (const iterator_impl& y) const {return i_ != y.i_;}
+
+ private:
+ I i_;
+ };
+
+ using size_type = typename D::size_type;
+
+ using value_type = pair<typename value_traits<K>::const_type,
+ typename value_traits<V>::type>;
+
+ using const_value_type = pair<typename value_traits<K>::const_type,
+ typename value_traits<V>::const_type>;
+
+ // Make iterator the same as const_iterator if our data type is const.
+ //
+ using const_iterator =
+ iterator_impl<names::const_iterator, const_value_type>;
+ using iterator = typename std::conditional<
+ std::is_const<D>::value,
+ const_iterator,
+ iterator_impl<names::iterator, value_type>>::type;
+
+
+ public:
+ map_value&
+ operator= (std::map<K, V> m) {assign (std::move (m)); return *this;}
+
+ map_value&
+ assign (std::map<K, V>);
+
+ bool empty () const {return d->empty ();}
+ size_type size () const {return d->size ();}
+
+ iterator find (const K&);
+ const_iterator find (const K&) const;
+
+ iterator begin () {return iterator (d->begin ());}
+ iterator end () {return iterator (d->end ());}
+
+ const_iterator begin () const {return const_iterator (d->begin ());}
+ const_iterator end () const {return const_iterator (d->end ());}
+
+ // Implementation details.
+ //
+ public:
+ explicit map_value (D& d): d (&d) {}
+
+ map_value (const map_value&) = delete;
+ map_value& operator= (const map_value&) = delete; // Rebind or deep?
+
+ map_value (map_value&&) = default;
+ map_value& operator= (map_value&&) = delete;
+
+ D* d; // names
+ };
+
+ template <typename K, typename V>
+ struct value_traits<std::map<K, V>>
+ {
+ using type = map_value<K, V, names>;
+ using const_type = map_value<K, V, const names>;
+
+ static type as (value&);
+ static const_type as (const value&);
+
+ template <typename M> static void assign (value&, M);
+ template <typename M> static void append (value&, M);
+
+ static const std::string type_name;
+ static const build2::value_type value_type;
+ };
+
+ template <typename K, typename V, typename D>
+ struct value_traits<map_value<K, V, D>>: value_traits<std::map<K, V>> {};
+}
+
+namespace std
+{
+ template <>
+ struct hash<build2::variable>: hash<string>
+ {
+ size_t
+ operator() (const build2::variable& v) const noexcept
+ {
+ return hash<string>::operator() (v.name);
+ }
+ };
+}
+
+namespace butl
+{
+ template <>
+ struct compare_prefix<build2::variable_cref>: compare_prefix<std::string>
+ {
+ typedef compare_prefix<std::string> base;
+
+ explicit
+ compare_prefix (char d): base (d) {}
+
+ bool
+ operator() (const build2::variable& x, const build2::variable& y) const
+ {
+ return base::operator() (x.name, y.name);
+ }
+
+ bool
+ prefix (const build2::variable& p, const build2::variable& k) const
+ {
+ return base::prefix (p.name, k.name);
+ }
+ };
+}
+
+namespace build2
+{
+ // variable_pool
+ //
+ using variable_pool_base = std::unordered_set<variable>;
+ struct variable_pool: private variable_pool_base
+ {
+ const variable&
+ find (string name, const build2::value_type* t = nullptr, char p = '\0')
+ {
+ return find (name, nullptr, t, p);
+ }
+
+ const variable&
+ find (string name,
+ variable_visibility v,
+ const build2::value_type* t = nullptr,
+ char p = '\0')
+ {
+ return find (name, &v, t, p);
+ }
+
+ using variable_pool_base::clear;
+
+ private:
+ const variable&
+ find (string name,
+ const variable_visibility* vv,
+ const build2::value_type* t,
+ char p)
+ {
+ auto r (
+ insert (
+ variable {
+ std::move (name),
+ t,
+ vv != nullptr ? *vv : variable_visibility::normal,
+ p}));
+ const variable& v (*r.first);
+
+ // Update type?
+ //
+ if (!r.second && t != nullptr && v.type != t)
+ {
+ assert (v.type == nullptr);
+ const_cast<variable&> (v).type = t; // Not changing the key.
+ }
+
+ // Change visibility? While this might at first seem like a bad idea,
+ // it can happen that the variable lookup happens before any values
+ // were set, in which case the variable will be entered with the
+ // default visibility.
+ //
+ if (!r.second && vv != nullptr && v.visibility != *vv)
+ {
+ assert (v.visibility == variable_visibility::normal); // Default.
+ const_cast<variable&> (v).visibility = *vv; // Not changing the key.
+ }
+
+ return v;
+ }
+ };
+
+ extern variable_pool var_pool;
+
+ // variable_map
+ //
+ struct variable_map
+ {
+ using map_type = butl::prefix_map<variable_cref, value, '.'>;
+ using size_type = map_type::size_type;
+
+ template <typename I>
+ struct iterator_adapter: I
+ {
+ iterator_adapter () = default;
+ iterator_adapter (const I& i): I (i) {}
+ typename I::reference operator* () const;
+ typename I::pointer operator-> () const;
+ };
+
+ using const_iterator = iterator_adapter<map_type::const_iterator>;
+
+ const value*
+ find (const variable& var) const
+ {
+ auto i (m_.find (var));
+ const value* r (i != m_.end () ? &i->second : nullptr);
+
+ // First access after being assigned a type?
+ //
+ if (r != nullptr && var.type != nullptr && r->type != var.type)
+ build2::assign (const_cast<value&> (*r), var.type, var);
+
+ return r;
+ }
+
+ value*
+ find (const variable& var)
+ {
+ auto i (m_.find (var));
+ value* r (i != m_.end () ? &i->second : nullptr);
+
+ // First access after being assigned a type?
+ //
+ if (r != nullptr && var.type != nullptr && r->type != var.type)
+ build2::assign (*r, var.type, var);
+
+ return r;
+ }
+
+ lookup<const value>
+ operator[] (const variable& var) const
+ {
+ return lookup<const value> (find (var), this);
+ }
+
+ lookup<const value>
+ operator[] (const std::string& name) const
+ {
+ return operator[] (var_pool.find (name));
+ }
+
+ // Non-const lookup. Only exposed on the map directly.
+ //
+ lookup<value>
+ operator[] (const variable& var)
+ {
+ return lookup<value> (find (var), this);
+ }
+
+ lookup<value>
+ operator[] (const std::string& name)
+ {
+ return operator[] (var_pool.find (name));
+ }
+
+ // The second member in the pair indicates whether the new
+ // value (which will be NULL) was assigned.
+ //
+ std::pair<std::reference_wrapper<value>, bool>
+ assign (const variable& var)
+ {
+ auto r (m_.emplace (var, value (var.type)));
+ value& v (r.first->second);
+
+ // First access after being assigned a type?
+ //
+ if (!r.second && var.type != nullptr && v.type != var.type)
+ build2::assign (v, var.type, var);
+
+ return std::make_pair (std::reference_wrapper<value> (v), r.second);
+ }
+
+ std::pair<std::reference_wrapper<value>, bool>
+ assign (const std::string& name)
+ {
+ return assign (var_pool.find (name));
+ }
+
+ std::pair<const_iterator, const_iterator>
+ find_namespace (const std::string& ns) const
+ {
+ auto r (m_.find_prefix (var_pool.find (ns)));
+ return std::make_pair (const_iterator (r.first),
+ const_iterator (r.second));
+ }
+
+ const_iterator
+ begin () const {return m_.begin ();}
+
+ const_iterator
+ end () const {return m_.end ();}
+
+ bool
+ empty () const {return m_.empty ();}
+
+ size_type
+ size () const {return m_.size ();}
+
+ private:
+ map_type m_;
+ };
+
+ // Target type/pattern-specific variables.
+ //
+ // @@ In quite a few places we assume that we can store a reference
+ // to the returned value (e.g., install::lookup_install()). If
+ // we "instantiate" the value on the fly, then we will need to
+ // consider its lifetime.
+ //
+ using variable_pattern_map = std::map<std::string, variable_map>;
+
+ struct variable_type_map: std::map<std::reference_wrapper<const target_type>,
+ variable_pattern_map>
+ {
+ build2::lookup<const value>
+ lookup (const target_type&, const string& name, const variable&) const;
+ };
+}
+
+#include <build2/variable.ixx>
+#include <build2/variable.txx>
+
+#endif // BUILD2_VARIABLE
diff --git a/build2/variable.cxx b/build2/variable.cxx
new file mode 100644
index 0000000..27555ba
--- /dev/null
+++ b/build2/variable.cxx
@@ -0,0 +1,452 @@
+// file : build2/variable.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/variable>
+
+#include <iterator> // make_move_iterator()
+
+#include <build2/utility>
+#include <build2/diagnostics>
+
+using namespace std;
+
+namespace build2
+{
+ // value
+ //
+ void
+ assign (value& v, const value_type* t, const variable& var)
+ {
+ if (v.type == nullptr)
+ {
+ v.type = t;
+
+ if (v && t->assign != nullptr)
+ v.state_ = t->assign (v.data_, var)
+ ? value::state_type::filled
+ : value::state_type::empty;
+ }
+ else
+ fail << "variable '" << var.name << "' type mismatch" <<
+ info << "value '" << v.data_ << "' is " << v.type->name <<
+ info << (t == var.type ? "variable" : "new type") << " is "
+ << (var.type != nullptr ? var.type->name : "untyped");
+ }
+
+ value& value::
+ operator= (value&& v)
+ {
+ assert (type == nullptr || type == v.type);
+
+ // Since the types are the same, we don't need to call
+ // the callbacks.
+ //
+ type = v.type;
+ state_ = v.state_;
+ data_ = move (v.data_);
+
+ return *this;
+ }
+
+ value& value::
+ append (value v, const variable& var)
+ {
+ assert (type == v.type);
+ append (move (v.data_), var);
+ return *this;
+ }
+
+ value& value::
+ prepend (value v, const variable& var)
+ {
+ assert (type == v.type);
+ prepend (move (v.data_), var);
+ return *this;
+ }
+
+ void value::
+ append (names v, const variable& var)
+ {
+ // Treat append to NULL as assign.
+ //
+ if (!null () && type != nullptr && type->append != nullptr)
+ {
+ state_ = type->append (data_, move (v), var)
+ ? state_type::filled
+ : state_type::empty;
+ return;
+ }
+
+ if (data_.empty ())
+ data_ = move (v);
+ else
+ data_.insert (data_.end (),
+ make_move_iterator (v.begin ()),
+ make_move_iterator (v.end ()));
+
+ state_ = (type != nullptr && type->assign != nullptr
+ ? type->assign (data_, var)
+ : !data_.empty ())
+ ? state_type::filled
+ : state_type::empty;
+ }
+
+ void value::
+ prepend (names v, const variable& var)
+ {
+ // Reduce to append.
+ //
+ if (!null () && type != nullptr && type->append != nullptr)
+ {
+ state_ = type->append (v, move (data_), var)
+ ? state_type::filled
+ : state_type::empty;
+ swap (data_, v);
+ return;
+ }
+
+ if (data_.empty ())
+ data_ = move (v);
+ else
+ {
+ v.insert (v.end (),
+ make_move_iterator (data_.begin ()),
+ make_move_iterator (data_.end ()));
+ swap (data_, v);
+ }
+
+ state_ = (type != nullptr && type->assign != nullptr
+ ? type->assign (data_, var)
+ : !data_.empty ())
+ ? state_type::filled
+ : state_type::empty;
+ }
+
+ // bool value
+ //
+ bool value_traits<bool>::
+ assign (name& n)
+ {
+ if (n.simple ())
+ {
+ const string& s (n.value);
+
+ if (s == "true" || s == "false")
+ return true;
+ }
+
+ return false;
+ }
+
+ static bool
+ bool_assign (names& v, const variable& var)
+ {
+ // Verify the value is either "true" or "false".
+ //
+ if (v.size () == 1)
+ {
+ name& n (v.front ());
+
+ if (value_traits<bool>::assign (n))
+ return true;
+ }
+
+ fail << "invalid bool variable '" << var.name << "' value '" << v << "'";
+ return false;
+ }
+
+ static bool
+ bool_append (names& v, names a, const variable& var)
+ {
+ // Translate append to OR.
+ //
+ bool_assign (a, var); // Verify "true" or "false".
+
+ if (a.front ().value[0] == 't' && v.front ().value[0] == 'f')
+ v = move (a);
+
+ return true;
+ }
+
+ const value_type value_traits<bool>::value_type
+ {
+ "bool",
+ &bool_assign,
+ &bool_append
+ };
+
+ const value_type* bool_type = &value_traits<bool>::value_type;
+
+ // string value
+ //
+ bool value_traits<string>::
+ assign (name& n)
+ {
+ // The below code is quite convoluted because we don't want to
+ // modify the name until we know it good (if it is not, then it
+ // will most likely be printed by the caller in diagnostics).
+
+ // Suspend project qualification.
+ //
+ const string* p (n.proj);
+ n.proj = nullptr;
+
+ // Convert directory to string.
+ //
+ if (n.directory ())
+ {
+ n.value = std::move (n.dir).string (); // Move string out of path.
+
+ // Add / back to the end of the path unless it is already there.
+ // Note that the string cannot be empty (n.directory () would
+ // have been false).
+ //
+ if (!dir_path::traits::is_separator (n.value[n.value.size () - 1]))
+ n.value += '/';
+ }
+
+ if (!n.simple ())
+ {
+ n.proj = p; // Restore.
+ return false;
+ }
+
+ // Convert project qualification to its string representation.
+ //
+ if (p != nullptr)
+ {
+ string s (*p);
+ s += '%';
+ s += n.value;
+ s.swap (n.value);
+ }
+
+ return true;
+ }
+
+ static bool
+ string_assign (names& v, const variable& var)
+ {
+ // Verify/convert the value is/to a single simple name.
+ //
+ if (v.empty ())
+ {
+ v.emplace_back (name ()); // Canonical empty string representation.
+ return false;
+ }
+ else if (v.size () == 1)
+ {
+ name& n (v.front ());
+
+ if (value_traits<string>::assign (n))
+ return !n.value.empty ();
+ }
+
+ fail << "invalid string variable '" << var.name << "' value '" << v << "'";
+ return false;
+ }
+
+ static bool
+ string_append (names& v, names a, const variable& var)
+ {
+ // Translate append to string concatenation.
+ //
+ string_assign (a, var); // Verify/convert value is/to string.
+
+ if (v.front ().value.empty ())
+ v = move (a);
+ else
+ v.front ().value += a.front ().value;
+
+ return !v.front ().value.empty ();
+ }
+
+ const value_type value_traits<string>::value_type
+ {
+ "string",
+ &string_assign,
+ &string_append
+ };
+
+ const value_type* string_type = &value_traits<string>::value_type;
+
+ // dir_path value
+ //
+ bool value_traits<dir_path>::
+ assign (name& n)
+ {
+ if (n.directory ())
+ return true;
+
+ if (n.simple ())
+ {
+ try
+ {
+ n.dir = n.empty () ? dir_path () : dir_path (move (n.value));
+ n.value.clear ();
+ return true;
+ }
+ catch (const invalid_path&) {} // Fall through.
+ }
+
+ return false;
+ }
+
+ static bool
+ dir_path_assign (names& v, const variable& var)
+ {
+ // Verify/convert the value is/to a single directory name.
+ //
+ if (v.empty ())
+ {
+ v.emplace_back (dir_path ()); // Canonical empty path representation.
+ return false;
+ }
+ else if (v.size () == 1)
+ {
+ name& n (v.front ());
+
+ if (value_traits<dir_path>::assign (n))
+ return !n.dir.empty ();
+ }
+
+ fail << "invalid dir_path variable '" << var.name << "' "
+ << "value '" << v << "'";
+ return false;
+ }
+
+ static bool
+ dir_path_append (names& v, names a, const variable& var)
+ {
+ // Translate append to path concatenation.
+ //
+ dir_path_assign (a, var); // Verify/convert value is/to dir_path.
+
+ dir_path& d (a.front ().dir);
+ if (d.relative ())
+ return !(v.front ().dir /= d).empty ();
+ else
+ fail << "append of absolute path '" << d << "' to dir_path variable "
+ << var.name;
+
+ return false;
+ }
+
+ const value_type value_traits<dir_path>::value_type
+ {
+ "dir_path",
+ &dir_path_assign,
+ &dir_path_append
+ };
+
+ const value_type* dir_path_type = &value_traits<dir_path>::value_type;
+
+ // name value
+ //
+ static bool
+ name_assign (names& v, const variable& var)
+ {
+ // Verify the value is a single name.
+ //
+ if (v.size () == 1)
+ return v.front ().empty ();
+
+ fail << "invalid string variable '" << var.name << "' value '" << v << "'";
+ return false;
+ }
+
+ static bool
+ name_append (names&, names, const variable& var)
+ {
+ fail << "append to name variable '" << var.name << "'";
+ return false;
+ }
+
+ const value_type value_traits<name>::value_type
+ {
+ "name",
+ &name_assign,
+ &name_append
+ };
+
+ const value_type* name_type = &value_traits<name>::value_type;
+
+ // vector<T> value
+ //
+ const value_type* strings_type = &value_traits<strings>::value_type;
+ const value_type* dir_paths_type = &value_traits<dir_paths>::value_type;
+ const value_type* names_type = &value_traits<names>::value_type;
+
+ // variable_set
+ //
+ variable_pool var_pool;
+
+ // variable_type_map
+ //
+ lookup<const value> variable_type_map::
+ lookup (const target_type& type,
+ const string& name,
+ const variable& var) const
+ {
+ using result = build2::lookup<const value>;
+
+ // Search across target type hierarchy.
+ //
+ for (auto tt (&type); tt != nullptr; tt = tt->base)
+ {
+ auto i (find (*tt));
+
+ if (i == end ())
+ continue;
+
+ // Try to match the pattern, starting from the longest values
+ // so that the more "specific" patterns (i.e., those that cover
+ // fewer characters with the wildcard) take precedence. See
+ // tests/variable/type-pattern.
+ //
+ const variable_pattern_map& m (i->second);
+
+ for (auto j (m.rbegin ()); j != m.rend (); ++j)
+ {
+ const string& p (j->first);
+
+ size_t nn (name.size ());
+ size_t pn (p.size ());
+
+ if (nn < pn - 1) // One for '*'.
+ continue;
+
+ size_t w (p.find ('*'));
+ assert (w != string::npos);
+
+ // Compare prefix.
+ //
+ if (w != 0 &&
+ name.compare (0, w, p, 0, w) != 0)
+ continue;
+
+ ++w; // First suffix character.
+ pn -= w; // Suffix length.
+
+ // Compare suffix.
+ //
+ if (pn != 0 &&
+ name.compare (nn - pn, pn, p, w, pn) != 0)
+ continue;
+
+ // Ok, this pattern matches. But is there a variable?
+ //
+ if (const value* v = j->second.find (var))
+ {
+ //@@ TODO: should we detect ambiguity? 'foo-*' '*-foo' and
+ // 'foo-foo'? Right now the last defined will be used.
+ //
+ return result (v, &j->second);
+ }
+ }
+ }
+
+ return result ();
+ }
+}
diff --git a/build2/variable.ixx b/build2/variable.ixx
new file mode 100644
index 0000000..2933deb
--- /dev/null
+++ b/build2/variable.ixx
@@ -0,0 +1,398 @@
+// file : build2/variable.ixx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+namespace build2
+{
+ // value
+ //
+ template <typename T>
+ inline void
+ assign (value& v, const variable& var)
+ {
+ auto t (&value_traits<T>::value_type);
+
+ if (v.type != t)
+ assign (v, t, var);
+ }
+
+ template <typename T>
+ inline typename value_traits<T>::type
+ as (value& v)
+ {
+ return value_traits<T>::as (v);
+ }
+
+ template <typename T>
+ inline typename value_traits<T>::const_type
+ as (const value& v)
+ {
+ return value_traits<T>::as (v);
+ }
+
+ template <typename T>
+ inline bool
+ assign (name& n)
+ {
+ return value_traits<T>::assign (n);
+ }
+
+ template <typename T>
+ inline typename value_traits<T>::type
+ as (name& n)
+ {
+ return value_traits<T>::as (n);
+ }
+
+ template <typename T>
+ inline typename value_traits<T>::const_type
+ as (const name& n)
+ {
+ return value_traits<T>::as (n);
+ }
+
+ template <typename T>
+ inline value& value::
+ operator= (T v)
+ {
+ value_traits<T>::assign (*this, std::move (v));
+ return *this;
+ }
+
+ template <typename T>
+ inline value& value::
+ operator+= (T v)
+ {
+ value_traits<T>::append (*this, std::move (v));
+ return *this;
+ }
+
+ inline void value::
+ assign (names v, const variable& var)
+ {
+ data_ = std::move (v);
+ state_ = (type != nullptr && type->assign != nullptr
+ ? type->assign (data_, var)
+ : !data_.empty ())
+ ? state_type::filled
+ : state_type::empty;
+ }
+
+ // bool value
+ //
+ inline bool_value<name> value_traits<bool>::
+ as (value& v)
+ {
+ assert (v.type == bool_type);
+ return bool_value<name> (v.data_.front ());
+ }
+
+ inline bool_value<const name> value_traits<bool>::
+ as (const value& v)
+ {
+ assert (v.type == bool_type);
+ return bool_value<const name> (v.data_.front ());
+ }
+
+ inline void value_traits<bool>::
+ assign (value& v, bool x)
+ {
+ if (v.null ())
+ {
+ if (v.type == nullptr)
+ v.type = bool_type;
+ v.data_.emplace_back (name ());
+ v.state_ = value::state_type::empty;
+ }
+
+ as (v) = x;
+ v.state_ = value::state_type::filled;
+ }
+
+ inline void value_traits<bool>::
+ append (value& v, bool x)
+ {
+ if (v.null ())
+ assign (v, x);
+ else
+ as (v) += x; // Cannot be empty.
+ }
+
+ // string value
+ //
+ inline std::string& value_traits<std::string>::
+ as (value& v)
+ {
+ assert (v.type == string_type);
+ return v.data_.front ().value;
+ }
+
+ inline const std::string& value_traits<std::string>::
+ as (const value& v)
+ {
+ assert (v.type == string_type);
+ return v.data_.front ().value;
+ }
+
+ inline void value_traits<std::string>::
+ assign (value& v, std::string x)
+ {
+ if (v.null ())
+ {
+ if (v.type == nullptr)
+ v.type = string_type;
+ v.data_.emplace_back (name ());
+ v.state_ = value::state_type::empty;
+ }
+
+ v.state_ = (as (v) = std::move (x)).empty ()
+ ? value::state_type::empty
+ : value::state_type::filled;
+ }
+
+ inline void value_traits<std::string>::
+ append (value& v, std::string x)
+ {
+ if (v.null ())
+ assign (v, std::move (x));
+ else
+ v.state_ = (as (v) += std::move (x)).empty ()
+ ? value::state_type::empty
+ : value::state_type::filled;
+ }
+
+ // dir_path value
+ //
+ inline dir_path& value_traits<dir_path>::
+ as (value& v)
+ {
+ assert (v.type == dir_path_type);
+ return v.data_.front ().dir;
+ }
+
+ inline const dir_path& value_traits<dir_path>::
+ as (const value& v)
+ {
+ assert (v.type == dir_path_type);
+ return v.data_.front ().dir;
+ }
+
+ inline void value_traits<dir_path>::
+ assign (value& v, dir_path x)
+ {
+ if (v.null ())
+ {
+ if (v.type == nullptr)
+ v.type = dir_path_type;
+ v.data_.emplace_back (name ());
+ v.state_ = value::state_type::empty;
+ }
+
+ v.state_ = (as (v) = std::move (x)).empty ()
+ ? value::state_type::empty
+ : value::state_type::filled;
+ }
+
+ inline void value_traits<dir_path>::
+ append (value& v, dir_path x)
+ {
+ if (v.null ())
+ assign (v, std::move (x));
+ else
+ v.state_ = (as (v) /= std::move (x)).empty ()
+ ? value::state_type::empty
+ : value::state_type::filled;
+ }
+
+ // name value
+ //
+ inline name& value_traits<name>::
+ as (value& v)
+ {
+ assert (v.type == name_type);
+ return v.data_.front ();
+ }
+
+ inline const name& value_traits<name>::
+ as (const value& v)
+ {
+ assert (v.type == name_type);
+ return v.data_.front ();
+ }
+
+ inline void value_traits<name>::
+ assign (value& v, name x)
+ {
+ if (v.null ())
+ {
+ if (v.type == nullptr)
+ v.type = name_type;
+ v.data_.emplace_back (name ());
+ v.state_ = value::state_type::empty;
+ }
+
+ v.state_ = (as (v) = std::move (x)).empty ()
+ ? value::state_type::empty
+ : value::state_type::filled;
+ }
+
+ // vector<T> value
+ //
+ template <typename T, typename D>
+ inline vector_value<T, D>& vector_value<T, D>::
+ assign (std::vector<T> v)
+ {
+ d->clear ();
+ d->insert (d->end (),
+ std::make_move_iterator (v.begin ()),
+ std::make_move_iterator (v.end ()));
+ return *this;
+ }
+
+ template <typename T, typename D>
+ template <typename D1>
+ inline vector_value<T, D>& vector_value<T, D>::
+ assign (const vector_value<T, D1>& v)
+ {
+ d->clear ();
+ d->insert (d->end (), v.begin (), v.end ());
+ return *this;
+ }
+
+ template <typename T, typename D>
+ template <typename D1>
+ inline vector_value<T, D>& vector_value<T, D>::
+ append (const vector_value<T, D1>& v)
+ {
+ d->insert (d->end (), v.begin (), v.end ());
+ return *this;
+ }
+
+ template <typename T>
+ inline vector_value<T, names> value_traits<std::vector<T>>::
+ as (value& v)
+ {
+ assert (v.type == &value_traits<std::vector<T>>::value_type);
+ return vector_value<T, names> (v.data_);
+ }
+
+ template <typename T>
+ inline vector_value<T, const names> value_traits<std::vector<T>>::
+ as (const value& v)
+ {
+ assert (v.type == &value_traits<std::vector<T>>::value_type);
+ return vector_value<T, const names> (v.data_);
+ }
+
+ template <typename T>
+ template <typename V>
+ inline void value_traits<std::vector<T>>::
+ assign (value& v, V x)
+ {
+ if (v.null ())
+ {
+ if (v.type == nullptr)
+ v.type = &value_traits<std::vector<T>>::value_type;
+ v.state_ = value::state_type::empty;
+ }
+
+ v.state_ = (as (v).assign (std::move (x))).empty ()
+ ? value::state_type::empty
+ : value::state_type::filled;
+ }
+
+ template <typename T>
+ template <typename V>
+ inline void value_traits<std::vector<T>>::
+ append (value& v, V x)
+ {
+ if (v.null ())
+ assign (v, std::move (x));
+ else
+ v.state_ = (as (v).append (std::move (x))).empty ()
+ ? value::state_type::empty
+ : value::state_type::filled;
+ }
+
+ // map<K, V> value
+ //
+ template <typename K, typename V>
+ inline map_value<K, V, names> value_traits<std::map<K, V>>::
+ as (value& v)
+ {
+ assert ((v.type == &value_traits<std::map<K, V>>::value_type));
+ return map_value<K, V, names> (v.data_);
+ }
+
+ template <typename K, typename V>
+ inline map_value<K, V, const names> value_traits<std::map<K, V>>::
+ as (const value& v)
+ {
+ assert ((v.type == &value_traits<std::map<K, V>>::value_type));
+ return map_value<K, V, const names> (v.data_);
+ }
+
+ template <typename K, typename V>
+ template <typename M>
+ inline void value_traits<std::map<K, V>>::
+ assign (value& v, M x)
+ {
+ if (v.null ())
+ {
+ if (v.type == nullptr)
+ v.type = &value_traits<std::map<K, V>>::value_type;
+ v.state_ = value::state_type::empty;
+ }
+
+ v.state_ = (as (v).assign (std::move (x))).empty ()
+ ? value::state_type::empty
+ : value::state_type::filled;
+ }
+
+ template <typename K, typename V>
+ template <typename M>
+ inline void value_traits<std::map<K, V>>::
+ append (value& v, M x)
+ {
+ if (v.null ())
+ assign (v, std::move (x));
+ else
+ v.state_ = (as (v).append (std::move (x))).empty ()
+ ? value::state_type::empty
+ : value::state_type::filled;
+ }
+
+ // variable_map::iterator_adapter
+ //
+ template <typename I>
+ inline typename I::reference variable_map::iterator_adapter<I>::
+ operator* () const
+ {
+ auto& r (I::operator* ());
+ const variable& var (r.first);
+ auto& val (r.second);
+
+ // First access after being assigned a type?
+ //
+ if (var.type != nullptr && val.type != var.type)
+ build2::assign (const_cast<value&> (val), var.type, var);
+
+ return r;
+ }
+
+ template <typename I>
+ inline typename I::pointer variable_map::iterator_adapter<I>::
+ operator-> () const
+ {
+ auto p (I::operator-> ());
+ const variable& var (p->first);
+ auto& val (p->second);
+
+ // First access after being assigned a type?
+ //
+ if (var.type != nullptr && val.type != var.type)
+ build2::assign (const_cast<value&> (val), var.type, var);
+
+ return p;
+ }
+}
diff --git a/build2/variable.txx b/build2/variable.txx
new file mode 100644
index 0000000..907072e
--- /dev/null
+++ b/build2/variable.txx
@@ -0,0 +1,168 @@
+// file : build2/variable.txx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <iterator> // make_move_iterator()
+
+#include <build2/diagnostics>
+
+namespace build2
+{
+ // vector<T> value
+ //
+ template <typename T>
+ bool
+ vector_assign (names& v, const variable& var)
+ {
+ // Verify each element has valid value of T.
+ //
+ for (name& n: v)
+ {
+ if (!assign<T> (n))
+ fail << "invalid " << value_traits<T>::value_type.name << " element "
+ << "'" << n << "' in variable '" << var.name << "'";
+ }
+
+ return !v.empty ();
+ }
+
+ template <typename T>
+ bool
+ vector_append (names& v, names a, const variable& var)
+ {
+ // Verify that what we are appending is valid.
+ //
+ vector_assign<T> (a, var);
+
+ if (v.empty ())
+ v = move (a);
+ else
+ v.insert (v.end (),
+ std::make_move_iterator (a.begin ()),
+ std::make_move_iterator (a.end ()));
+
+ return !v.empty ();
+ }
+
+ template <typename T>
+ const std::string value_traits<std::vector<T>>::type_name = std::string (
+ value_traits<T>::value_type.name) + 's';
+
+ template <typename T>
+ const value_type value_traits<std::vector<T>>::value_type
+ {
+ value_traits<std::vector<T>>::type_name.c_str (),
+ &vector_assign<T>,
+ &vector_append<T>
+ };
+
+ // map<K, V> value
+ //
+ template <typename K, typename V, typename D>
+ map_value<K, V, D>& map_value<K, V, D>::
+ assign (std::map<K, V> m)
+ {
+ d->clear ();
+ for (auto& p: m)
+ {
+ d->emplace_back (p.first); // Const, can't move.
+ d->back ().pair = '=';
+ d->emplace_back (std::move (p.second));
+ }
+
+ return *this;
+ }
+
+ template <typename K, typename V, typename D>
+ auto map_value<K, V, D>::
+ find (const K& k) -> iterator
+ {
+ // @@ Scan backwards to handle duplicates.
+ //
+ for (auto i (d->rbegin ()); i != d->rend (); ++i)
+ if (as<K> (*++i) == k)
+ return iterator (--(i.base ()));
+
+ return end ();
+ }
+
+ template <typename K, typename V, typename D>
+ auto map_value<K, V, D>::
+ find (const K& k) const -> const_iterator
+ {
+ // @@ Scan backwards to handle duplicates.
+ //
+ for (auto i (d->rbegin ()); i != d->rend (); ++i)
+ if (as<K> (*++i) == k)
+ return const_iterator (--(i.base ()));
+
+ return end ();
+ }
+
+ template <typename K, typename V>
+ bool
+ map_assign (names& v, const variable& var)
+ {
+ // Verify we have a sequence of pairs and each lhs/rhs is a valid
+ // value of K/V.
+ //
+ for (auto i (v.begin ()); i != v.end (); ++i)
+ {
+ if (i->pair == '\0')
+ fail << value_traits<std::map<K, V>>::value_type.name << " key-value "
+ << "pair expected instead of '" << *i << "' "
+ << "in variable '" << var.name << "'";
+
+ if (!assign<K> (*i))
+ fail << "invalid " << value_traits<K>::value_type.name << " key "
+ << "'" << *i << "' in variable '" << var.name << "'";
+
+ ++i; // Got to have the second half of the pair.
+
+ if (!assign<V> (*i))
+ fail << "invalid " << value_traits<V>::value_type.name << " value "
+ << "'" << *i << "' in variable '" << var.name << "'";
+ }
+
+ //@@ When doing sorting, note that assign() can convert the
+ // value.
+
+ //@@ Is sorting really the right trade-off (i.e., insertion
+ // vs search)? Perhaps linear search is ok?
+
+ return !v.empty ();
+ }
+
+ template <typename K, typename V>
+ bool
+ map_append (names& v, names a, const variable& var)
+ {
+ //@@ Not weeding out duplicates.
+
+ // Verify that what we are appending is valid.
+ //
+ map_assign<K, V> (a, var);
+
+ if (v.empty ())
+ v = move (a);
+ else
+ v.insert (v.end (),
+ std::make_move_iterator (a.begin ()),
+ std::make_move_iterator (a.end ()));
+
+ return !v.empty ();
+ }
+
+ template <typename K, typename V>
+ const std::string value_traits<std::map<K, V>>::type_name = std::string (
+ value_traits<K>::value_type.name) + '_' +
+ value_traits<V>::value_type.name + "_map";
+
+ template <typename K, typename V>
+ const value_type value_traits<std::map<K, V>>::value_type
+ {
+ value_traits<std::map<K, V>>::type_name.c_str (),
+ &map_assign<K, V>,
+ &map_append<K, V>
+ };
+}
diff --git a/build2/version b/build2/version
new file mode 100644
index 0000000..3339078
--- /dev/null
+++ b/build2/version
@@ -0,0 +1,37 @@
+// file : build2/version -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_VERSION // Note: using the version macro itself.
+
+#include <butl/version> // LIBBUTL_VERSION
+
+// Version format is AABBCCDD where
+//
+// AA - major version number
+// BB - minor version number
+// CC - bugfix version number
+// DD - alpha / beta (DD + 50) version number
+//
+// When DD is not 00, 1 is subtracted from AABBCC. For example:
+//
+// Version AABBCCDD
+// 2.0.0 02000000
+// 2.1.0 02010000
+// 2.1.1 02010100
+// 2.2.0-a1 02019901
+// 3.0.0-b2 02999952
+//
+#define BUILD2_VERSION 10000
+#define BUILD2_VERSION_STR "0.1.0"
+
+// Generally, we expect minor versions to be source code backwards-
+// compatible, thought we might have a minimum version requirement.
+//
+// Note: does not apply during early development.
+//
+#if LIBBUTL_VERSION != 10000
+# error incompatible libbutl version
+#endif
+
+#endif // BUILD2_VERSION