aboutsummaryrefslogtreecommitdiff
path: root/libbuild2
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2019-06-24 12:01:19 +0200
committerKaren Arutyunov <karen@codesynthesis.com>2019-07-01 18:13:55 +0300
commit977d07a3ae47ef204665d1eda2d642e5064724f3 (patch)
tree525a3d6421f61ce789b690191d3c30fc09be3517 /libbuild2
parent7161b24963dd9da4d218f92c736b77c35c328a2d (diff)
Split build system into library and driver
Diffstat (limited to 'libbuild2')
-rw-r--r--libbuild2/.gitignore5
-rw-r--r--libbuild2/action.hxx202
-rw-r--r--libbuild2/algorithm.cxx2205
-rw-r--r--libbuild2/algorithm.hxx778
-rw-r--r--libbuild2/algorithm.ixx764
-rw-r--r--libbuild2/buildfile85
-rw-r--r--libbuild2/config.hxx0
-rw-r--r--libbuild2/config.hxx.in37
-rw-r--r--libbuild2/context.cxx1026
-rw-r--r--libbuild2/context.hxx572
-rw-r--r--libbuild2/context.ixx60
-rw-r--r--libbuild2/depdb.cxx399
-rw-r--r--libbuild2/depdb.hxx288
-rw-r--r--libbuild2/depdb.ixx45
-rw-r--r--libbuild2/diagnostics.cxx138
-rw-r--r--libbuild2/diagnostics.hxx436
-rw-r--r--libbuild2/dump.cxx491
-rw-r--r--libbuild2/dump.hxx34
-rw-r--r--libbuild2/export.hxx58
-rw-r--r--libbuild2/file.cxx1660
-rw-r--r--libbuild2/file.hxx243
-rw-r--r--libbuild2/file.ixx31
-rw-r--r--libbuild2/filesystem.cxx274
-rw-r--r--libbuild2/filesystem.hxx182
-rw-r--r--libbuild2/filesystem.txx111
-rw-r--r--libbuild2/function+call.test.testscript161
-rw-r--r--libbuild2/function+syntax.test.testscript29
-rw-r--r--libbuild2/function.cxx400
-rw-r--r--libbuild2/function.hxx905
-rw-r--r--libbuild2/function.test.cxx134
-rw-r--r--libbuild2/functions-builtin.cxx56
-rw-r--r--libbuild2/functions-filesystem.cxx220
-rw-r--r--libbuild2/functions-name.cxx109
-rw-r--r--libbuild2/functions-path.cxx361
-rw-r--r--libbuild2/functions-process-path.cxx25
-rw-r--r--libbuild2/functions-process.cxx253
-rw-r--r--libbuild2/functions-project-name.cxx63
-rw-r--r--libbuild2/functions-regex.cxx542
-rw-r--r--libbuild2/functions-string.cxx43
-rw-r--r--libbuild2/functions-target-triplet.cxx36
-rw-r--r--libbuild2/lexer+buildspec.test.testscript16
-rw-r--r--libbuild2/lexer+comment.test.testscript139
-rw-r--r--libbuild2/lexer+eval.test.testscript76
-rw-r--r--libbuild2/lexer+quoting.test.testscript108
-rw-r--r--libbuild2/lexer.cxx720
-rw-r--r--libbuild2/lexer.hxx207
-rw-r--r--libbuild2/lexer.test.cxx98
-rw-r--r--libbuild2/module.cxx147
-rw-r--r--libbuild2/module.hxx120
-rw-r--r--libbuild2/name.cxx187
-rw-r--r--libbuild2/name.hxx172
-rw-r--r--libbuild2/name.ixx40
-rw-r--r--libbuild2/name.test.cxx96
-rw-r--r--libbuild2/operation.cxx617
-rw-r--r--libbuild2/operation.hxx361
-rw-r--r--libbuild2/parser.cxx5526
-rw-r--r--libbuild2/parser.hxx673
-rw-r--r--libbuild2/prerequisite.cxx120
-rw-r--r--libbuild2/prerequisite.hxx229
-rw-r--r--libbuild2/prerequisite.ixx34
-rw-r--r--libbuild2/rule-map.hxx123
-rw-r--r--libbuild2/rule.cxx309
-rw-r--r--libbuild2/rule.hxx107
-rw-r--r--libbuild2/scheduler.cxx820
-rw-r--r--libbuild2/scheduler.hxx709
-rw-r--r--libbuild2/scheduler.test.cxx187
-rw-r--r--libbuild2/scheduler.txx138
-rw-r--r--libbuild2/scope.cxx911
-rw-r--r--libbuild2/scope.hxx471
-rw-r--r--libbuild2/scope.ixx54
-rw-r--r--libbuild2/search.cxx244
-rw-r--r--libbuild2/search.hxx41
-rw-r--r--libbuild2/spec.cxx111
-rw-r--r--libbuild2/spec.hxx72
-rw-r--r--libbuild2/target-key.hxx106
-rw-r--r--libbuild2/target-state.hxx46
-rw-r--r--libbuild2/target-type.hxx208
-rw-r--r--libbuild2/target.cxx1260
-rw-r--r--libbuild2/target.hxx1817
-rw-r--r--libbuild2/target.ixx496
-rw-r--r--libbuild2/target.txx185
-rw-r--r--libbuild2/token.cxx60
-rw-r--r--libbuild2/token.hxx191
-rw-r--r--libbuild2/types.hxx360
-rw-r--r--libbuild2/utility.cxx517
-rw-r--r--libbuild2/utility.hxx671
-rw-r--r--libbuild2/utility.ixx155
-rw-r--r--libbuild2/utility.txx115
-rw-r--r--libbuild2/variable.cxx1533
-rw-r--r--libbuild2/variable.hxx1596
-rw-r--r--libbuild2/variable.ixx812
-rw-r--r--libbuild2/variable.txx670
-rw-r--r--libbuild2/version.hxx0
-rw-r--r--libbuild2/version.hxx.in46
94 files changed, 38288 insertions, 0 deletions
diff --git a/libbuild2/.gitignore b/libbuild2/.gitignore
new file mode 100644
index 0000000..292d2f8
--- /dev/null
+++ b/libbuild2/.gitignore
@@ -0,0 +1,5 @@
+# Unit test executables and Testscript output directories
+# (can be symlinks).
+#
+*.test
+test-*.test
diff --git a/libbuild2/action.hxx b/libbuild2/action.hxx
new file mode 100644
index 0000000..9fa2a16
--- /dev/null
+++ b/libbuild2/action.hxx
@@ -0,0 +1,202 @@
+// file : libbuild2/action.hxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef LIBBUILD2_ACTION_HXX
+#define LIBBUILD2_ACTION_HXX
+
+#include <libbuild2/types.hxx>
+#include <libbuild2/utility.hxx>
+
+#include <libbuild2/export.hxx>
+
+namespace build2
+{
+ // 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 = uint8_t;
+ using operation_id = uint8_t;
+ using action_id = 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".
+ //
+ // This inner/outer operation support is implemented by maintaining two
+ // independent "target states" (see target::state; initially we tried to do
+ // it via rule/recipe override but that didn't end up well, to put it
+ // mildly). While the outer operation normally "directs" the inner, inner
+ // rules can still be matched/executed directly, without outer's involvement
+ // (e.g., because of other inner rules). A typical implementation of an
+ // outer rule either returns noop or delegates to the inner rule. In
+ // particular, it should not replace or override the inner's logic.
+ //
+ // While most of the relevant target state is duplicated, certain things are
+ // shared among the inner/outer rules, such as the target data pad and the
+ // group state. In particular, it is assumed the group state is always
+ // determined by the inner rule (see resolve_members()).
+ //
+ // Normally, an outer rule will be responsible for any additional, outer
+ // operation-specific work. Sometimes, however, the inner rule needs to
+ // customize its behavior. In this case the outer and inner rules must
+ // communicate this explicitly (normally via the target's data pad) and
+ // there is a number of restrictions to this approach. See
+ // cc::{link,install}_rule for details.
+ //
+ struct action
+ {
+ action (): inner_id (0), outer_id (0) {} // Invalid action.
+
+ // 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;}
+
+ bool inner () const {return outer_id == 0;}
+ bool outer () const {return outer_id != 0;}
+
+ action
+ inner_action () const
+ {
+ return action (meta_operation (), operation ());
+ }
+
+ // Implicit conversion operator to action_id for the switch() statement,
+ // etc. Most places only care about the inner operation.
+ //
+ operator action_id () const {return inner_id;}
+
+ action_id inner_id;
+ action_id outer_id;
+ };
+
+ inline bool
+ operator== (action x, action y)
+ {
+ return x.inner_id == y.inner_id && x.outer_id == y.outer_id;
+ }
+
+ inline bool
+ operator!= (action x, action y) {return !(x == y);}
+
+ bool operator> (action, action) = delete;
+ bool operator< (action, action) = delete;
+ bool operator>= (action, action) = delete;
+ bool operator<= (action, action) = delete;
+
+ LIBBUILD2_SYMEXPORT ostream&
+ operator<< (ostream&, action); // operation.cxx
+
+ // Inner/outer operation state container.
+ //
+ template <typename T>
+ struct action_state
+ {
+ T data[2]; // [0] -- inner, [1] -- outer.
+
+ T& operator[] (action a) {return data[a.inner () ? 0 : 1];}
+ const T& operator[] (action a) const {return data[a.inner () ? 0 : 1];}
+ };
+
+ // Id constants for build-in and pre-defined meta/operations.
+ //
+ const meta_operation_id noop_id = 1; // nomop?
+ const meta_operation_id perform_id = 2;
+ const meta_operation_id configure_id = 3;
+ const meta_operation_id disfigure_id = 4;
+ const meta_operation_id create_id = 5;
+ const meta_operation_id dist_id = 6;
+ const meta_operation_id info_id = 7;
+
+ // The default operation is a special marker that can be used to indicate
+ // that no operation was explicitly specified by the user. If adding
+ // something here remember to update the man page.
+ //
+ const operation_id default_id = 1; // Shall be first.
+ const operation_id update_id = 2; // Shall be second.
+ const operation_id clean_id = 3;
+
+ const operation_id test_id = 4;
+ const operation_id update_for_test_id = 5; // update(for test) alias.
+
+ const operation_id install_id = 6;
+ const operation_id uninstall_id = 7;
+ const operation_id update_for_install_id = 8; // update(for install) alias.
+
+ 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 perform_uninstall_id = (perform_id << 4) | uninstall_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};
+}
+
+#endif // LIBBUILD2_ACTION_HXX
diff --git a/libbuild2/algorithm.cxx b/libbuild2/algorithm.cxx
new file mode 100644
index 0000000..963714b
--- /dev/null
+++ b/libbuild2/algorithm.cxx
@@ -0,0 +1,2205 @@
+// file : libbuild2/algorithm.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <libbuild2/algorithm.hxx>
+
+#include <libbuild2/scope.hxx>
+#include <libbuild2/target.hxx>
+#include <libbuild2/rule.hxx>
+#include <libbuild2/file.hxx> // import()
+#include <libbuild2/search.hxx>
+#include <libbuild2/context.hxx>
+#include <libbuild2/filesystem.hxx>
+#include <libbuild2/diagnostics.hxx>
+#include <libbuild2/prerequisite.hxx>
+
+using namespace std;
+using namespace butl;
+
+namespace build2
+{
+ const target&
+ search (const target& t, const prerequisite_key& pk)
+ {
+ assert (phase == run_phase::match);
+
+ // If this is a project-qualified prerequisite, then this is import's
+ // business.
+ //
+ if (pk.proj)
+ return import (pk);
+
+ if (const target* pt = pk.tk.type->search (t, pk))
+ return *pt;
+
+ return create_new_target (pk);
+ }
+
+ const target*
+ search_existing (const prerequisite_key& pk)
+ {
+ assert (phase == run_phase::match || phase == run_phase::execute);
+
+ return pk.proj ? import_existing (pk) : search_existing_target (pk);
+ }
+
+ const target&
+ search (const target& t, name n, const scope& s)
+ {
+ assert (phase == run_phase::match);
+
+ auto rp (s.find_target_type (n, location ()));
+ const target_type* tt (rp.first);
+ optional<string>& ext (rp.second);
+
+ if (tt == nullptr)
+ fail << "unknown target type " << n.type << " in name " << n;
+
+ if (!n.dir.empty ())
+ n.dir.normalize (false, true); // Current dir collapses to an empty one.
+
+ // @@ OUT: for now we assume the prerequisite's out is undetermined.
+ // Would need to pass a pair of names.
+ //
+ return search (t,
+ *tt,
+ n.dir,
+ dir_path (),
+ n.value,
+ ext ? &*ext : nullptr,
+ &s,
+ n.proj);
+ }
+
+ const target*
+ search_existing (const name& cn, const scope& s, const dir_path& out)
+ {
+ assert (phase == run_phase::match || phase == run_phase::execute);
+
+ name n (cn);
+ auto rp (s.find_target_type (n, location ()));
+ const target_type* tt (rp.first);
+ optional<string>& ext (rp.second);
+
+ // For now we treat an unknown target type as an unknown target. Seems
+ // logical.
+ //
+ if (tt == nullptr)
+ return nullptr;
+
+ if (!n.dir.empty ())
+ n.dir.normalize (false, true); // Current dir collapses to an empty one.
+
+ bool q (cn.qualified ());
+
+ // @@ OUT: for now we assume the prerequisite's out is undetermined.
+ // Would need to pass a pair of names.
+ //
+ prerequisite_key pk {
+ n.proj, {tt, &n.dir, q ? &empty_dir_path : &out, &n.value, ext}, &s};
+
+ return q ? import_existing (pk) : search_existing_target (pk);
+ }
+
+ // target_lock
+ //
+ static
+#ifdef __cpp_thread_local
+ thread_local
+#else
+ __thread
+#endif
+ const target_lock* target_lock_stack = nullptr;
+
+ const target_lock* target_lock::
+ stack () noexcept
+ {
+ return target_lock_stack;
+ }
+
+ const target_lock* target_lock::
+ stack (const target_lock* s) noexcept
+ {
+ const target_lock* r (target_lock_stack);
+ target_lock_stack = s;
+ return r;
+ }
+
+ // If the work_queue is absent, then we don't wait.
+ //
+ target_lock
+ lock_impl (action a, const target& ct, optional<scheduler::work_queue> wq)
+ {
+ assert (phase == run_phase::match);
+
+ // Most likely the target's state is (count_touched - 1), that is, 0 or
+ // previously executed, so let's start with that.
+ //
+ size_t b (target::count_base ());
+ size_t e (b + target::offset_touched - 1);
+
+ size_t appl (b + target::offset_applied);
+ size_t busy (b + target::offset_busy);
+
+ atomic_count& task_count (ct[a].task_count);
+
+ while (!task_count.compare_exchange_strong (
+ e,
+ busy,
+ memory_order_acq_rel, // Synchronize on success.
+ memory_order_acquire)) // Synchronize on failure.
+ {
+ // Wait for the count to drop below busy if someone is already working
+ // on this target.
+ //
+ if (e >= busy)
+ {
+ // Check for dependency cycles. The cycle members should be evident
+ // from the "while ..." info lines that will follow.
+ //
+ if (dependency_cycle (a, ct))
+ fail << "dependency cycle detected involving target " << ct;
+
+ if (!wq)
+ return target_lock {a, nullptr, e - b};
+
+ // We also unlock the phase for the duration of the wait. Why?
+ // Consider this scenario: we are trying to match a dir{} target whose
+ // buildfile still needs to be loaded. Let's say someone else started
+ // the match before us. So we wait for their completion and they wait
+ // to switch the phase to load. Which would result in a deadlock
+ // unless we release the phase.
+ //
+ phase_unlock ul;
+ e = sched.wait (busy - 1, task_count, *wq);
+ }
+
+ // We don't lock already applied or executed targets.
+ //
+ if (e >= appl)
+ return target_lock {a, nullptr, e - b};
+ }
+
+ // We now have the lock. Analyze the old value and decide what to do.
+ //
+ target& t (const_cast<target&> (ct));
+ target::opstate& s (t[a]);
+
+ size_t offset;
+ if (e <= b)
+ {
+ // First lock for this operation.
+ //
+ s.rule = nullptr;
+ s.dependents.store (0, memory_order_release);
+
+ offset = target::offset_touched;
+ }
+ else
+ {
+ offset = e - b;
+ assert (offset == target::offset_touched ||
+ offset == target::offset_tried ||
+ offset == target::offset_matched);
+ }
+
+ return target_lock {a, &t, offset};
+ }
+
+ void
+ unlock_impl (action a, target& t, size_t offset)
+ {
+ assert (phase == run_phase::match);
+
+ atomic_count& task_count (t[a].task_count);
+
+ // Set the task count and wake up any threads that might be waiting for
+ // this target.
+ //
+ task_count.store (offset + target::count_base (), memory_order_release);
+ sched.resume (task_count);
+ }
+
+ target&
+ add_adhoc_member (target& t,
+ const target_type& tt,
+ const dir_path& dir,
+ const dir_path& out,
+ string n)
+ {
+ tracer trace ("add_adhoc_member");
+
+ const_ptr<target>* mp (&t.member);
+ for (; *mp != nullptr && !(*mp)->is_a (tt); mp = &(*mp)->member) ;
+
+ target& m (*mp != nullptr // Might already be there.
+ ? **mp
+ : targets.insert (tt,
+ dir,
+ out,
+ move (n),
+ nullopt /* ext */,
+ true /* implied */,
+ trace).first);
+ if (*mp == nullptr)
+ {
+ *mp = &m;
+ m.group = &t;
+ }
+
+ return m;
+ };
+
+ // Return the matching rule or NULL if no match and try_match is true.
+ //
+ const rule_match*
+ match_impl (action a, target& t, const rule* skip, bool try_match)
+ {
+ // If this is an outer operation (Y-for-X), then we look for rules
+ // registered for the outer id (X). Note that we still pass the original
+ // action to the rule's match() function so that it can distinguish
+ // between a pre/post operation (Y-for-X) and the actual operation (X).
+ //
+ meta_operation_id mo (a.meta_operation ());
+ operation_id o (a.inner () ? a.operation () : a.outer_operation ());
+
+ const 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[mo]);
+
+ 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 (operation_id 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_sub (hint));
+
+ for (auto i (rs.first); i != rs.second; ++i)
+ {
+ const auto& r (*i);
+ const string& n (r.first);
+ const rule& ru (r.second);
+
+ if (&ru == skip)
+ continue;
+
+ {
+ auto df = make_diag_frame (
+ [a, &t, &n](const diag_record& dr)
+ {
+ if (verb != 0)
+ dr << info << "while matching rule " << n << " to "
+ << diag_do (a, t);
+ });
+
+ if (!ru.match (a, t, hint))
+ continue;
+ }
+
+ // 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 df = make_diag_frame (
+ [a, &t, &n1](const diag_record& dr)
+ {
+ if (verb != 0)
+ dr << info << "while matching rule " << n1 << " to "
+ << diag_do (a, t);
+ });
+
+ // @@ TODO: this makes target state in match() undetermined
+ // so need to fortify rules that modify anything in match
+ // to clear things.
+ //
+ // @@ Can't we temporarily swap things out in target?
+ //
+ if (!ru1.match (a, t, hint))
+ continue;
+ }
+
+ if (!ambig)
+ {
+ dr << fail << "multiple rules matching " << diag_doing (a, t)
+ << info << "rule " << n << " matches";
+ ambig = true;
+ }
+
+ dr << info << "rule " << n1 << " also matches";
+ }
+
+ if (!ambig)
+ return &r;
+ else
+ dr << info << "use rule hint to disambiguate this match";
+ }
+ }
+ }
+ }
+
+ if (!try_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 nullptr;
+ }
+
+ recipe
+ apply_impl (action a,
+ target& t,
+ const pair<const string, reference_wrapper<const rule>>& r)
+ {
+ auto df = make_diag_frame (
+ [a, &t, &r](const diag_record& dr)
+ {
+ if (verb != 0)
+ dr << info << "while applying rule " << r.first << " to "
+ << diag_do (a, t);
+ });
+
+ return r.second.get ().apply (a, t);
+ }
+
+ // If step is true then perform only one step of the match/apply sequence.
+ //
+ // If try_match is true, then indicate whether there is a rule match with
+ // the first half of the result.
+ //
+ static pair<bool, target_state>
+ match_impl (target_lock& l,
+ bool step = false,
+ bool try_match = false)
+ {
+ assert (l.target != nullptr);
+
+ action a (l.action);
+ target& t (*l.target);
+ target::opstate& s (t[a]);
+
+ // Intercept and handle matching an ad hoc group member.
+ //
+ if (t.adhoc_member ())
+ {
+ assert (!step);
+
+ const target& g (*t.group);
+
+ // It feels natural to "convert" this call to the one for the group,
+ // including the try_match part. Semantically, we want to achieve the
+ // following:
+ //
+ // [try_]match (a, g);
+ // match_recipe (l, group_recipe);
+ //
+ auto df = make_diag_frame (
+ [a, &t](const diag_record& dr)
+ {
+ if (verb != 0)
+ dr << info << "while matching group rule to " << diag_do (a, t);
+ });
+
+ pair<bool, target_state> r (match (a, g, 0, nullptr, try_match));
+
+ if (r.first)
+ {
+ if (r.second != target_state::failed)
+ {
+ match_inc_dependens (a, g);
+ match_recipe (l, group_recipe);
+ }
+ }
+ else
+ l.offset = target::offset_tried;
+
+ return r; // Group state.
+ }
+
+ try
+ {
+ // Continue from where the target has been left off.
+ //
+ switch (l.offset)
+ {
+ case target::offset_tried:
+ {
+ if (try_match)
+ return make_pair (false, target_state::unknown);
+
+ // To issue diagnostics ...
+ }
+ // Fall through.
+ case target::offset_touched:
+ {
+ // Match.
+ //
+
+ // Clear the rule-specific variables, resolved targets list, and the
+ // data pad before calling match(). The rule is free to modify these
+ // in its match() (provided that it matches) in order to, for
+ // example, convey some information to apply().
+ //
+ s.vars.clear ();
+ t.prerequisite_targets[a].clear ();
+ if (a.inner ()) t.clear_data ();
+
+ const rule_match* r (match_impl (a, t, nullptr, try_match));
+
+ assert (l.offset != target::offset_tried); // Should have failed.
+
+ if (r == nullptr) // Not found (try_match == true).
+ {
+ l.offset = target::offset_tried;
+ return make_pair (false, target_state::unknown);
+ }
+
+ s.rule = r;
+ l.offset = target::offset_matched;
+
+ if (step)
+ // Note: s.state is still undetermined.
+ return make_pair (true, target_state::unknown);
+
+ // Otherwise ...
+ }
+ // Fall through.
+ case target::offset_matched:
+ {
+ // Apply.
+ //
+ set_recipe (l, apply_impl (a, t, *s.rule));
+ l.offset = target::offset_applied;
+ break;
+ }
+ default:
+ assert (false);
+ }
+ }
+ catch (const failed&)
+ {
+ // As a sanity measure clear the target data since it can be incomplete
+ // or invalid (mark()/unmark() should give you some ideas).
+ //
+ s.vars.clear ();
+ t.prerequisite_targets[a].clear ();
+ if (a.inner ()) t.clear_data ();
+
+ s.state = target_state::failed;
+ l.offset = target::offset_applied;
+ }
+
+ return make_pair (true, s.state);
+ }
+
+ // If try_match is true, then indicate whether there is a rule match with
+ // the first half of the result.
+ //
+ pair<bool, target_state>
+ match (action a,
+ const target& ct,
+ size_t start_count,
+ atomic_count* task_count,
+ bool try_match)
+ {
+ // If we are blocking then work our own queue one task at a time. The
+ // logic here is that we may have already queued other tasks before this
+ // one and there is nothing bad (except a potentially deep stack trace)
+ // about working through them while we wait. On the other hand, we want
+ // to continue as soon as the lock is available in order not to nest
+ // things unnecessarily.
+ //
+ // That's what we used to do but that proved to be too deadlock-prone. For
+ // example, we may end up popping the last task which needs a lock that we
+ // are already holding. A fuzzy feeling is that we need to look for tasks
+ // (compare their task_counts?) that we can safely work on (though we will
+ // need to watch out for indirections). So perhaps it's just better to keep
+ // it simple and create a few extra threads.
+ //
+ target_lock l (
+ lock_impl (a,
+ ct,
+ task_count == nullptr
+ ? optional<scheduler::work_queue> (scheduler::work_none)
+ : nullopt));
+
+ if (l.target != nullptr)
+ {
+ assert (l.offset < target::offset_applied); // Shouldn't lock otherwise.
+
+ if (try_match && l.offset == target::offset_tried)
+ return make_pair (false, target_state::unknown);
+
+ if (task_count == nullptr)
+ return match_impl (l, false /* step */, try_match);
+
+ // Pass "disassembled" lock since the scheduler queue doesn't support
+ // task destruction.
+ //
+ target_lock::data ld (l.release ());
+
+ // Also pass our diagnostics and lock stacks (this is safe since we
+ // expect the caller to wait for completion before unwinding its stack).
+ //
+ if (sched.async (start_count,
+ *task_count,
+ [a, try_match] (const diag_frame* ds,
+ const target_lock* ls,
+ target& t, size_t offset)
+ {
+ // Switch to caller's diag and lock stacks.
+ //
+ diag_frame::stack_guard dsg (ds);
+ target_lock::stack_guard lsg (ls);
+
+ try
+ {
+ phase_lock pl (run_phase::match); // Can throw.
+ {
+ target_lock l {a, &t, offset}; // Reassemble.
+ match_impl (l, false /* step */, try_match);
+ // Unlock within the match phase.
+ }
+ }
+ catch (const failed&) {} // Phase lock failure.
+ },
+ diag_frame::stack (),
+ target_lock::stack (),
+ ref (*ld.target),
+ ld.offset))
+ return make_pair (true, target_state::postponed); // Queued.
+
+ // Matched synchronously, fall through.
+ }
+ else
+ {
+ // Already applied, executed, or busy.
+ //
+ if (l.offset >= target::offset_busy)
+ return make_pair (true, target_state::busy);
+
+ // Fall through.
+ }
+
+ return ct.try_matched_state (a, false);
+ }
+
+ group_view
+ resolve_members_impl (action a, const target& g, target_lock l)
+ {
+ // Note that we will be unlocked if the target is already applied.
+ //
+ group_view r;
+
+ // Continue from where the target has been left off.
+ //
+ switch (l.offset)
+ {
+ case target::offset_touched:
+ case target::offset_tried:
+ {
+ // Match (locked).
+ //
+ if (match_impl (l, true).second == target_state::failed)
+ throw failed ();
+
+ if ((r = g.group_members (a)).members != nullptr)
+ break;
+
+ // To apply ...
+ }
+ // Fall through.
+ case target::offset_matched:
+ {
+ // @@ Doing match without execute messes up our target_count. Does
+ // not seem like it will be easy to fix (we don't know whether
+ // someone else will execute this target).
+ //
+ // @@ What if we always do match & execute together? After all,
+ // if a group can be resolved in apply(), then it can be
+ // resolved in match()!
+ //
+
+ // Apply (locked).
+ //
+ if (match_impl (l, true).second == target_state::failed)
+ throw failed ();
+
+ if ((r = g.group_members (a)).members != nullptr)
+ break;
+
+ // Unlock and to execute ...
+ //
+ l.unlock ();
+ }
+ // Fall through.
+ case target::offset_applied:
+ {
+ // Execute (unlocked).
+ //
+ // 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.
+ //
+ {
+ phase_switch ps (run_phase::execute);
+ execute_direct (a, g);
+ }
+
+ r = g.group_members (a);
+ break;
+ }
+ }
+
+ return r;
+ }
+
+ void
+ resolve_group_impl (action, const target&, target_lock l)
+ {
+ match_impl (l, true /* step */, true /* try_match */);
+ }
+
+ template <typename R, typename S>
+ static void
+ match_prerequisite_range (action a, target& t,
+ R&& r,
+ const S& ms,
+ const scope* s)
+ {
+ auto& pts (t.prerequisite_targets[a]);
+
+ // Start asynchronous matching of prerequisites. Wait with unlocked phase
+ // to allow phase switching.
+ //
+ wait_guard wg (target::count_busy (), t[a].task_count, true);
+
+ size_t i (pts.size ()); // Index of the first to be added.
+ for (auto&& p: forward<R> (r))
+ {
+ // Ignore excluded.
+ //
+ include_type pi (include (a, t, p));
+
+ if (!pi)
+ continue;
+
+ prerequisite_target pt (ms
+ ? ms (a, t, p, pi)
+ : prerequisite_target (&search (t, p), pi));
+
+ if (pt.target == nullptr || (s != nullptr && !pt.target->in (*s)))
+ continue;
+
+ match_async (a, *pt.target, target::count_busy (), t[a].task_count);
+ pts.push_back (move (pt));
+ }
+
+ wg.wait ();
+
+ // Finish matching all the targets that we have started.
+ //
+ for (size_t n (pts.size ()); i != n; ++i)
+ {
+ const target& pt (*pts[i]);
+ match (a, pt);
+ }
+ }
+
+ void
+ match_prerequisites (action a, target& t,
+ const match_search& ms,
+ const scope* s)
+ {
+ match_prerequisite_range (a, t, group_prerequisites (t), ms, s);
+ }
+
+ void
+ match_prerequisite_members (action a, target& t,
+ const match_search_member& msm,
+ const scope* s)
+ {
+ match_prerequisite_range (a, t, group_prerequisite_members (a, t), msm, s);
+ }
+
+ template <typename T>
+ void
+ match_members (action a, target& t, T const* ts, size_t n)
+ {
+ // Pretty much identical to match_prerequisite_range() except we don't
+ // search.
+ //
+ wait_guard wg (target::count_busy (), t[a].task_count, true);
+
+ for (size_t i (0); i != n; ++i)
+ {
+ const target* m (ts[i]);
+
+ if (m == nullptr || marked (m))
+ continue;
+
+ match_async (a, *m, target::count_busy (), t[a].task_count);
+ }
+
+ wg.wait ();
+
+ // Finish matching all the targets that we have started.
+ //
+ for (size_t i (0); i != n; ++i)
+ {
+ const target* m (ts[i]);
+
+ if (m == nullptr || marked (m))
+ continue;
+
+ match (a, *m);
+ }
+ }
+
+ // Instantiate only for what we need.
+ //
+ template LIBBUILD2_SYMEXPORT void
+ match_members<const target*> (action, target&,
+ const target* const*, size_t);
+
+ template LIBBUILD2_SYMEXPORT void
+ match_members<prerequisite_target> (action, target&,
+ prerequisite_target const*, size_t);
+
+ const fsdir*
+ inject_fsdir (action a, target& t, bool parent)
+ {
+ tracer trace ("inject_fsdir");
+
+ // 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 (parent && t.name.empty () ? t.dir.directory () : t.dir);
+
+ const scope& bs (scopes.find (d));
+ const scope* rs (bs.root_scope ());
+
+ // If root scope is NULL, then this can mean that we are out of any
+ // project or if the directory is in src_root. In both cases we don't
+ // inject anything unless explicitly requested.
+ //
+ // Note that we also used to bail out if this is the root of the
+ // project. But that proved not to be such a great idea in case of
+ // subprojects (e.g., tests/).
+ //
+ const fsdir* r (nullptr);
+ if (rs != nullptr && !d.sub (rs->src_path ()))
+ {
+ l6 ([&]{trace << d << " for " << t;});
+
+ // Target is in the out tree, so out directory is empty.
+ //
+ r = &search<fsdir> (t, d, dir_path (), string (), nullptr, nullptr);
+ }
+ else
+ {
+ // See if one was mentioned explicitly.
+ //
+ for (const prerequisite& p: group_prerequisites (t))
+ {
+ if (p.is_a<fsdir> ())
+ {
+ const target& pt (search (t, p));
+
+ if (pt.dir == d)
+ {
+ r = &pt.as<fsdir> ();
+ break;
+ }
+ }
+ }
+ }
+
+ if (r != nullptr)
+ {
+ match (a, *r);
+ t.prerequisite_targets[a].emplace_back (r);
+ }
+
+ return r;
+ }
+
+ // Execute the specified recipe (if any) and the scope operation callbacks
+ // (if any/applicable) then merge and return the resulting target state.
+ //
+ static target_state
+ execute_recipe (action a, target& t, const recipe& r)
+ {
+ target_state ts (target_state::unknown);
+
+ try
+ {
+ auto df = make_diag_frame (
+ [a, &t](const diag_record& dr)
+ {
+ if (verb != 0)
+ dr << info << "while " << diag_doing (a, t);
+ });
+
+ // If this is a dir{} target, see if we have any operation callbacks
+ // in the corresponding scope.
+ //
+ const dir* op_t (t.is_a<dir> ());
+ const scope* op_s (nullptr);
+
+ using op_iterator = scope::operation_callback_map::const_iterator;
+ pair<op_iterator, op_iterator> op_p;
+
+ if (op_t != nullptr)
+ {
+ op_s = &scopes.find (t.dir);
+
+ if (op_s->out_path () == t.dir && !op_s->operation_callbacks.empty ())
+ {
+ op_p = op_s->operation_callbacks.equal_range (a);
+
+ if (op_p.first == op_p.second)
+ op_s = nullptr; // Ignore.
+ }
+ else
+ op_s = nullptr; // Ignore.
+ }
+
+ // Pre operations.
+ //
+ // Note that here we assume the dir{} target cannot be part of a group
+ // and as a result we (a) don't try to avoid calling post callbacks in
+ // case of a group failure and (b) merge the pre and post states with
+ // the group state.
+ //
+ if (op_s != nullptr)
+ {
+ for (auto i (op_p.first); i != op_p.second; ++i)
+ if (const auto& f = i->second.pre)
+ ts |= f (a, *op_s, *op_t);
+ }
+
+ // Recipe.
+ //
+ ts |= r != nullptr ? r (a, t) : target_state::unchanged;
+
+ // Post operations.
+ //
+ if (op_s != nullptr)
+ {
+ for (auto i (op_p.first); i != op_p.second; ++i)
+ if (const auto& f = i->second.post)
+ ts |= f (a, *op_s, *op_t);
+ }
+
+ // See the recipe documentation for details on what's going on here.
+ // Note that if the result is group, then the group's state can be
+ // failed.
+ //
+ switch (t[a].state = ts)
+ {
+ case target_state::changed:
+ case target_state::unchanged:
+ break;
+ case target_state::postponed:
+ ts = t[a].state = target_state::unchanged;
+ break;
+ case target_state::group:
+ ts = (*t.group)[a].state;
+ break;
+ default:
+ assert (false);
+ }
+ }
+ catch (const failed&)
+ {
+ ts = t[a].state = target_state::failed;
+ }
+
+ return ts;
+ }
+
+ void
+ update_backlink (const file& f, const path& l, bool changed, backlink_mode m)
+ {
+ using mode = backlink_mode;
+
+ const path& p (f.path ());
+ dir_path d (l.directory ());
+
+ // At low verbosity levels we print the command if the target changed or
+ // the link does not exist (we also treat errors as "not exist" and let
+ // the link update code below handle it).
+ //
+ // Note that in the changed case we print it even if the link is not
+ // actually updated to signal to the user that the updated out target is
+ // now available in src.
+ //
+ if (verb <= 2)
+ {
+ if (changed || !butl::entry_exists (l,
+ false /* follow_symlinks */,
+ true /* ignore_errors */))
+ {
+ const char* c (nullptr);
+ switch (m)
+ {
+ case mode::link:
+ case mode::symbolic: c = verb >= 2 ? "ln -s" : "ln"; break;
+ case mode::hard: c = "ln"; break;
+ case mode::copy:
+ case mode::overwrite: c = l.to_directory () ? "cp -r" : "cp"; break;
+ }
+
+ // Note: 'ln foo/ bar/' means a different thing.
+ //
+ if (verb >= 2)
+ text << c << ' ' << p.string () << ' ' << l.string ();
+ else
+ text << c << ' ' << f << " -> " << d;
+ }
+ }
+
+ // What if there is no such subdirectory in src (some like to stash their
+ // executables in bin/ or some such). The easiest is probably just to
+ // create it even though we won't be cleaning it up.
+ //
+ if (!exists (d))
+ mkdir_p (d, 2 /* verbosity */);
+
+ update_backlink (p, l, m);
+ }
+
+ void
+ update_backlink (const path& p, const path& l, bool changed, backlink_mode m)
+ {
+ // As above but with a slightly different diagnostics.
+
+ using mode = backlink_mode;
+
+ dir_path d (l.directory ());
+
+ if (verb <= 2)
+ {
+ if (changed || !butl::entry_exists (l,
+ false /* follow_symlinks */,
+ true /* ignore_errors */))
+ {
+ const char* c (nullptr);
+ switch (m)
+ {
+ case mode::link:
+ case mode::symbolic: c = verb >= 2 ? "ln -s" : "ln"; break;
+ case mode::hard: c = "ln"; break;
+ case mode::copy:
+ case mode::overwrite: c = l.to_directory () ? "cp -r" : "cp"; break;
+ }
+
+ if (verb >= 2)
+ text << c << ' ' << p.string () << ' ' << l.string ();
+ else
+ text << c << ' ' << p.string () << " -> " << d;
+ }
+ }
+
+ if (!exists (d))
+ mkdir_p (d, 2 /* verbosity */);
+
+ update_backlink (p, l, m);
+ }
+
+ static inline void
+ try_rmbacklink (const path& l,
+ backlink_mode m,
+ bool ie /* ignore_errors */= false)
+ {
+ // See also clean_backlink() below.
+
+ using mode = backlink_mode;
+
+ if (l.to_directory ())
+ {
+ switch (m)
+ {
+ case mode::link:
+ case mode::symbolic:
+ case mode::hard: try_rmsymlink (l, true /* directory */, ie); break;
+ case mode::copy: try_rmdir_r (path_cast<dir_path> (l), ie); break;
+ case mode::overwrite: break;
+ }
+ }
+ else
+ {
+ // try_rmfile() should work for symbolic and hard file links.
+ //
+ switch (m)
+ {
+ case mode::link:
+ case mode::symbolic:
+ case mode::hard:
+ case mode::copy: try_rmfile (l, ie); break;
+ case mode::overwrite: break;
+ }
+ }
+ }
+
+ void
+ update_backlink (const path& p, const path& l, backlink_mode om)
+ {
+ using mode = backlink_mode;
+
+ bool d (l.to_directory ());
+ mode m (om); // Keep original mode.
+
+ auto print = [&p, &l, &m, d] ()
+ {
+ if (verb >= 3)
+ {
+ const char* c (nullptr);
+ switch (m)
+ {
+ case mode::link:
+ case mode::symbolic: c = "ln -sf"; break;
+ case mode::hard: c = "ln -f"; break;
+ case mode::copy:
+ case mode::overwrite: c = d ? "cp -r" : "cp"; break;
+ }
+
+ text << c << ' ' << p.string () << ' ' << l.string ();
+ }
+ };
+
+ try
+ {
+ // Normally will be there.
+ //
+ if (!dry_run)
+ try_rmbacklink (l, m);
+
+ // Skip (ad hoc) targets that don't exist.
+ //
+ if (!(d ? dir_exists (p) : file_exists (p)))
+ return;
+
+ for (; !dry_run; ) // Retry/fallback loop.
+ try
+ {
+ switch (m)
+ {
+ case mode::link:
+ case mode::symbolic: mksymlink (p, l, d); break;
+ case mode::hard: mkhardlink (p, l, d); break;
+ case mode::copy:
+ case mode::overwrite:
+ {
+ if (d)
+ {
+ // Currently, for a directory, we do a "copy-link": we make the
+ // target directory and then link each entry (for now this is
+ // only used to "link" a Windows DLL assembly with only files
+ // inside).
+ //
+ dir_path fr (path_cast<dir_path> (p));
+ dir_path to (path_cast<dir_path> (l));
+
+ try_mkdir (to);
+
+ for (const auto& de: dir_iterator (fr,
+ false /* ignore_dangling */))
+ {
+ path f (fr / de.path ());
+ path t (to / de.path ());
+
+ update_backlink (f, t, mode::link);
+ }
+ }
+ else
+ cpfile (p, l, cpflags::overwrite_content);
+
+ break;
+ }
+ }
+
+ break; // Success.
+ }
+ catch (const system_error& e)
+ {
+ // If symlinks not supported, try a hardlink.
+ //
+ if (m == mode::link)
+ {
+ // Note that we are not guaranteed that the system_error exception
+ // is of the generic category.
+ //
+ int c (e.code ().value ());
+ if (e.code ().category () == generic_category () &&
+ (c == ENOSYS || // Not implemented.
+ c == EPERM)) // Not supported by the filesystem(s).
+ {
+ m = mode::hard;
+ continue;
+ }
+ }
+
+ throw;
+ }
+ }
+ catch (const system_error& e)
+ {
+ const char* w (nullptr);
+ switch (m)
+ {
+ case mode::link:
+ case mode::symbolic: w = "symbolic link"; break;
+ case mode::hard: w = "hard link"; break;
+ case mode::copy:
+ case mode::overwrite: w = "copy"; break;
+ }
+
+ print ();
+ fail << "unable to make " << w << ' ' << l << ": " << e;
+ }
+
+ print ();
+ }
+
+ void
+ clean_backlink (const path& l, uint16_t v /*verbosity*/, backlink_mode m)
+ {
+ // Like try_rmbacklink() but with diagnostics and error handling.
+
+ using mode = backlink_mode;
+
+ if (l.to_directory ())
+ {
+ switch (m)
+ {
+ case mode::link:
+ case mode::symbolic:
+ case mode::hard: rmsymlink (l, true /* directory */, v); break;
+ case mode::copy: rmdir_r (path_cast<dir_path> (l), true, v); break;
+ case mode::overwrite: break;
+ }
+ }
+ else
+ {
+ // remfile() should work for symbolic and hard file links.
+ //
+ switch (m)
+ {
+ case mode::link:
+ case mode::symbolic:
+ case mode::hard:
+ case mode::copy: rmfile (l, v); break;
+ case mode::overwrite: break;
+ }
+ }
+ }
+
+ // If target/link path are syntactically to a directory, then the backlink
+ // is assumed to be to a directory, otherwise -- to a file.
+ //
+ struct backlink: auto_rm<path>
+ {
+ using path_type = build2::path;
+
+ reference_wrapper<const path_type> target;
+ backlink_mode mode;
+
+ backlink (const path_type& t, path_type&& l, backlink_mode m)
+ : auto_rm<path_type> (move (l)), target (t), mode (m)
+ {
+ assert (t.to_directory () == path.to_directory ());
+ }
+
+ ~backlink ()
+ {
+ if (active)
+ {
+ try_rmbacklink (path, mode, true /* ignore_errors */);
+ active = false;
+ }
+ }
+
+ backlink (backlink&&) = default;
+ backlink& operator= (backlink&&) = default;
+ };
+
+ // Normally (i.e., on sane platforms that don't have things like PDBs, etc)
+ // there will be just one backlink so optimize for that.
+ //
+ using backlinks = small_vector<backlink, 1>;
+
+ static optional<backlink_mode>
+ backlink_test (const target& t, const lookup& l)
+ {
+ using mode = backlink_mode;
+
+ optional<mode> r;
+ const string& v (cast<string> (l));
+
+ if (v == "true") r = mode::link;
+ else if (v == "symbolic") r = mode::symbolic;
+ else if (v == "hard") r = mode::hard;
+ else if (v == "copy") r = mode::copy;
+ else if (v == "overwrite") r = mode::overwrite;
+ else if (v != "false")
+ fail << "invalid backlink variable value '" << v << "' "
+ << "specified for target " << t;
+
+ return r;
+ }
+
+ static optional<backlink_mode>
+ backlink_test (action a, target& t)
+ {
+ // Note: the order of these checks is from the least to most expensive.
+
+ // Only for plain update/clean.
+ //
+ if (a.outer () || (a != perform_update_id && a != perform_clean_id))
+ return nullopt;
+
+ // Only file-based targets in the out tree can be backlinked.
+ //
+ if (!t.out.empty () || !t.is_a<file> ())
+ return nullopt;
+
+ // Neither an out-of-project nor in-src configuration can be forwarded.
+ //
+ const scope& bs (t.base_scope ());
+ const scope* rs (bs.root_scope ());
+ if (rs == nullptr || bs.src_path () == bs.out_path ())
+ return nullopt;
+
+ // Only for forwarded configurations.
+ //
+ if (!cast_false<bool> (rs->vars[var_forwarded]))
+ return nullopt;
+
+ lookup l (t.state[a][var_backlink]);
+
+ // If not found, check for some defaults in the global scope (this does
+ // not happen automatically since target type/pattern-specific lookup
+ // stops at the project boundary).
+ //
+ if (!l.defined ())
+ l = global_scope->find (*var_backlink, t.key ());
+
+ return l ? backlink_test (t, l) : nullopt;
+ }
+
+ static backlinks
+ backlink_collect (action a, target& t, backlink_mode m)
+ {
+ using mode = backlink_mode;
+
+ const scope& s (t.base_scope ());
+
+ backlinks bls;
+ auto add = [&bls, &s] (const path& p, mode m)
+ {
+ bls.emplace_back (p, s.src_path () / p.leaf (s.out_path ()), m);
+ };
+
+ // First the target itself.
+ //
+ add (t.as<file> ().path (), m);
+
+ // Then ad hoc group file/fsdir members, if any.
+ //
+ for (const target* mt (t.member); mt != nullptr; mt = mt->member)
+ {
+ const path* p (nullptr);
+
+ if (const file* f = mt->is_a<file> ())
+ {
+ p = &f->path ();
+
+ if (p->empty ()) // The "trust me, it's somewhere" case.
+ p = nullptr;
+ }
+ else if (const fsdir* d = mt->is_a<fsdir> ())
+ p = &d->dir;
+
+ if (p != nullptr)
+ {
+ // Check for a custom backlink mode for this member. If none, then
+ // inherit the one from the group (so if the user asked to copy .exe,
+ // we will also copy .pdb).
+ //
+ // Note that we want to avoid group or tt/patter-spec lookup. And
+ // since this is an ad hoc member (which means it was either declared
+ // in the buildfile or added by the rule), we assume that the value,
+ // if any, will be set as a rule-specific variable (since setting it
+ // as a target-specific wouldn't be MT-safe). @@ Don't think this
+ // applies to declared ad hoc members.
+ //
+ lookup l (mt->state[a].vars[var_backlink]);
+
+ optional<mode> bm (l ? backlink_test (*mt, l) : m);
+
+ if (bm)
+ add (*p, *bm);
+ }
+ }
+
+ return bls;
+ }
+
+ static inline backlinks
+ backlink_update_pre (action a, target& t, backlink_mode m)
+ {
+ return backlink_collect (a, t, m);
+ }
+
+ static void
+ backlink_update_post (target& t, target_state ts, backlinks& bls)
+ {
+ if (ts == target_state::failed)
+ return; // Let auto rm clean things up.
+
+ // Make backlinks.
+ //
+ for (auto b (bls.begin ()), i (b); i != bls.end (); ++i)
+ {
+ const backlink& bl (*i);
+
+ if (i == b)
+ update_backlink (t.as<file> (),
+ bl.path,
+ ts == target_state::changed,
+ bl.mode);
+ else
+ update_backlink (bl.target, bl.path, bl.mode);
+ }
+
+ // Cancel removal.
+ //
+ for (backlink& bl: bls)
+ bl.cancel ();
+ }
+
+ static void
+ backlink_clean_pre (action a, target& t, backlink_mode m)
+ {
+ backlinks bls (backlink_collect (a, t, m));
+
+ for (auto b (bls.begin ()), i (b); i != bls.end (); ++i)
+ {
+ // Printing anything at level 1 will probably just add more noise.
+ //
+ backlink& bl (*i);
+ bl.cancel ();
+ clean_backlink (bl.path, i == b ? 2 : 3 /* verbosity */, bl.mode);
+ }
+ }
+
+ static target_state
+ execute_impl (action a, target& t)
+ {
+ target::opstate& s (t[a]);
+
+ assert (s.task_count.load (memory_order_consume) == target::count_busy ()
+ && s.state == target_state::unknown);
+
+ target_state ts;
+ try
+ {
+ // Handle target backlinking to forwarded configurations.
+ //
+ // Note that this function will never be called if the recipe is noop
+ // which is ok since such targets are probably not interesting for
+ // backlinking.
+ //
+ backlinks bls;
+ optional<backlink_mode> blm (backlink_test (a, t));
+
+ if (blm)
+ {
+ if (a == perform_update_id)
+ bls = backlink_update_pre (a, t, *blm);
+ else
+ backlink_clean_pre (a, t, *blm);
+ }
+
+ ts = execute_recipe (a, t, s.recipe);
+
+ if (blm)
+ {
+ if (a == perform_update_id)
+ backlink_update_post (t, ts, bls);
+ }
+ }
+ catch (const failed&)
+ {
+ // If we could not backlink the target, then the best way to signal the
+ // failure seems to be to mark the target as failed.
+ //
+ ts = s.state = target_state::failed;
+ }
+
+ // Decrement the target count (see set_recipe() for details).
+ //
+ if (a.inner ())
+ {
+ recipe_function** f (s.recipe.target<recipe_function*> ());
+ if (f == nullptr || *f != &group_action)
+ target_count.fetch_sub (1, memory_order_relaxed);
+ }
+
+ // Decrement the task count (to count_executed) and wake up any threads
+ // that might be waiting for this target.
+ //
+ size_t tc (s.task_count.fetch_sub (
+ target::offset_busy - target::offset_executed,
+ memory_order_release));
+ assert (tc == target::count_busy ());
+ sched.resume (s.task_count);
+
+ return ts;
+ }
+
+ target_state
+ execute (action a,
+ const target& ct,
+ size_t start_count,
+ atomic_count* task_count)
+ {
+ target& t (const_cast<target&> (ct)); // MT-aware.
+ target::opstate& s (t[a]);
+
+ // Update dependency counts and make sure they are not skew.
+ //
+ size_t gd (dependency_count.fetch_sub (1, memory_order_relaxed));
+ size_t td (s.dependents.fetch_sub (1, memory_order_release));
+ assert (td != 0 && gd != 0);
+ td--;
+
+ // 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 we are also going to treat the group state as 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.
+ //
+ // Note also that the target execution is postponed with regards to this
+ // thread. For other threads the state will still be unknown (until they
+ // try to execute it).
+ //
+ if (current_mode == execution_mode::last && td != 0)
+ return target_state::postponed;
+
+ // Try to atomically change applied to busy.
+ //
+ size_t tc (target::count_applied ());
+
+ size_t exec (target::count_executed ());
+ size_t busy (target::count_busy ());
+
+ if (s.task_count.compare_exchange_strong (
+ tc,
+ busy,
+ memory_order_acq_rel, // Synchronize on success.
+ memory_order_acquire)) // Synchronize on failure.
+ {
+ // Handle the noop recipe.
+ //
+ if (s.state == target_state::unchanged)
+ {
+ // There could still be scope operations.
+ //
+ if (t.is_a<dir> ())
+ execute_recipe (a, t, nullptr /* recipe */);
+
+ s.task_count.store (exec, memory_order_release);
+ sched.resume (s.task_count);
+ }
+ else
+ {
+ if (task_count == nullptr)
+ return execute_impl (a, t);
+
+ // Pass our diagnostics stack (this is safe since we expect the
+ // caller to wait for completion before unwinding its diag stack).
+ //
+ if (sched.async (start_count,
+ *task_count,
+ [a] (const diag_frame* ds, target& t)
+ {
+ diag_frame::stack_guard dsg (ds);
+ execute_impl (a, t);
+ },
+ diag_frame::stack (),
+ ref (t)))
+ return target_state::unknown; // Queued.
+
+ // Executed synchronously, fall through.
+ }
+ }
+ else
+ {
+ // Either busy or already executed.
+ //
+ if (tc >= busy) return target_state::busy;
+ else assert (tc == exec);
+ }
+
+ return t.executed_state (a, false);
+ }
+
+ target_state
+ execute_direct (action a, const target& ct)
+ {
+ target& t (const_cast<target&> (ct)); // MT-aware.
+ target::opstate& s (t[a]);
+
+ // Similar logic to match() above except we execute synchronously.
+ //
+ size_t tc (target::count_applied ());
+
+ size_t exec (target::count_executed ());
+ size_t busy (target::count_busy ());
+
+ if (s.task_count.compare_exchange_strong (
+ tc,
+ busy,
+ memory_order_acq_rel, // Synchronize on success.
+ memory_order_acquire)) // Synchronize on failure.
+ {
+ if (s.state == target_state::unknown)
+ execute_impl (a, t);
+ else
+ {
+ assert (s.state == target_state::unchanged ||
+ s.state == target_state::failed);
+
+ if (s.state == target_state::unchanged)
+ {
+ if (t.is_a<dir> ())
+ execute_recipe (a, t, nullptr /* recipe */);
+ }
+
+ s.task_count.store (exec, memory_order_release);
+ sched.resume (s.task_count);
+ }
+ }
+ else
+ {
+ // If the target is busy, wait for it.
+ //
+ if (tc >= busy) sched.wait (exec, s.task_count, scheduler::work_none);
+ else assert (tc == exec);
+ }
+
+ return t.executed_state (a);
+ }
+
+ static inline void
+ blank_adhoc_member (const target*&)
+ {
+ }
+
+ static inline void
+ blank_adhoc_member (prerequisite_target& pt)
+ {
+ if (pt.adhoc)
+ pt.target = nullptr;
+ }
+
+ template <typename T>
+ target_state
+ straight_execute_members (action a, atomic_count& tc,
+ T ts[], size_t n, size_t p)
+ {
+ target_state r (target_state::unchanged);
+
+ // Start asynchronous execution of prerequisites.
+ //
+ wait_guard wg (target::count_busy (), tc);
+
+ n += p;
+ for (size_t i (p); i != n; ++i)
+ {
+ const target*& mt (ts[i]);
+
+ if (mt == nullptr) // Skipped.
+ continue;
+
+ target_state s (execute_async (a, *mt, target::count_busy (), tc));
+
+ if (s == target_state::postponed)
+ {
+ r |= s;
+ mt = nullptr;
+ }
+ }
+
+ wg.wait ();
+
+ // Now all the targets in prerequisite_targets must be either still busy
+ // or executed and synchronized (and we have blanked out all the postponed
+ // ones).
+ //
+ for (size_t i (p); i != n; ++i)
+ {
+ if (ts[i] == nullptr)
+ continue;
+
+ const target& mt (*ts[i]);
+
+ // If the target is still busy, wait for its completion.
+ //
+ const auto& tc (mt[a].task_count);
+ if (tc.load (memory_order_acquire) >= target::count_busy ())
+ sched.wait (target::count_executed (), tc, scheduler::work_none);
+
+ r |= mt.executed_state (a);
+
+ blank_adhoc_member (ts[i]);
+ }
+
+ return r;
+ }
+
+ template <typename T>
+ target_state
+ reverse_execute_members (action a, atomic_count& tc,
+ T ts[], size_t n, size_t p)
+ {
+ // Pretty much as straight_execute_members() but in reverse order.
+ //
+ target_state r (target_state::unchanged);
+
+ wait_guard wg (target::count_busy (), tc);
+
+ n = p - n;
+ for (size_t i (p); i != n; )
+ {
+ const target*& mt (ts[--i]);
+
+ if (mt == nullptr)
+ continue;
+
+ target_state s (execute_async (a, *mt, target::count_busy (), tc));
+
+ if (s == target_state::postponed)
+ {
+ r |= s;
+ mt = nullptr;
+ }
+ }
+
+ wg.wait ();
+
+ for (size_t i (p); i != n; )
+ {
+ if (ts[--i] == nullptr)
+ continue;
+
+ const target& mt (*ts[i]);
+
+ const auto& tc (mt[a].task_count);
+ if (tc.load (memory_order_acquire) >= target::count_busy ())
+ sched.wait (target::count_executed (), tc, scheduler::work_none);
+
+ r |= mt.executed_state (a);
+
+ blank_adhoc_member (ts[i]);
+ }
+
+ return r;
+ }
+
+ // Instantiate only for what we need.
+ //
+ template LIBBUILD2_SYMEXPORT target_state
+ straight_execute_members<const target*> (
+ action, atomic_count&, const target*[], size_t, size_t);
+
+ template LIBBUILD2_SYMEXPORT target_state
+ reverse_execute_members<const target*> (
+ action, atomic_count&, const target*[], size_t, size_t);
+
+ template LIBBUILD2_SYMEXPORT target_state
+ straight_execute_members<prerequisite_target> (
+ action, atomic_count&, prerequisite_target[], size_t, size_t);
+
+ template LIBBUILD2_SYMEXPORT target_state
+ reverse_execute_members<prerequisite_target> (
+ action, atomic_count&, prerequisite_target[], size_t, size_t);
+
+ pair<optional<target_state>, const target*>
+ execute_prerequisites (const target_type* tt,
+ action a, const target& t,
+ const timestamp& mt, const execute_filter& ef,
+ size_t n)
+ {
+ assert (current_mode == execution_mode::first);
+
+ auto& pts (t.prerequisite_targets[a]);
+
+ if (n == 0)
+ n = pts.size ();
+
+ // Pretty much as straight_execute_members() but hairier.
+ //
+ target_state rs (target_state::unchanged);
+
+ wait_guard wg (target::count_busy (), t[a].task_count);
+
+ for (size_t i (0); i != n; ++i)
+ {
+ const target*& pt (pts[i]);
+
+ if (pt == nullptr) // Skipped.
+ continue;
+
+ target_state s (
+ execute_async (
+ a, *pt, target::count_busy (), t[a].task_count));
+
+ if (s == target_state::postponed)
+ {
+ rs |= s;
+ pt = nullptr;
+ }
+ }
+
+ wg.wait ();
+
+ bool e (mt == timestamp_nonexistent);
+ const target* rt (tt != nullptr ? nullptr : &t);
+
+ for (size_t i (0); i != n; ++i)
+ {
+ prerequisite_target& p (pts[i]);
+
+ if (p == nullptr)
+ continue;
+
+ const target& pt (*p.target);
+
+ const auto& tc (pt[a].task_count);
+ if (tc.load (memory_order_acquire) >= target::count_busy ())
+ sched.wait (target::count_executed (), tc, scheduler::work_none);
+
+ target_state s (pt.executed_state (a));
+ rs |= s;
+
+ // Should we compare the timestamp to this target's?
+ //
+ if (!e && (p.adhoc || !ef || ef (pt, i)))
+ {
+ // If this is an mtime-based target, then compare timestamps.
+ //
+ if (const mtime_target* mpt = pt.is_a<mtime_target> ())
+ {
+ timestamp mp (mpt->mtime ());
+
+ // The same logic as in mtime_target::newer() (but avoids a call to
+ // state()).
+ //
+ if (mt < mp || (mt == mp && s == target_state::changed))
+ e = true;
+ }
+ else
+ {
+ // Otherwise we assume the prerequisite is newer if it was changed.
+ //
+ if (s == target_state::changed)
+ e = true;
+ }
+ }
+
+ if (p.adhoc)
+ p.target = nullptr; // Blank out.
+ else
+ {
+ if (rt == nullptr && pt.is_a (*tt))
+ rt = &pt;
+ }
+ }
+
+ assert (rt != nullptr);
+
+ return pair<optional<target_state>, const target*> (
+ e ? optional<target_state> () : rs,
+ tt != nullptr ? rt : nullptr);
+ }
+
+ target_state
+ noop_action (action a, const target& t)
+ {
+ text << "noop action triggered for " << diag_doing (a, t);
+ assert (false); // We shouldn't be called (see set_recipe()).
+ return target_state::unchanged;
+ }
+
+ target_state
+ group_action (action a, const target& t)
+ {
+ // If the group is busy, we wait, similar to prerequisites.
+ //
+ const target& g (*t.group);
+
+ target_state gs (execute (a, g));
+
+ if (gs == target_state::busy)
+ sched.wait (target::count_executed (),
+ g[a].task_count,
+ scheduler::work_none);
+
+ // Return target_state::group to signal to execute() that this target's
+ // state comes from the group (which, BTW, can be failed).
+ //
+ // There is just one small problem: if the returned group state is
+ // postponed, then this means the group hasn't been executed yet. And if
+ // we return target_state::group, then this means any state queries (see
+ // executed_state()) will be directed to the target which might still not
+ // be executed or, worse, is being executed as we query.
+ //
+ // So in this case we return target_state::postponed (which will result in
+ // the member being treated as unchanged). This is how it is done for
+ // prerequisites and seeing that we've been acting as if the group is our
+ // prerequisite, there is no reason to deviate (see the recipe return
+ // value documentation for details).
+ //
+ return gs != target_state::postponed ? target_state::group : gs;
+ }
+
+ target_state
+ default_action (action a, const target& t)
+ {
+ return execute_prerequisites (a, t);
+ }
+
+ target_state
+ perform_clean_extra (action a, const file& ft,
+ const clean_extras& extras,
+ const clean_adhoc_extras& adhoc_extras)
+ {
+ // Clean the extras first and don't print the commands at verbosity level
+ // below 3. Note the first extra file/directory that actually got removed
+ // for diagnostics below.
+ //
+ // Note that dry-run is taken care of by the filesystem functions.
+ //
+ target_state er (target_state::unchanged);
+ bool ed (false);
+ path ep;
+
+ auto clean_extra = [&er, &ed, &ep] (const file& f,
+ const path* fp,
+ const clean_extras& es)
+ {
+ for (const char* e: es)
+ {
+ size_t n;
+ if (e == nullptr || (n = strlen (e)) == 0)
+ continue;
+
+ path p;
+ bool d;
+
+ if (path::traits_type::absolute (e))
+ {
+ p = path (e);
+ d = p.to_directory ();
+ }
+ else
+ {
+ if ((d = (e[n - 1] == '/')))
+ --n;
+
+ if (fp == nullptr)
+ {
+ fp = &f.path ();
+ assert (!fp->empty ()); // Must be assigned.
+ }
+
+ p = *fp;
+ for (; *e == '-'; ++e)
+ p = p.base ();
+
+ p.append (e, n);
+ }
+
+ target_state r (target_state::unchanged);
+
+ if (d)
+ {
+ dir_path dp (path_cast<dir_path> (p));
+
+ switch (build2::rmdir_r (dp, true, 3))
+ {
+ case rmdir_status::success:
+ {
+ r = target_state::changed;
+ break;
+ }
+ case rmdir_status::not_empty:
+ {
+ if (verb >= 3)
+ text << dp << " is current working directory, not removing";
+ break;
+ }
+ case rmdir_status::not_exist:
+ break;
+ }
+ }
+ else
+ {
+ if (rmfile (p, 3))
+ r = target_state::changed;
+ }
+
+ if (r == target_state::changed && ep.empty ())
+ {
+ ed = d;
+ ep = move (p);
+ }
+
+ er |= r;
+ }
+ };
+
+ const path& fp (ft.path ());
+
+ if (!fp.empty () && !extras.empty ())
+ clean_extra (ft, nullptr, extras);
+
+ target_state tr (target_state::unchanged);
+
+ // Check if we were asked not to actually remove the files. The extras are
+ // tricky: some of them, like depdb should definitely be removed. But
+ // there could also be those that shouldn't. Currently we only use this
+ // for auto-generated source code where the only extra file, if any, is
+ // depdb so for now we treat them as "to remove" but in the future we may
+ // need to have two lists.
+ //
+ bool clean (cast_true<bool> (ft[var_clean]));
+
+ // Now clean the ad hoc group file members, if any.
+ //
+ for (const target* m (ft.member); m != nullptr; m = m->member)
+ {
+ const file* mf (m->is_a<file> ());
+ const path* mp (mf != nullptr ? &mf->path () : nullptr);
+
+ if (mf == nullptr || mp->empty ())
+ continue;
+
+ if (!adhoc_extras.empty ())
+ {
+ auto i (find_if (adhoc_extras.begin (),
+ adhoc_extras.end (),
+ [mf] (const clean_adhoc_extra& e)
+ {
+ return mf->is_a (e.type);
+ }));
+
+ if (i != adhoc_extras.end ())
+ clean_extra (*mf, mp, i->extras);
+ }
+
+ if (!clean)
+ continue;
+
+ // Make this "primary target" for diagnostics/result purposes if the
+ // primary target is unreal.
+ //
+ if (fp.empty ())
+ {
+ if (rmfile (*mp, *mf))
+ tr = target_state::changed;
+ }
+ else
+ {
+ target_state r (rmfile (*mp, 3)
+ ? target_state::changed
+ : target_state::unchanged);
+
+ if (r == target_state::changed && ep.empty ())
+ ep = *mp;
+
+ er |= r;
+ }
+ }
+
+ // Now clean the primary target and its prerequisited in the reverse order
+ // of update: first remove the file, then clean the prerequisites.
+ //
+ if (clean && !fp.empty () && rmfile (fp, ft))
+ tr = target_state::changed;
+
+ // Update timestamp in case there are operations after us that could use
+ // the information.
+ //
+ ft.mtime (timestamp_nonexistent);
+
+ // Clean prerequisites.
+ //
+ tr |= reverse_execute_prerequisites (a, ft);
+
+ // Factor the result of removing the extra files into the target state.
+ // While strictly speaking removing them doesn't change the target state,
+ // if we don't do this, then we may end up removing the file but still
+ // saying that everything is clean (e.g., if someone removes the target
+ // file but leaves the extra laying around). That would be confusing.
+ //
+ // What would also be confusing is if we didn't print any commands in
+ // this case.
+ //
+ if (tr != target_state::changed && er == target_state::changed)
+ {
+ if (verb > (current_diag_noise ? 0 : 1) && verb < 3)
+ {
+ if (ed)
+ text << "rm -r " << path_cast<dir_path> (ep);
+ else
+ text << "rm " << ep;
+ }
+ }
+
+ tr |= er;
+ return tr;
+ }
+
+ target_state
+ perform_clean (action a, const target& t)
+ {
+ const file& f (t.as<file> ());
+ assert (!f.path ().empty ());
+ return perform_clean_extra (a, f, {});
+ }
+
+ target_state
+ perform_clean_depdb (action a, const target& t)
+ {
+ const file& f (t.as<file> ());
+ assert (!f.path ().empty ());
+ return perform_clean_extra (a, f, {".d"});
+ }
+
+ target_state
+ perform_clean_group (action a, const target& xg)
+ {
+ const mtime_target& g (xg.as<mtime_target> ());
+
+ // Similar logic to perform_clean_extra() above.
+ //
+ target_state r (target_state::unchanged);
+
+ if (cast_true<bool> (g[var_clean]))
+ {
+ for (group_view gv (g.group_members (a)); gv.count != 0; --gv.count)
+ {
+ if (const target* m = gv.members[gv.count - 1])
+ {
+ if (rmfile (m->as<file> ().path (), *m))
+ r |= target_state::changed;
+ }
+ }
+ }
+
+ g.mtime (timestamp_nonexistent);
+
+ r |= reverse_execute_prerequisites (a, g);
+ return r;
+ }
+
+ target_state
+ perform_clean_group_depdb (action a, const target& g)
+ {
+ // The same twisted target state merging logic as in perform_clean_extra().
+ //
+ target_state er (target_state::unchanged);
+ path ep;
+
+ group_view gv (g.group_members (a));
+ if (gv.count != 0)
+ {
+ ep = gv.members[0]->as<file> ().path () + ".d";
+
+ if (rmfile (ep, 3))
+ er = target_state::changed;
+ }
+
+ target_state tr (perform_clean_group (a, g));
+
+ if (tr != target_state::changed && er == target_state::changed)
+ {
+ if (verb > (current_diag_noise ? 0 : 1) && verb < 3)
+ text << "rm " << ep;
+ }
+
+ tr |= er;
+ return tr;
+ }
+}
diff --git a/libbuild2/algorithm.hxx b/libbuild2/algorithm.hxx
new file mode 100644
index 0000000..50e8423
--- /dev/null
+++ b/libbuild2/algorithm.hxx
@@ -0,0 +1,778 @@
+// file : libbuild2/algorithm.hxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef LIBBUILD2_ALGORITHM_HXX
+#define LIBBUILD2_ALGORITHM_HXX
+
+#include <libbuild2/types.hxx>
+#include <libbuild2/utility.hxx>
+
+#include <libbuild2/action.hxx>
+#include <libbuild2/target.hxx>
+
+#include <libbuild2/export.hxx>
+
+namespace build2
+{
+ class scope;
+ class prerequisite;
+ class prerequisite_key;
+
+ // The default prerequisite search implementation. It first calls the
+ // prerequisite-type-specific search function. If that doesn't yeld
+ // anything, it creates a new target.
+ //
+ const target&
+ search (const target&, const prerequisite&);
+
+ // As above but only search for an already existing target.
+ //
+ const target*
+ search_existing (const prerequisite&);
+
+ // As above but cache a target searched in a custom way.
+ //
+ const target&
+ search_custom (const prerequisite&, const target&);
+
+ // As above but specify the prerequisite to search as a key.
+ //
+ LIBBUILD2_SYMEXPORT const target&
+ search (const target&, const prerequisite_key&);
+
+ LIBBUILD2_SYMEXPORT const target*
+ search_existing (const prerequisite_key&);
+
+ // Uniform search interface for prerequisite/prerequisite_member.
+ //
+ inline const target&
+ search (const target& t, const prerequisite_member& p) {return p.search (t);}
+
+ // As above but override the target type. Useful for searching for
+ // target group members where we need to search for a different
+ // target type.
+ //
+ const target&
+ search (const target&, const target_type&, const prerequisite_key&);
+
+ // As above but specify the prerequisite to search as individual key
+ // components. Scope can be NULL if the directory is absolute.
+ //
+ const target&
+ search (const target&,
+ const target_type& type,
+ const dir_path& dir,
+ const dir_path& out,
+ const string& name,
+ const string* ext = nullptr, // NULL means unspecified.
+ const scope* = nullptr, // NULL means dir is absolute.
+ const optional<project_name>& proj = nullopt);
+
+ const target*
+ search_existing (const target_type& type,
+ const dir_path& dir,
+ const dir_path& out,
+ const string& name,
+ const string* ext = nullptr,
+ const scope* = nullptr,
+ const optional<project_name>& proj = nullopt);
+
+ // As above but specify the target type as template argument.
+ //
+ template <typename T>
+ const T&
+ search (const target&,
+ const dir_path& dir,
+ const dir_path& out,
+ const string& name,
+ const string* ext = nullptr,
+ const scope* = nullptr);
+
+ // 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.
+ //
+ LIBBUILD2_SYMEXPORT const target&
+ search (const target&, name, const scope&);
+
+ // Unlike the above version, this one can be called during the execute
+ // phase. Return NULL for unknown target types.
+ //
+ LIBBUILD2_SYMEXPORT const target*
+ search_existing (const name&,
+ const scope&,
+ const dir_path& out = dir_path ());
+
+ // Target match lock: a non-const target reference and the target::offset_*
+ // state that has already been "achieved". Note that target::task_count
+ // itself is set to busy for the duration or the lock. While at it we also
+ // maintain a stack of active locks in the current dependency chain (used to
+ // detect dependency cycles).
+ //
+ struct LIBBUILD2_SYMEXPORT target_lock
+ {
+ using action_type = build2::action;
+ using target_type = build2::target;
+
+ action_type action;
+ target_type* target = nullptr;
+ size_t offset = 0;
+
+ explicit operator bool () const {return target != nullptr;}
+
+ void
+ unlock ();
+
+ // Movable-only type with move-assignment only to NULL lock.
+ //
+ target_lock () = default;
+ target_lock (target_lock&&);
+ target_lock& operator= (target_lock&&);
+
+ target_lock (const target_lock&) = delete;
+ target_lock& operator= (const target_lock&) = delete;
+
+ // Implementation details.
+ //
+ ~target_lock ();
+ target_lock (action_type, target_type*, size_t);
+
+ struct data
+ {
+ action_type action;
+ target_type* target;
+ size_t offset;
+ };
+
+ data
+ release ();
+
+ // Tip of the stack.
+ //
+ static const target_lock*
+ stack () noexcept;
+
+ // Set the new and return the previous tip of the stack.
+ //
+ static const target_lock*
+ stack (const target_lock*) noexcept;
+
+ const target_lock* prev;
+
+ void
+ unstack ();
+
+ struct stack_guard
+ {
+ explicit stack_guard (const target_lock* s): s_ (stack (s)) {}
+ ~stack_guard () {stack (s_);}
+ const target_lock* s_;
+ };
+ };
+
+ // If this target is already locked in this dependency chain, then return
+ // the corresponding lock. Return NULL otherwise (so can be used a boolean
+ // predicate).
+ //
+ const target_lock*
+ dependency_cycle (action, const target&);
+
+ // If the target is already applied (for this action) or executed, then no
+ // lock is acquired. Otherwise, the target must not yet be matched for this
+ // action.
+ //
+ // @@ MT fuzzy: what if it is already in the desired state, why assert?
+ // Currently we only use it with match_recipe() and if it is matched
+ // but not applied, then it's not clear why we are overriding that
+ // match.
+ //
+ target_lock
+ lock (action, const target&);
+
+ // Add an ad hoc member to the end of the chain assuming that an already
+ // existing member of this target type is the same. Return the newly added
+ // or already existing target. The member directories (dir and out) are
+ // expected to be absolute and normalized.
+ //
+ // Note that here and in find_adhoc_member() below (as well as in
+ // perform_clean_extra()) we use target type (as opposed to, say, type and
+ // name) as the member's identity. This fits our current needs where every
+ // (rule-managed) ad hoc member has a unique target type and we have no need
+ // for multiple members of the same type. This also allows us to support
+ // things like changing the ad hoc member name by declaring it in a
+ // buildfile.
+ //
+ LIBBUILD2_SYMEXPORT target&
+ add_adhoc_member (target&,
+ const target_type&,
+ const dir_path& dir,
+ const dir_path& out,
+ string name);
+
+ // If the extension is specified then it is added to the member's target
+ // name.
+ //
+ target&
+ add_adhoc_member (target&, const target_type&, const char* ext = nullptr);
+
+ template <typename T>
+ inline T&
+ add_adhoc_member (target& g, const target_type& tt, const char* e = nullptr)
+ {
+ return static_cast<T&> (add_adhoc_member (g, tt, e));
+ }
+
+ template <typename T>
+ inline T&
+ add_adhoc_member (target& g, const char* e = nullptr)
+ {
+ return add_adhoc_member<T> (g, T::static_type, e);
+ }
+
+ // Find an ad hoc member of the specified target type returning NULL if not
+ // found.
+ //
+ target*
+ find_adhoc_member (target&, const target_type&);
+
+ const target*
+ find_adhoc_member (const target&, const target_type&);
+
+ template <typename T>
+ inline T*
+ find_adhoc_member (target& g, const target_type& tt)
+ {
+ return static_cast<T*> (find_adhoc_member (g, tt));
+ }
+
+ template <typename T>
+ inline const T*
+ find_adhoc_member (const target& g, const target_type& tt)
+ {
+ return static_cast<const T*> (find_adhoc_member (g, tt));
+ }
+
+ template <typename T>
+ inline const T*
+ find_adhoc_member (const target& g)
+ {
+ return find_adhoc_member<T> (g, T::static_type);
+ }
+
+ template <typename T>
+ inline T*
+ find_adhoc_member (target& g)
+ {
+ return find_adhoc_member<T> (g, T::static_type);
+ }
+
+ // 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(). Return the target
+ // state translating target_state::failed to the failed exception unless
+ // instructed otherwise.
+ //
+ // The try_match() version doesn't issue diagnostics if there is no rule
+ // match (but fails as match() for all other errors, like rule ambiguity,
+ // inability to apply, etc). The first half of the result indicated whether
+ // there was a rule match.
+ //
+ // The unmatch argument allows optimizations that avoid calling execute().
+ // If it is unmatch::unchanged then only unmatch the target if it is known
+ // to be unchanged after match. If it is unmatch::safe, then unmatch the
+ // target if it is safe (this includes unchanged or if we know that someone
+ // else will execute this target). Return true if unmatch succeeded. Always
+ // throw if failed.
+ //
+ enum class unmatch {none, unchanged, safe};
+
+ target_state
+ match (action, const target&, bool fail = true);
+
+ pair<bool, target_state>
+ try_match (action, const target&, bool fail = true);
+
+ bool
+ match (action, const target&, unmatch);
+
+ // Start asynchronous match. Return target_state::postponed if the
+ // asynchrounous operation has been started and target_state::busy if the
+ // target has already been busy. Regardless of the result, match() must be
+ // called in order to complete the operation (except target_state::failed).
+ //
+ // If fail is false, then return target_state::failed if the target match
+ // failed. Otherwise, throw the failed exception if keep_going is false and
+ // return target_state::failed otherwise.
+ //
+ target_state
+ match_async (action, const target&,
+ size_t start_count, atomic_count& task_count,
+ bool fail = true);
+
+ // Match by specifying the recipe directly and without incrementing the
+ // dependency counts. The target must be locked.
+ //
+ void
+ match_recipe (target_lock&, recipe);
+
+ // Match a "delegate rule" from withing another rules' apply() function
+ // avoiding recursive matches (thus the third argument). Unless try_match is
+ // true, fail if no rule is found. Otherwise return empty recipe. Note that
+ // unlike match(), this function does not increment the dependents count and
+ // the two rules must coordinate who is using the target's data pad and/or
+ // prerequisite_targets. See also the companion execute_delegate().
+ //
+ recipe
+ match_delegate (action, target&, const rule&, bool try_match = false);
+
+ // Match a rule for the inner operation from withing the outer rule's
+ // apply() function. See also the companion execute_inner().
+ //
+ target_state
+ match_inner (action, const target&);
+
+ bool
+ match_inner (action, const target&, unmatch);
+
+ // The standard prerequisite search and match implementations. They call
+ // search() (unless a custom is provided) and then match() (unless custom
+ // returned NULL) for each prerequisite in a loop omitting out of project
+ // prerequisites for the clean operation. If this target is a member of a
+ // group, then first do this to the group's prerequisites.
+ //
+ using match_search = function<
+ prerequisite_target (action,
+ const target&,
+ const prerequisite&,
+ include_type)>;
+
+ void
+ match_prerequisites (action, target&, const match_search& = nullptr);
+
+ // As above but go into group members.
+ //
+ // Note that if we cleaning, this function doesn't go into group members, as
+ // an optimization (the group should clean everything up).
+ //
+ using match_search_member = function<
+ prerequisite_target (action,
+ const target&,
+ const prerequisite_member&,
+ include_type)>;
+
+ void
+ match_prerequisite_members (action, target&,
+ const match_search_member& = nullptr);
+
+ // As above but omit prerequisites that are not in the specified scope.
+ //
+ void
+ match_prerequisites (action, target&, const scope&);
+
+ void
+ match_prerequisite_members (action, target&, const scope&);
+
+ // Match (already searched) members of a group or similar prerequisite-like
+ // dependencies. Similar in semantics to match_prerequisites(). Any marked
+ // target pointers are skipped.
+ //
+ // T can only be const target* or prerequisite_target.
+ //
+ template <typename T>
+ void
+ match_members (action, target&, T const*, size_t);
+
+ template <size_t N>
+ inline void
+ match_members (action a, target& t, const target* (&ts)[N])
+ {
+ match_members (a, t, ts, N);
+ }
+
+ inline void
+ match_members (action a,
+ target& t,
+ prerequisite_targets& ts,
+ size_t start = 0)
+ {
+ match_members (a, t, ts.data () + start, ts.size () - start);
+ }
+
+ // Unless already known, match, and, if necessary, execute the group in
+ // order to resolve 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).
+ //
+ // If the action is for an outer operation, then it is changed to inner
+ // which means the members are always resolved by the inner (e.g., update)
+ // rule. This feels right since this is the rule that will normally do the
+ // work (e.g., update) and therefore knows what it will produce (and if we
+ // don't do this, then the group resolution will be racy since we will use
+ // two different task_count instances for synchronization).
+ //
+ group_view
+ resolve_members (action, const target&);
+
+ // Unless already known, match the target in order to resolve its group.
+ //
+ // Unlike the member case, a rule can only decide whether a target is a
+ // member of the group in its match() since otherwise it (presumably) should
+ // not match (and some other rule may).
+ //
+ // If the action is for an outer operation, then it is changed to inner, the
+ // same as for members.
+ //
+ const target*
+ resolve_group (action, const target&);
+
+ // Inject dependency on the target's directory fsdir{}, unless it is in the
+ // src tree or is outside of any project (say, for example, an installation
+ // directory). If the parent argument is true, then inject the parent
+ // directory of a target that is itself a directory (name is empty). Return
+ // the injected target or NULL. Normally this function is called from the
+ // rule's apply() function.
+ //
+ // As an extension, this function will also search for an existing fsdir{}
+ // prerequisite for the directory and if one exists, return that (even if
+ // the target is in src tree). This can be used, for example, to place
+ // output into an otherwise non-existent directory.
+ //
+ LIBBUILD2_SYMEXPORT const fsdir*
+ inject_fsdir (action, target&, bool parent = true);
+
+ // Execute the action on target, assuming a rule has been matched and the
+ // recipe for this action has been set. This is the synchrounous executor
+ // implementation (but may still return target_state::busy if the target
+ // is already being executed). Decrements the dependents count.
+ //
+ // Note: does not translate target_state::failed to the failed exception.
+ //
+ target_state
+ execute (action, const target&);
+
+ // As above but wait for completion if the target is busy and translate
+ // target_state::failed to the failed exception.
+ //
+ target_state
+ execute_wait (action, const target&);
+
+ // As above but start asynchronous execution. Return target_state::unknown
+ // if the asynchrounous execution has been started and target_state::busy if
+ // the target has already been busy.
+ //
+ // If fail is false, then return target_state::failed if the target match
+ // failed. Otherwise, throw the failed exception if keep_going is false and
+ // return target_state::failed otherwise.
+ //
+ target_state
+ execute_async (action, const target&,
+ size_t start_count, atomic_count& task_count,
+ bool fail = true);
+
+ // 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, const target&);
+
+ // Execute the inner operation matched with match_inner(). Note that the
+ // returned target state is for the inner operation. The appropriate usage
+ // is to call this function from the outer operation's recipe and to factor
+ // the obtained state into the one returned (similar to how we do it for
+ // prerequisites).
+ //
+ // Note: waits for the completion if the target is busy and translates
+ // target_state::failed to the failed exception.
+ //
+ target_state
+ execute_inner (action, const 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 never returns the postponed target state).
+ //
+ // Note: waits for the completion if the target is busy and translates
+ // target_state::failed to the failed exception.
+ //
+ LIBBUILD2_SYMEXPORT target_state
+ execute_direct (action, const target&);
+
+ // The default prerequisite execute implementation. Call execute_async() on
+ // each non-ignored (non-NULL) prerequisite target in a loop and then wait
+ // for their completion. Return target_state::changed if any of them were
+ // changed and target_state::unchanged otherwise. If a prerequisite's
+ // execution is postponed (and thus its state cannot be queried MT-safely)
+ // of if the prerequisite is marked as ad hoc, then set its pointer in
+ // prerequisite_targets to NULL. If count is not 0, then only the first
+ // count prerequisites are executed beginning from start.
+ //
+ // Note that because after the call the ad hoc prerequisites are no longer
+ // easily accessible, this function shouldn't be used in rules that make a
+ // timestamp-based out-of-date'ness determination (which must take into
+ // account such prerequisites). Instead, consider the below versions that
+ // incorporate the timestamp check and do the right thing.
+ //
+ target_state
+ straight_execute_prerequisites (action, const target&,
+ size_t count = 0, size_t start = 0);
+
+ // As above but iterates over the prerequisites in reverse.
+ //
+ target_state
+ reverse_execute_prerequisites (action, const target&, size_t count = 0);
+
+ // Call straight or reverse depending on the current mode.
+ //
+ target_state
+ execute_prerequisites (action, const target&, size_t count = 0);
+
+ // As above but execute prerequisites for the inner action (that have
+ // been matched with match_inner()).
+ //
+ target_state
+ straight_execute_prerequisites_inner (action, const target&,
+ size_t count = 0, size_t start = 0);
+
+ target_state
+ reverse_execute_prerequisites_inner (action, const target&, size_t count = 0);
+
+ target_state
+ execute_prerequisites_inner (action, const target&, size_t count = 0);
+
+ // A version of the above that also determines whether the action needs to
+ // be executed on the target based on the passed timestamp and filter. If
+ // count is not 0, then only the first count prerequisites are executed.
+ //
+ // The filter is passed each prerequisite target and is expected to signal
+ // which ones should be used for timestamp comparison. If the filter is
+ // NULL, then all the prerequisites are used. Note that ad hoc prerequisites
+ // are always used.
+ //
+ // Note that the return value is an optional target state. If the target
+ // needs updating, then the value is absent. Otherwise it is the state that
+ // should be returned. This is used to handle the situation where some
+ // prerequisites were updated but no update of the target is necessary. In
+ // this case we still signal that the target was (conceptually, but not
+ // physically) changed. This is important both to propagate the fact that
+ // some work has been done and to also allow our dependents to detect this
+ // case if they are up to something tricky (like recursively linking liba{}
+ // prerequisites).
+ //
+ // Note that because we use mtime, this function should normally only be
+ // used in the perform_update action (which is straight).
+ //
+ using execute_filter = function<bool (const target&, size_t pos)>;
+
+ optional<target_state>
+ execute_prerequisites (action, const target&,
+ const timestamp&,
+ const execute_filter& = nullptr,
+ size_t count = 0);
+
+ // 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 finds a prerequisite of the specified type
+ // (e.g., a source file). If there are multiple prerequisites of this type,
+ // then the first is returned (this can become important if additional
+ // prerequisites of the same type get injected).
+ //
+ template <typename T>
+ pair<optional<target_state>, const T&>
+ execute_prerequisites (action, const target&,
+ const timestamp&,
+ const execute_filter& = nullptr,
+ size_t count = 0);
+
+ pair<optional<target_state>, const target&>
+ execute_prerequisites (const target_type&,
+ action, const target&,
+ const timestamp&,
+ const execute_filter& = nullptr,
+ size_t count = 0);
+
+ template <typename T>
+ pair<optional<target_state>, const T&>
+ execute_prerequisites (const target_type&,
+ action, const target&,
+ const timestamp&,
+ const execute_filter& = nullptr,
+ size_t count = 0);
+
+ // Execute members of a group or similar prerequisite-like dependencies.
+ // Similar in semantics to execute_prerequisites().
+ //
+ // T can only be const target* or prerequisite_target. If it is the latter,
+ // the ad hoc blank out semantics described in execute_prerequsites() is in
+ // effect.
+ //
+ template <typename T>
+ target_state
+ straight_execute_members (action, atomic_count&, T[], size_t, size_t);
+
+ template <typename T>
+ target_state
+ reverse_execute_members (action, atomic_count&, T[], size_t, size_t);
+
+ template <typename T>
+ inline target_state
+ straight_execute_members (action a, const target& t,
+ T ts[], size_t c, size_t s)
+ {
+ return straight_execute_members (a, t[a].task_count, ts, c, s);
+ }
+
+ template <typename T>
+ inline target_state
+ reverse_execute_members (action a, const target& t,
+ T ts[], size_t c, size_t s)
+ {
+ return reverse_execute_members (a, t[a].task_count, ts, c, s);
+ }
+
+ // Call straight or reverse depending on the current mode.
+ //
+ target_state
+ execute_members (action, const target&, const target*[], size_t);
+
+ template <size_t N>
+ inline target_state
+ straight_execute_members (action a, const target& t, const target* (&ts)[N])
+ {
+ return straight_execute_members (a, t, ts, N, 0);
+ }
+
+ template <size_t N>
+ inline target_state
+ reverse_execute_members (action a, const target& t, const target* (&ts)[N])
+ {
+ return reverse_execute_members (a, t, ts, N, N);
+ }
+
+ template <size_t N>
+ inline target_state
+ execute_members (action a, const target& t, const target* (&ts)[N])
+ {
+ return execute_members (a, t, ts, N);
+ }
+
+ // Return noop_recipe instead of using this function directly.
+ //
+ LIBBUILD2_SYMEXPORT target_state
+ noop_action (action, const target&);
+
+ // Default action implementation which forwards to the prerequisites.
+ // Use default_recipe instead of using this function directly.
+ //
+ LIBBUILD2_SYMEXPORT target_state
+ default_action (action, const target&);
+
+ // Standard perform(clean) action implementation for the file target
+ // (or derived).
+ //
+ LIBBUILD2_SYMEXPORT target_state
+ perform_clean (action, const target&);
+
+ // As above, but also removes the auxiliary dependency database (.d file).
+ //
+ LIBBUILD2_SYMEXPORT target_state
+ perform_clean_depdb (action, const target&);
+
+ // As above but clean the target group. The group should be an mtime_target
+ // and members should be files.
+ //
+ LIBBUILD2_SYMEXPORT target_state
+ perform_clean_group (action, const target&);
+
+ // As above but clean both the target group and depdb. The depdb file path
+ // is derived from the first member file path.
+ //
+ LIBBUILD2_SYMEXPORT target_state
+ perform_clean_group_depdb (action, const target&);
+
+ // Helper for custom perform(clean) implementations that cleans extra files
+ // and directories (recursively) specified as a list of either absolute
+ // paths or "path derivation directives". The directive string can be NULL,
+ // or empty in which case it is ignored. If the last character in a
+ // directive is '/', then the resulting path is treated as a directory
+ // rather than a file. The directive can start with zero or more '-'
+ // characters which indicate the number of extensions that should be
+ // stripped before the new extension (if any) is added (so if you want to
+ // strip the extension, specify just "-"). For example:
+ //
+ // perform_clean_extra (a, t, {".d", ".dlls/", "-.dll"});
+ //
+ // The extra files/directories are removed first in the specified order
+ // followed by the ad hoc group member, then target itself, and, finally,
+ // the prerequisites in the reverse order.
+ //
+ // You can also clean extra files derived from ad hoc group members that are
+ // "indexed" using using their target types (see add/find_adhoc_member() for
+ // details).
+ //
+ // Note that if the target path is empty then it is assumed "unreal" and is
+ // not cleaned (but its prerequisites/members still are).
+ //
+ using clean_extras = small_vector<const char*, 8>;
+
+ struct clean_adhoc_extra
+ {
+ const target_type& type;
+ clean_extras extras;
+ };
+
+ using clean_adhoc_extras = small_vector<clean_adhoc_extra, 2>;
+
+ LIBBUILD2_SYMEXPORT target_state
+ perform_clean_extra (action, const file&,
+ const clean_extras&,
+ const clean_adhoc_extras& = {});
+
+ inline target_state
+ perform_clean_extra (action a, const file& f,
+ initializer_list<const char*> e)
+ {
+ return perform_clean_extra (a, f, clean_extras (e));
+ }
+
+ // Update/clean a backlink issuing appropriate diagnostics at appropriate
+ // levels depending on the overload and the changed argument.
+ //
+ enum class backlink_mode
+ {
+ link, // Make a symbolic link if possible, hard otherwise.
+ symbolic, // Make a symbolic link.
+ hard, // Make a hard link.
+ copy, // Make a copy.
+ overwrite // Copy over but don't remove on clean (committed gen code).
+ };
+
+ LIBBUILD2_SYMEXPORT void
+ update_backlink (const file& target,
+ const path& link,
+ bool changed,
+ backlink_mode = backlink_mode::link);
+
+ LIBBUILD2_SYMEXPORT void
+ update_backlink (const path& target,
+ const path& link,
+ bool changed,
+ backlink_mode = backlink_mode::link);
+
+ LIBBUILD2_SYMEXPORT void
+ update_backlink (const path& target,
+ const path& link,
+ backlink_mode = backlink_mode::link);
+
+ LIBBUILD2_SYMEXPORT void
+ clean_backlink (const path& link,
+ uint16_t verbosity,
+ backlink_mode = backlink_mode::link);
+}
+
+#include <libbuild2/algorithm.ixx>
+
+#endif // LIBBUILD2_ALGORITHM_HXX
diff --git a/libbuild2/algorithm.ixx b/libbuild2/algorithm.ixx
new file mode 100644
index 0000000..7d68611
--- /dev/null
+++ b/libbuild2/algorithm.ixx
@@ -0,0 +1,764 @@
+// file : libbuild2/algorithm.ixx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <libbuild2/rule.hxx>
+#include <libbuild2/context.hxx>
+
+#include <libbuild2/export.hxx>
+
+namespace build2
+{
+ inline const target&
+ search (const target& t, const prerequisite& p)
+ {
+ assert (phase == run_phase::match);
+
+ const target* r (p.target.load (memory_order_consume));
+
+ if (r == nullptr)
+ r = &search_custom (p, search (t, p.key ()));
+
+ return *r;
+ }
+
+ inline const target*
+ search_existing (const prerequisite& p)
+ {
+ assert (phase == run_phase::match || phase == run_phase::execute);
+
+ const target* r (p.target.load (memory_order_consume));
+
+ if (r == nullptr)
+ {
+ r = search_existing (p.key ());
+
+ if (r != nullptr)
+ search_custom (p, *r);
+ }
+
+ return r;
+ }
+
+ inline const target&
+ search_custom (const prerequisite& p, const target& t)
+ {
+ assert (phase == run_phase::match || phase == run_phase::execute);
+
+ const target* e (nullptr);
+ if (!p.target.compare_exchange_strong (
+ e, &t,
+ memory_order_release,
+ memory_order_consume))
+ assert (e == &t);
+
+ return t;
+ }
+
+ inline const target&
+ search (const target& t, const target_type& tt, const prerequisite_key& k)
+ {
+ return search (
+ t,
+ prerequisite_key {
+ k.proj, {&tt, k.tk.dir, k.tk.out, k.tk.name, k.tk.ext}, k.scope});
+ }
+
+ inline const target&
+ search (const target& t,
+ const target_type& type,
+ const dir_path& dir,
+ const dir_path& out,
+ const string& name,
+ const string* ext,
+ const scope* scope,
+ const optional<project_name>& proj)
+ {
+ return search (
+ t,
+ prerequisite_key {
+ proj,
+ {
+ &type,
+ &dir, &out, &name,
+ ext != nullptr ? optional<string> (*ext) : nullopt
+ },
+ scope});
+ }
+
+ inline const target*
+ search_existing (const target_type& type,
+ const dir_path& dir,
+ const dir_path& out,
+ const string& name,
+ const string* ext,
+ const scope* scope,
+ const optional<project_name>& proj)
+ {
+ return search_existing (
+ prerequisite_key {
+ proj,
+ {
+ &type,
+ &dir, &out, &name,
+ ext != nullptr ? optional<string> (*ext) : nullopt
+ },
+ scope});
+ }
+
+ template <typename T>
+ inline const T&
+ search (const target& t,
+ const dir_path& dir,
+ const dir_path& out,
+ const string& name,
+ const string* ext,
+ const scope* scope)
+ {
+ return search (
+ t, T::static_type, dir, out, name, ext, scope).template as<T> ();
+ }
+
+ LIBBUILD2_SYMEXPORT target_lock
+ lock_impl (action, const target&, optional<scheduler::work_queue>);
+
+ LIBBUILD2_SYMEXPORT void
+ unlock_impl (action, target&, size_t);
+
+ inline target_lock::
+ target_lock (action_type a, target_type* t, size_t o)
+ : action (a), target (t), offset (o)
+ {
+ if (target != nullptr)
+ prev = stack (this);
+ }
+
+ inline void target_lock::
+ unstack ()
+ {
+ if (target != nullptr && prev != this)
+ {
+ const target_lock* cur (stack (prev));
+ assert (cur == this);
+ prev = this;
+ }
+ }
+
+ inline void target_lock::
+ unlock ()
+ {
+ if (target != nullptr)
+ {
+ unlock_impl (action, *target, offset);
+
+ if (prev != this)
+ {
+ const target_lock* cur (stack (prev));
+ assert (cur == this);
+ }
+
+ target = nullptr;
+ }
+ }
+
+ inline auto target_lock::
+ release () -> data
+ {
+ data r {action, target, offset};
+
+ if (target != nullptr)
+ {
+ if (prev != this)
+ {
+ const target_lock* cur (stack (prev));
+ assert (cur == this);
+ }
+
+ target = nullptr;
+ }
+
+ return r;
+ }
+
+ inline target_lock::
+ ~target_lock ()
+ {
+ unlock ();
+ }
+
+ inline target_lock::
+ target_lock (target_lock&& x)
+ : action (x.action), target (x.target), offset (x.offset)
+ {
+ if (target != nullptr)
+ {
+ if (x.prev != &x)
+ {
+ const target_lock* cur (stack (this));
+ assert (cur == &x);
+ prev = x.prev;
+ }
+ else
+ prev = this;
+
+ x.target = nullptr;
+ }
+ }
+
+ inline target_lock& target_lock::
+ operator= (target_lock&& x)
+ {
+ if (this != &x)
+ {
+ assert (target == nullptr);
+
+ action = x.action;
+ target = x.target;
+ offset = x.offset;
+
+ if (target != nullptr)
+ {
+ if (x.prev != &x)
+ {
+ const target_lock* cur (stack (this));
+ assert (cur == &x);
+ prev = x.prev;
+ }
+ else
+ prev = this;
+
+ x.target = nullptr;
+ }
+ }
+
+ return *this;
+ }
+
+ inline const target_lock*
+ dependency_cycle (action a, const target& t)
+ {
+ const target_lock* l (target_lock::stack ());
+
+ for (; l != nullptr; l = l->prev)
+ {
+ if (l->action == a && l->target == &t)
+ break;
+ }
+
+ return l;
+ }
+
+ inline target_lock
+ lock (action a, const target& t)
+ {
+ // We don't allow locking a target that has already been matched.
+ //
+ target_lock r (lock_impl (a, t, scheduler::work_none));
+ assert (!r ||
+ r.offset == target::offset_touched ||
+ r.offset == target::offset_tried);
+ return r;
+ }
+
+ inline target&
+ add_adhoc_member (target& t, const target_type& tt, const char* e)
+ {
+ string n (t.name);
+
+ if (e != nullptr)
+ {
+ n += '.';
+ n += e;
+ }
+
+ return add_adhoc_member (t, tt, t.dir, t.out, move (n));
+ }
+
+ inline target*
+ find_adhoc_member (target& g, const target_type& tt)
+ {
+ target* m (g.member);
+ for (; m != nullptr && !m->is_a (tt); m = m->member) ;
+ return m;
+ }
+
+ inline const target*
+ find_adhoc_member (const target& g, const target_type& tt)
+ {
+ const target* m (g.member);
+ for (; m != nullptr && !m->is_a (tt); m = m->member) ;
+ return m;
+ }
+
+ LIBBUILD2_SYMEXPORT const rule_match*
+ match_impl (action, target&, const rule* skip, bool try_match = false);
+
+ LIBBUILD2_SYMEXPORT recipe
+ apply_impl (action, target&, const rule_match&);
+
+ LIBBUILD2_SYMEXPORT pair<bool, target_state>
+ match (action, const target&, size_t, atomic_count*, bool try_match = false);
+
+ inline void
+ match_inc_dependens (action a, const target& t)
+ {
+ dependency_count.fetch_add (1, memory_order_relaxed);
+ t[a].dependents.fetch_add (1, memory_order_release);
+ }
+
+ inline target_state
+ match (action a, const target& t, bool fail)
+ {
+ assert (phase == run_phase::match);
+
+ target_state r (match (a, t, 0, nullptr).second);
+
+ if (r != target_state::failed)
+ match_inc_dependens (a, t);
+ else if (fail)
+ throw failed ();
+
+ return r;
+ }
+
+ inline pair<bool, target_state>
+ try_match (action a, const target& t, bool fail)
+ {
+ assert (phase == run_phase::match);
+
+ pair<bool, target_state> r (
+ match (a, t, 0, nullptr, true /* try_match */));
+
+ if (r.first)
+ {
+ if (r.second != target_state::failed)
+ match_inc_dependens (a, t);
+ else if (fail)
+ throw failed ();
+ }
+
+ return r;
+ }
+
+ inline bool
+ match (action a, const target& t, unmatch um)
+ {
+ assert (phase == run_phase::match);
+
+ target_state s (match (a, t, 0, nullptr).second);
+
+ if (s == target_state::failed)
+ throw failed ();
+
+ switch (um)
+ {
+ case unmatch::none: break;
+ case unmatch::unchanged:
+ {
+ if (s == target_state::unchanged)
+ return true;
+
+ break;
+ }
+ case unmatch::safe:
+ {
+ // Safe if unchanged or someone else is also a dependent (note that
+ // we never decrement this count during match so that someone else
+ // cannot change their mind).
+ //
+ if (s == target_state::unchanged ||
+ t[a].dependents.load (memory_order_consume) != 0)
+ return true;
+
+ break;
+ }
+ }
+
+ match_inc_dependens (a, t);
+ return false;
+ }
+
+ inline target_state
+ match_async (action a, const target& t,
+ size_t sc, atomic_count& tc,
+ bool fail)
+ {
+ assert (phase == run_phase::match);
+ target_state r (match (a, t, sc, &tc).second);
+
+ if (fail && !keep_going && r == target_state::failed)
+ throw failed ();
+
+ return r;
+ }
+
+ inline void
+ set_recipe (target_lock& l, recipe&& r)
+ {
+ target::opstate& s ((*l.target)[l.action]);
+
+ s.recipe = move (r);
+
+ // If this is a noop recipe, then mark the target unchanged to allow for
+ // some optimizations.
+ //
+ recipe_function** f (s.recipe.target<recipe_function*> ());
+
+ if (f != nullptr && *f == &noop_action)
+ s.state = target_state::unchanged;
+ else
+ {
+ s.state = target_state::unknown;
+
+ // This gets tricky when we start considering direct execution, etc. So
+ // here seems like the best place to do it.
+ //
+ // We also ignore the group recipe since group action means real recipe
+ // is in the group and so this feels right conceptually.
+ //
+ // We also avoid incrementing this count twice for the same target if we
+ // have both the inner and outer operations. In our model the outer
+ // operation is either noop or it must delegate to the inner. While it's
+ // possible the inner is noop while the outer is not, it is not very
+ // likely. The alternative (trying to "merge" the count keeping track of
+ // whether inner and/or outer is noop) gets hairy rather quickly.
+ //
+ if (l.action.inner ())
+ {
+ if (f == nullptr || *f != &group_action)
+ target_count.fetch_add (1, memory_order_relaxed);
+ }
+ }
+ }
+
+ inline void
+ match_recipe (target_lock& l, recipe r)
+ {
+ assert (phase == run_phase::match && l.target != nullptr);
+
+ (*l.target)[l.action].rule = nullptr; // No rule.
+ set_recipe (l, move (r));
+ l.offset = target::offset_applied;
+ }
+
+ inline recipe
+ match_delegate (action a, target& t, const rule& dr, bool try_match)
+ {
+ assert (phase == run_phase::match);
+
+ // Note: we don't touch any of the t[a] state since that was/will be set
+ // for the delegating rule.
+ //
+ const rule_match* r (match_impl (a, t, &dr, try_match));
+ return r != nullptr ? apply_impl (a, t, *r) : empty_recipe;
+ }
+
+ inline target_state
+ match_inner (action a, const target& t)
+ {
+ // In a sense this is like any other dependency.
+ //
+ assert (a.outer ());
+ return match (a.inner_action (), t);
+ }
+
+ inline bool
+ match_inner (action a, const target& t, unmatch um)
+ {
+ assert (a.outer ());
+ return match (a.inner_action (), t, um);
+ }
+
+ LIBBUILD2_SYMEXPORT group_view
+ resolve_members_impl (action, const target&, target_lock);
+
+ inline group_view
+ resolve_members (action a, const target& g)
+ {
+ group_view r;
+
+ if (a.outer ())
+ a = a.inner_action ();
+
+ // We can be called during execute though everything should have been
+ // already resolved.
+ //
+ switch (phase)
+ {
+ case run_phase::match:
+ {
+ // Grab a target lock to make sure the group state is synchronized.
+ //
+ target_lock l (lock_impl (a, g, scheduler::work_none));
+ r = g.group_members (a);
+
+ // If the group members are alrealy known or there is nothing else
+ // we can do, then unlock and return.
+ //
+ if (r.members == nullptr && l.offset != target::offset_executed)
+ r = resolve_members_impl (a, g, move (l));
+
+ break;
+ }
+ case run_phase::execute: r = g.group_members (a); break;
+ case run_phase::load: assert (false);
+ }
+
+ return r;
+ }
+
+ LIBBUILD2_SYMEXPORT void
+ resolve_group_impl (action, const target&, target_lock);
+
+ inline const target*
+ resolve_group (action a, const target& t)
+ {
+ if (a.outer ())
+ a = a.inner_action ();
+
+ switch (phase)
+ {
+ case run_phase::match:
+ {
+ // Grab a target lock to make sure the group state is synchronized.
+ //
+ target_lock l (lock_impl (a, t, scheduler::work_none));
+
+ // If the group is alrealy known or there is nothing else we can do,
+ // then unlock and return.
+ //
+ if (t.group == nullptr && l.offset < target::offset_tried)
+ resolve_group_impl (a, t, move (l));
+
+ break;
+ }
+ case run_phase::execute: break;
+ case run_phase::load: assert (false);
+ }
+
+ return t.group;
+ }
+
+ LIBBUILD2_SYMEXPORT void
+ match_prerequisites (action, target&, const match_search&, const scope*);
+
+ LIBBUILD2_SYMEXPORT void
+ match_prerequisite_members (action, target&,
+ const match_search_member&,
+ const scope*);
+
+ inline void
+ match_prerequisites (action a, target& t, const match_search& ms)
+ {
+ match_prerequisites (
+ a,
+ t,
+ ms,
+ (a.operation () != clean_id ? nullptr : &t.root_scope ()));
+ }
+
+ inline void
+ match_prerequisite_members (action a, target& t,
+ const match_search_member& msm)
+ {
+ if (a.operation () != clean_id)
+ match_prerequisite_members (a, t, msm, nullptr);
+ 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.
+ //
+ match_search ms (
+ msm
+ ? [&msm] (action a,
+ const target& t,
+ const prerequisite& p,
+ include_type i)
+ {
+ return msm (a, t, prerequisite_member {p, nullptr}, i);
+ }
+ : match_search ());
+
+ match_prerequisites (a, t, ms, &t.root_scope ());
+ }
+ }
+
+ inline void
+ match_prerequisites (action a, target& t, const scope& s)
+ {
+ match_prerequisites (a, t, nullptr, &s);
+ }
+
+ inline void
+ match_prerequisite_members (action a, target& t, const scope& s)
+ {
+ match_prerequisite_members (a, t, nullptr, &s);
+ }
+
+ LIBBUILD2_SYMEXPORT target_state
+ execute (action, const target&, size_t, atomic_count*);
+
+ inline target_state
+ execute (action a, const target& t)
+ {
+ return execute (a, t, 0, nullptr);
+ }
+
+ inline target_state
+ execute_wait (action a, const target& t)
+ {
+ if (execute (a, t) == target_state::busy)
+ sched.wait (target::count_executed (),
+ t[a].task_count,
+ scheduler::work_none);
+
+ return t.executed_state (a);
+ }
+
+ inline target_state
+ execute_async (action a, const target& t,
+ size_t sc, atomic_count& tc,
+ bool fail)
+ {
+ target_state r (execute (a, t, sc, &tc));
+
+ if (fail && !keep_going && r == target_state::failed)
+ throw failed ();
+
+ return r;
+ }
+
+ inline target_state
+ execute_delegate (const recipe& r, action a, const target& t)
+ {
+ return r (a, t);
+ }
+
+ inline target_state
+ execute_inner (action a, const target& t)
+ {
+ assert (a.outer ());
+ return execute_wait (a.inner_action (), t);
+ }
+
+ inline target_state
+ straight_execute_prerequisites (action a, const target& t,
+ size_t c, size_t s)
+ {
+ auto& p (t.prerequisite_targets[a]);
+ return straight_execute_members (a, t,
+ p.data (),
+ c == 0 ? p.size () - s: c,
+ s);
+ }
+
+ inline target_state
+ reverse_execute_prerequisites (action a, const target& t, size_t c)
+ {
+ auto& p (t.prerequisite_targets[a]);
+ return reverse_execute_members (a, t,
+ p.data (),
+ c == 0 ? p.size () : c,
+ p.size ());
+ }
+
+ inline target_state
+ execute_prerequisites (action a, const target& t, size_t c)
+ {
+ return current_mode == execution_mode::first
+ ? straight_execute_prerequisites (a, t, c)
+ : reverse_execute_prerequisites (a, t, c);
+ }
+
+ inline target_state
+ straight_execute_prerequisites_inner (action a, const target& t,
+ size_t c, size_t s)
+ {
+ assert (a.outer ());
+ auto& p (t.prerequisite_targets[a]);
+ return straight_execute_members (a.inner_action (),
+ t[a].task_count,
+ p.data (),
+ c == 0 ? p.size () - s : c,
+ s);
+ }
+
+ inline target_state
+ reverse_execute_prerequisites_inner (action a, const target& t, size_t c)
+ {
+ assert (a.outer ());
+ auto& p (t.prerequisite_targets[a]);
+ return reverse_execute_members (a.inner_action (),
+ t[a].task_count,
+ p.data (),
+ c == 0 ? p.size () : c,
+ p.size ());
+ }
+
+ inline target_state
+ execute_prerequisites_inner (action a, const target& t, size_t c)
+ {
+ return current_mode == execution_mode::first
+ ? straight_execute_prerequisites_inner (a, t, c)
+ : reverse_execute_prerequisites_inner (a, t, c);
+ }
+
+ // If the first argument is NULL, then the result is treated as a boolean
+ // value.
+ //
+ LIBBUILD2_SYMEXPORT pair<optional<target_state>, const target*>
+ execute_prerequisites (const target_type*,
+ action, const target&,
+ const timestamp&, const execute_filter&,
+ size_t);
+
+ inline optional<target_state>
+ execute_prerequisites (action a, const target& t,
+ const timestamp& mt, const execute_filter& ef,
+ size_t n)
+ {
+ return execute_prerequisites (nullptr, a, t, mt, ef, n).first;
+ }
+
+ template <typename T>
+ inline pair<optional<target_state>, const T&>
+ execute_prerequisites (action a, const target& t,
+ const timestamp& mt, const execute_filter& ef,
+ size_t n)
+ {
+ auto p (execute_prerequisites (T::static_type, a, t, mt, ef, n));
+ return pair<optional<target_state>, const T&> (
+ p.first, static_cast<const T&> (p.second));
+ }
+
+ inline pair<optional<target_state>, const target&>
+ execute_prerequisites (const target_type& tt,
+ action a, const target& t,
+ const timestamp& mt, const execute_filter& ef,
+ size_t n)
+ {
+ auto p (execute_prerequisites (&tt, a, t, mt, ef, n));
+ return pair<optional<target_state>, const target&> (p.first, *p.second);
+ }
+
+ template <typename T>
+ inline pair<optional<target_state>, const T&>
+ execute_prerequisites (const target_type& tt,
+ action a, const target& t,
+ const timestamp& mt, const execute_filter& ef,
+ size_t n)
+ {
+ auto p (execute_prerequisites (tt, a, t, mt, ef, n));
+ return pair<optional<target_state>, const T&> (
+ p.first, static_cast<const T&> (p.second));
+ }
+
+ inline target_state
+ execute_members (action a, const target& t, const target* ts[], size_t n)
+ {
+ return current_mode == execution_mode::first
+ ? straight_execute_members (a, t, ts, n, 0)
+ : reverse_execute_members (a, t, ts, n, n);
+ }
+}
diff --git a/libbuild2/buildfile b/libbuild2/buildfile
new file mode 100644
index 0000000..99f616c
--- /dev/null
+++ b/libbuild2/buildfile
@@ -0,0 +1,85 @@
+# file : libbuild2/buildfile
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+import int_libs = libbutl%lib{butl}
+
+./: lib{build2}: libul{build2}: {hxx ixx txx cxx}{** -config \
+ -version \
+ -**.test...} \
+ {hxx}{config version} \
+ $int_libs
+
+# Include the generated config and version headers into the distribution (so
+# that we don't pick up installed ones) and don't remove them when cleaning in
+# src (so that clean results in a state identical to distributed).
+#
+hxx{config}: in{config}
+hxx{version}: in{version} $src_root/manifest
+
+hxx{config version}:
+{
+ dist = true
+ clean = ($src_root != $out_root)
+}
+
+# Unit tests.
+#
+exe{*.test}:
+{
+ test = true
+ install = false
+}
+
+for t: cxx{**.test...}
+{
+ d = $directory($t)
+ n = $name($t)...
+ b = $path.base($name($t))
+
+ ./: $d/exe{$n}: $t $d/{hxx ixx txx}{+$n} $d/testscript{+$n +$b+*.test...}
+ $d/exe{$n}: libul{build2}: bin.whole = false
+}
+
+# Build options.
+#
+obja{*}: cxx.poptions += -DLIBBUILD2_STATIC_BUILD
+objs{*}: cxx.poptions += -DLIBBUILD2_SHARED_BUILD
+
+# Pass our compiler target to be used as libbuild2 host.
+#
+obj{context}: cxx.poptions += -DBUILD2_HOST_TRIPLET=\"$cxx.target\"
+obja{context}: cxx.poptions += -DLIBBUILD2_STATIC_BUILD
+objs{context}: cxx.poptions += -DLIBBUILD2_SHARED_BUILD
+
+if ($cxx.target.class != "windows")
+ cxx.libs += -lpthread
+
+# Export options.
+#
+lib{build2}:
+{
+ cxx.export.poptions = "-I$out_root" "-I$src_root"
+ cxx.export.libs = $int_libs
+}
+
+liba{build2}: cxx.export.poptions += -DLIBBUILD2_STATIC
+libs{build2}: cxx.export.poptions += -DLIBBUILD2_SHARED
+
+# For pre-releases use the complete version to make sure they cannot be used
+# in place of another pre-release or the final version. See the version module
+# for details on the version.* variable values.
+#
+if $version.pre_release
+ lib{build2}: bin.lib.version = @"-$version.project_id"
+else
+ lib{build2}: bin.lib.version = @"-$version.major.$version.minor"
+
+# Install into the libbuild2/ subdirectory of, say, /usr/include/
+# recreating subdirectories.
+#
+{hxx ixx txx}{*}:
+{
+ install = include/libbuild2/
+ install.subdirs = true
+}
diff --git a/libbuild2/config.hxx b/libbuild2/config.hxx
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/libbuild2/config.hxx
diff --git a/libbuild2/config.hxx.in b/libbuild2/config.hxx.in
new file mode 100644
index 0000000..62110da
--- /dev/null
+++ b/libbuild2/config.hxx.in
@@ -0,0 +1,37 @@
+// file : libbuild2/config.hxx.in -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+// This file is included by <libbuild2/types.hxx> so normally you don't need
+// to include it directly. Note that this file is included unprocessed (i.e.,
+// as an .in) during bootstrap.
+//
+// Also, note that some BUILD_* configuration macros are passed directly from
+// the buildfile with the -D options.
+
+#ifndef LIBBUILD2_CONFIG_HXX
+#define LIBBUILD2_CONFIG_HXX
+
+// Currently the value is adjusted manually during release but in the future
+// the idea is to use version metadata (e.g., 1.2.3-a.1+0.stage). This way it
+// will all be managed in a central place (manifest), we can teach the version
+// module to extract it, and we can also set it for the other packages in the
+// toolchain. Bootstrap will be a problem though. (Maybe set it to nullptr and
+// say that it shall not be queried?)
+//
+#define LIBBUILD2_STAGE true
+
+// Modification time sanity checks are by default only enabled for the staged
+// version but this can be overridden at runtime with --[no-]mtime-check.
+//
+#if LIBBUILD2_STAGE
+# define LIBBUILD2_MTIME_CHECK true
+#else
+# define LIBBUILD2_MTIME_CHECK false
+#endif
+
+#ifdef BUILD2_BOOTSTRAP
+#else
+#endif
+
+#endif // LIBBUILD2_CONFIG_HXX
diff --git a/libbuild2/context.cxx b/libbuild2/context.cxx
new file mode 100644
index 0000000..d56abb3
--- /dev/null
+++ b/libbuild2/context.cxx
@@ -0,0 +1,1026 @@
+// file : libbuild2/context.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <libbuild2/context.hxx>
+
+#include <sstream>
+#include <exception> // uncaught_exception[s]()
+
+#include <libbuild2/rule.hxx>
+#include <libbuild2/scope.hxx>
+#include <libbuild2/target.hxx>
+#include <libbuild2/diagnostics.hxx>
+
+#include <libbutl/ft/exception.hxx> // uncaught_exceptions
+
+// For command line variable parsing.
+//
+#include <libbuild2/token.hxx>
+#include <libbuild2/lexer.hxx>
+#include <libbuild2/parser.hxx>
+
+using namespace std;
+using namespace butl;
+
+namespace build2
+{
+ scheduler sched;
+
+ run_phase phase;
+ phase_mutex phase_mutex::instance;
+
+ size_t load_generation;
+
+ bool phase_mutex::
+ lock (run_phase p)
+ {
+ bool r;
+
+ {
+ mlock l (m_);
+ bool u (lc_ == 0 && mc_ == 0 && ec_ == 0); // Unlocked.
+
+ // Increment the counter.
+ //
+ condition_variable* v (nullptr);
+ switch (p)
+ {
+ case run_phase::load: lc_++; v = &lv_; break;
+ case run_phase::match: mc_++; v = &mv_; break;
+ case run_phase::execute: ec_++; v = &ev_; break;
+ }
+
+ // If unlocked, switch directly to the new phase. Otherwise wait for the
+ // phase switch. Note that in the unlocked case we don't need to notify
+ // since there is nobody waiting (all counters are zero).
+ //
+ if (u)
+ {
+ phase = p;
+ r = !fail_;
+ }
+ else if (phase != p)
+ {
+ sched.deactivate ();
+ for (; phase != p; v->wait (l)) ;
+ r = !fail_;
+ l.unlock (); // Important: activate() can block.
+ sched.activate ();
+ }
+ else
+ r = !fail_;
+ }
+
+ // In case of load, acquire the exclusive access mutex.
+ //
+ if (p == run_phase::load)
+ {
+ lm_.lock ();
+ r = !fail_; // Re-query.
+ }
+
+ return r;
+ }
+
+ void phase_mutex::
+ unlock (run_phase p)
+ {
+ // In case of load, release the exclusive access mutex.
+ //
+ if (p == run_phase::load)
+ lm_.unlock ();
+
+ {
+ mlock l (m_);
+
+ // Decrement the counter and see if this phase has become unlocked.
+ //
+ bool u (false);
+ switch (p)
+ {
+ case run_phase::load: u = (--lc_ == 0); break;
+ case run_phase::match: u = (--mc_ == 0); break;
+ case run_phase::execute: u = (--ec_ == 0); break;
+ }
+
+ // If the phase is unlocked, pick a new phase and notify the waiters.
+ // Note that we notify all load waiters so that they can all serialize
+ // behind the second-level mutex.
+ //
+ if (u)
+ {
+ condition_variable* v;
+
+ if (lc_ != 0) {phase = run_phase::load; v = &lv_;}
+ else if (mc_ != 0) {phase = run_phase::match; v = &mv_;}
+ else if (ec_ != 0) {phase = run_phase::execute; v = &ev_;}
+ else {phase = run_phase::load; v = nullptr;}
+
+ if (v != nullptr)
+ {
+ l.unlock ();
+ v->notify_all ();
+ }
+ }
+ }
+ }
+
+ bool phase_mutex::
+ relock (run_phase o, run_phase n)
+ {
+ // Pretty much a fused unlock/lock implementation except that we always
+ // switch into the new phase.
+ //
+ assert (o != n);
+
+ bool r;
+
+ if (o == run_phase::load)
+ lm_.unlock ();
+
+ {
+ mlock l (m_);
+ bool u (false);
+
+ switch (o)
+ {
+ case run_phase::load: u = (--lc_ == 0); break;
+ case run_phase::match: u = (--mc_ == 0); break;
+ case run_phase::execute: u = (--ec_ == 0); break;
+ }
+
+ // Set if will be waiting or notifying others.
+ //
+ condition_variable* v (nullptr);
+ switch (n)
+ {
+ case run_phase::load: v = lc_++ != 0 || !u ? &lv_ : nullptr; break;
+ case run_phase::match: v = mc_++ != 0 || !u ? &mv_ : nullptr; break;
+ case run_phase::execute: v = ec_++ != 0 || !u ? &ev_ : nullptr; break;
+ }
+
+ if (u)
+ {
+ phase = n;
+ r = !fail_;
+
+ // Notify others that could be waiting for this phase.
+ //
+ if (v != nullptr)
+ {
+ l.unlock ();
+ v->notify_all ();
+ }
+ }
+ else // phase != n
+ {
+ sched.deactivate ();
+ for (; phase != n; v->wait (l)) ;
+ r = !fail_;
+ l.unlock (); // Important: activate() can block.
+ sched.activate ();
+ }
+ }
+
+ if (n == run_phase::load)
+ {
+ lm_.lock ();
+ r = !fail_; // Re-query.
+ }
+
+ return r;
+ }
+
+ // C++17 deprecated uncaught_exception() so use uncaught_exceptions() if
+ // available.
+ //
+ static inline bool
+ uncaught_exception ()
+ {
+#ifdef __cpp_lib_uncaught_exceptions
+ return std::uncaught_exceptions () != 0;
+#else
+ return std::uncaught_exception ();
+#endif
+ }
+
+ // phase_lock
+ //
+ static
+#ifdef __cpp_thread_local
+ thread_local
+#else
+ __thread
+#endif
+ phase_lock* phase_lock_instance;
+
+ phase_lock::
+ phase_lock (run_phase p)
+ : p (p)
+ {
+ if (phase_lock* l = phase_lock_instance)
+ assert (l->p == p);
+ else
+ {
+ if (!phase_mutex::instance.lock (p))
+ {
+ phase_mutex::instance.unlock (p);
+ throw failed ();
+ }
+
+ phase_lock_instance = this;
+
+ //text << this_thread::get_id () << " phase acquire " << p;
+ }
+ }
+
+ phase_lock::
+ ~phase_lock ()
+ {
+ if (phase_lock_instance == this)
+ {
+ phase_lock_instance = nullptr;
+ phase_mutex::instance.unlock (p);
+
+ //text << this_thread::get_id () << " phase release " << p;
+ }
+ }
+
+ // phase_unlock
+ //
+ phase_unlock::
+ phase_unlock (bool u)
+ : l (u ? phase_lock_instance : nullptr)
+ {
+ if (u)
+ {
+ phase_lock_instance = nullptr;
+ phase_mutex::instance.unlock (l->p);
+
+ //text << this_thread::get_id () << " phase unlock " << l->p;
+ }
+ }
+
+ phase_unlock::
+ ~phase_unlock () noexcept (false)
+ {
+ if (l != nullptr)
+ {
+ bool r (phase_mutex::instance.lock (l->p));
+ phase_lock_instance = l;
+
+ // Fail unless we are already failing. Note that we keep the phase
+ // locked since there will be phase_lock down the stack to unlock it.
+ //
+ if (!r && !uncaught_exception ())
+ throw failed ();
+
+ //text << this_thread::get_id () << " phase lock " << l->p;
+ }
+ }
+
+ // phase_switch
+ //
+ phase_switch::
+ phase_switch (run_phase n)
+ : o (phase), n (n)
+ {
+ if (!phase_mutex::instance.relock (o, n))
+ {
+ phase_mutex::instance.relock (n, o);
+ throw failed ();
+ }
+
+ phase_lock_instance->p = n;
+
+ if (n == run_phase::load) // Note: load lock is exclusive.
+ load_generation++;
+
+ //text << this_thread::get_id () << " phase switch " << o << " " << n;
+ }
+
+ phase_switch::
+ ~phase_switch () noexcept (false)
+ {
+ // If we are coming off a failed load phase, mark the phase_mutex as
+ // failed to terminate all other threads since the build state may no
+ // longer be valid.
+ //
+ if (n == run_phase::load && uncaught_exception ())
+ {
+ mlock l (phase_mutex::instance.m_);
+ phase_mutex::instance.fail_ = true;
+ }
+
+ bool r (phase_mutex::instance.relock (n, o));
+ phase_lock_instance->p = o;
+
+ // Similar logic to ~phase_unlock().
+ //
+ if (!r && !uncaught_exception ())
+ throw failed ();
+
+ //text << this_thread::get_id () << " phase restore " << n << " " << o;
+ }
+
+ const variable* var_src_root;
+ const variable* var_out_root;
+ const variable* var_src_base;
+ const variable* var_out_base;
+ const variable* var_forwarded;
+
+ const variable* var_project;
+ const variable* var_amalgamation;
+ const variable* var_subprojects;
+ const variable* var_version;
+
+ const variable* var_project_url;
+ const variable* var_project_summary;
+
+ const variable* var_import_target;
+
+ const variable* var_clean;
+ const variable* var_backlink;
+ const variable* var_include;
+
+ const char var_extension[10] = "extension";
+
+ const variable* var_build_meta_operation;
+
+ string current_mname;
+ string current_oname;
+
+ const meta_operation_info* current_mif;
+ const operation_info* current_inner_oif;
+ const operation_info* current_outer_oif;
+ size_t current_on;
+ execution_mode current_mode;
+ bool current_diag_noise;
+
+ atomic_count dependency_count;
+ atomic_count target_count;
+ atomic_count skip_count;
+
+ bool keep_going = false;
+ bool dry_run = false;
+
+ void (*config_save_variable) (scope&, const variable&, uint64_t);
+
+ const string& (*config_preprocess_create) (const variable_overrides&,
+ values&,
+ vector_view<opspec>&,
+ bool,
+ const location&);
+
+ variable_overrides
+ reset (const strings& cmd_vars)
+ {
+ tracer trace ("reset");
+
+ // @@ Need to unload modules when we dynamically load them.
+ //
+
+ l6 ([&]{trace << "resetting build state";});
+
+ auto& vp (variable_pool::instance);
+ auto& sm (scope_map::instance);
+
+ variable_overrides vos;
+
+ targets.clear ();
+ sm.clear ();
+ vp.clear ();
+
+ // Reset meta/operation tables. Note that the order should match the id
+ // constants in <libbuild2/operation.hxx>.
+ //
+ meta_operation_table.clear ();
+ meta_operation_table.insert ("noop");
+ meta_operation_table.insert ("perform");
+ meta_operation_table.insert ("configure");
+ meta_operation_table.insert ("disfigure");
+
+ if (config_preprocess_create != nullptr)
+ meta_operation_table.insert (
+ meta_operation_data ("create", config_preprocess_create));
+
+ meta_operation_table.insert ("dist");
+ meta_operation_table.insert ("info");
+
+ operation_table.clear ();
+ operation_table.insert ("default");
+ operation_table.insert ("update");
+ operation_table.insert ("clean");
+ operation_table.insert ("test");
+ operation_table.insert ("update-for-test");
+ operation_table.insert ("install");
+ operation_table.insert ("uninstall");
+ operation_table.insert ("update-for-install");
+
+ // Create global scope. Note that the empty path is a prefix for any other
+ // path. See the comment in <libbutl/prefix-map.mxx> for details.
+ //
+ auto make_global_scope = [] () -> scope&
+ {
+ auto i (scope_map::instance.insert (dir_path ()));
+ scope& r (i->second);
+ r.out_path_ = &i->first;
+ global_scope = scope::global_ = &r;
+ return r;
+ };
+
+ scope& gs (make_global_scope ());
+
+ // Setup the global scope before parsing any variable overrides since they
+ // may reference these things.
+ //
+
+ gs.assign<dir_path> ("build.work") = work;
+ gs.assign<dir_path> ("build.home") = home;
+
+ // Build system driver process path.
+ //
+ gs.assign<process_path> ("build.path") =
+ process_path (nullptr, // Will be filled by value assignment.
+ path (argv0.recall_string ()),
+ path (argv0.effect));
+
+ // Build system verbosity level.
+ //
+ gs.assign<uint64_t> ("build.verbosity") = verb;
+
+ // Build system version (similar to what we do in the version module
+ // except here we don't include package epoch/revision).
+ //
+ {
+ const standard_version& v (build_version);
+
+ auto set = [&gs] (const char* var, auto val)
+ {
+ using T = decltype (val);
+ gs.assign (variable_pool::instance.insert<T> (var)) = move (val);
+ };
+
+ set ("build.version", v.string_project ());
+
+ set ("build.version.number", v.version);
+ set ("build.version.id", v.string_project_id ());
+
+ set ("build.version.major", uint64_t (v.major ()));
+ set ("build.version.minor", uint64_t (v.minor ()));
+ set ("build.version.patch", uint64_t (v.patch ()));
+
+ optional<uint16_t> a (v.alpha ());
+ optional<uint16_t> b (v.beta ());
+
+ set ("build.version.alpha", a.has_value ());
+ set ("build.version.beta", b.has_value ());
+ set ("build.version.pre_release", v.pre_release ().has_value ());
+ set ("build.version.pre_release_string", v.string_pre_release ());
+ set ("build.version.pre_release_number", uint64_t (a ? *a : b ? *b : 0));
+
+ set ("build.version.snapshot", v.snapshot ()); // bool
+ set ("build.version.snapshot_sn", v.snapshot_sn); // uint64
+ set ("build.version.snapshot_id", v.snapshot_id); // string
+ set ("build.version.snapshot_string", v.string_snapshot ());
+
+ // Allow detection (for example, in tests) whether this is a staged
+ // toolchain.
+ //
+ // Note that it is either staged or public, without queued, since we do
+ // not re-package things during the queued-to-public transition.
+ //
+ set ("build.version.stage", LIBBUILD2_STAGE);
+ }
+
+ // Enter the host information. Rather than jumping through hoops like
+ // config.guess, for now we are just going to use the compiler target we
+ // were built with. While it is not as precise (for example, a binary
+ // built for i686 might be running on x86_64), it is good enough of an
+ // approximation/fallback since most of the time we are interested in just
+ // the target class (e.g., linux, windows, macosx).
+ //
+ {
+ // Did the user ask us to use config.guess?
+ //
+ string orig (config_guess
+ ? run<string> (3,
+ *config_guess,
+ [](string& l, bool) {return move (l);})
+ : BUILD2_HOST_TRIPLET);
+
+ l5 ([&]{trace << "original host: '" << orig << "'";});
+
+ try
+ {
+ target_triplet t (orig);
+
+ l5 ([&]{trace << "canonical host: '" << t.string () << "'; "
+ << "class: " << t.class_;});
+
+ // Also enter as build.host.{cpu,vendor,system,version,class} for
+ // convenience of access.
+ //
+ gs.assign<string> ("build.host.cpu") = t.cpu;
+ gs.assign<string> ("build.host.vendor") = t.vendor;
+ gs.assign<string> ("build.host.system") = t.system;
+ gs.assign<string> ("build.host.version") = t.version;
+ gs.assign<string> ("build.host.class") = t.class_;
+
+ gs.assign<target_triplet> ("build.host") = move (t);
+ }
+ catch (const invalid_argument& e)
+ {
+ fail << "unable to parse build host '" << orig << "': " << e <<
+ info << "consider using the --config-guess option";
+ }
+ }
+
+ // Register builtin target types.
+ //
+ {
+ target_type_map& t (gs.target_types);
+
+ t.insert<file> ();
+ t.insert<alias> ();
+ t.insert<dir> ();
+ t.insert<fsdir> ();
+ t.insert<exe> ();
+ t.insert<doc> ();
+ t.insert<man> ();
+ t.insert<man1> ();
+
+ {
+ auto& tt (t.insert<manifest> ());
+ t.insert_file ("manifest", tt);
+ }
+
+ {
+ auto& tt (t.insert<buildfile> ());
+ t.insert_file ("buildfile", tt);
+ }
+ }
+
+ // Parse and enter the command line variables. We do it before entering
+ // any other variables so that all the variables that are overriden are
+ // marked as such first. Then, as we enter variables, we can verify that
+ // the override is alowed.
+ //
+ for (size_t i (0); i != cmd_vars.size (); ++i)
+ {
+ const string& s (cmd_vars[i]);
+
+ istringstream is (s);
+ is.exceptions (istringstream::failbit | istringstream::badbit);
+
+ // Similar to buildspec we do "effective escaping" and only for ['"\$(]
+ // (basically what's necessary inside a double-quoted literal plus the
+ // single quote).
+ //
+ lexer l (is, path ("<cmdline>"), 1 /* line */, "\'\"\\$(");
+
+ // At the buildfile level the scope-specific variable should be
+ // separated from the directory with a whitespace, for example:
+ //
+ // ./ foo=$bar
+ //
+ // However, requiring this for command line variables would be too
+ // inconvinient so we support both.
+ //
+ // We also have the optional visibility modifier as a first character of
+ // the variable name:
+ //
+ // ! - global
+ // % - project
+ // / - scope
+ //
+ // The last one clashes a bit with the directory prefix:
+ //
+ // ./ /foo=bar
+ // .//foo=bar
+ //
+ // But that's probably ok (the need for a scope-qualified override with
+ // scope visibility should be pretty rare). Note also that to set the
+ // value on the global scope we use !.
+ //
+ // And so the first token should be a word which can be either a
+ // variable name (potentially with the directory qualification) or just
+ // the directory, in which case it should be followed by another word
+ // (unqualified variable name).
+ //
+ token t (l.next ());
+
+ optional<dir_path> dir;
+ if (t.type == token_type::word)
+ {
+ string& v (t.value);
+ size_t p (path::traits_type::rfind_separator (v));
+
+ if (p != string::npos && p != 0) // If first then visibility.
+ {
+ if (p == v.size () - 1)
+ {
+ // Separate directory.
+ //
+ dir = dir_path (move (v));
+ t = l.next ();
+
+ // Target-specific overrides are not yet supported (and probably
+ // never will be; the beast is already complex enough).
+ //
+ if (t.type == token_type::colon)
+ fail << "'" << s << "' is a target-specific override" <<
+ info << "use double '--' to treat this argument as buildspec";
+ }
+ else
+ {
+ // Combined directory.
+ //
+ // If double separator (visibility marker), then keep the first in
+ // name.
+ //
+ if (p != 0 && path::traits_type::is_separator (v[p - 1]))
+ --p;
+
+ dir = dir_path (t.value, 0, p + 1); // Include the separator.
+ t.value.erase (0, p + 1); // Erase the separator.
+ }
+
+ if (dir->relative ())
+ {
+ // Handle the special relative to base scope case (.../).
+ //
+ auto i (dir->begin ());
+
+ if (*i == "...")
+ dir = dir_path (++i, dir->end ()); // Note: can become empty.
+ else
+ dir->complete (); // Relative to CWD.
+ }
+
+ if (dir->absolute ())
+ dir->normalize ();
+ }
+ }
+
+ token_type tt (l.next ().type);
+
+ // The token should be the variable name followed by =, +=, or =+.
+ //
+ if (t.type != token_type::word || t.value.empty () ||
+ (tt != token_type::assign &&
+ tt != token_type::prepend &&
+ tt != token_type::append))
+ {
+ fail << "expected variable assignment instead of '" << s << "'" <<
+ info << "use double '--' to treat this argument as buildspec";
+ }
+
+ // Take care of the visibility. Note that here we rely on the fact that
+ // none of these characters are lexer's name separators.
+ //
+ char c (t.value[0]);
+
+ if (path::traits_type::is_separator (c))
+ c = '/'; // Normalize.
+
+ string n (t.value, c == '!' || c == '%' || c == '/' ? 1 : 0);
+
+ if (c == '!' && dir)
+ fail << "scope-qualified global override of variable " << n;
+
+ variable& var (const_cast<variable&> (
+ vp.insert (n, true /* overridable */)));
+
+ const variable* o;
+ {
+ variable_visibility v (c == '/' ? variable_visibility::scope :
+ c == '%' ? variable_visibility::project :
+ variable_visibility::normal);
+
+ const char* k (tt == token_type::assign ? "__override" :
+ tt == token_type::append ? "__suffix" : "__prefix");
+
+ unique_ptr<variable> p (
+ new variable {
+ n + '.' + to_string (i + 1) + '.' + k,
+ nullptr /* aliases */,
+ nullptr /* type */,
+ nullptr /* overrides */,
+ v});
+
+ // Back link.
+ //
+ p->aliases = p.get ();
+ if (var.overrides != nullptr)
+ swap (p->aliases,
+ const_cast<variable*> (var.overrides.get ())->aliases);
+
+ // Forward link.
+ //
+ p->overrides = move (var.overrides);
+ var.overrides = move (p);
+
+ o = var.overrides.get ();
+ }
+
+ // Currently we expand project overrides in the global scope to keep
+ // things simple. Pass original variable for diagnostics. Use current
+ // working directory as pattern base.
+ //
+ parser p;
+ pair<value, token> r (p.parse_variable_value (l, gs, &work, var));
+
+ if (r.second.type != token_type::eos)
+ fail << "unexpected " << r.second << " in variable assignment "
+ << "'" << s << "'";
+
+ // Make sure the value is not typed.
+ //
+ if (r.first.type != nullptr)
+ fail << "typed override of variable " << n;
+
+ // Global and absolute scope overrides we can enter directly. Project
+ // and relative scope ones will be entered by the caller for each
+ // amalgamation/project.
+ //
+ if (c == '!' || (dir && dir->absolute ()))
+ {
+ scope& s (c == '!' ? gs : sm.insert (*dir)->second);
+
+ auto p (s.vars.insert (*o));
+ assert (p.second); // Variable name is unique.
+
+ value& v (p.first);
+ v = move (r.first);
+ }
+ else
+ vos.push_back (
+ variable_override {var, *o, move (dir), move (r.first)});
+ }
+
+ // Enter builtin variables and patterns.
+ //
+
+ // All config. variables are by default overridable.
+ //
+ vp.insert_pattern ("config.**", nullopt, true, nullopt, true, false);
+
+ // file.cxx:import() (note that order is important; see insert_pattern()).
+ //
+ vp.insert_pattern<abs_dir_path> (
+ "config.import.*", true, variable_visibility::normal, true);
+ vp.insert_pattern<path> (
+ "config.import.**", true, variable_visibility::normal, true);
+
+ // module.cxx:load_module().
+ //
+ {
+ auto v_p (variable_visibility::project);
+
+ vp.insert_pattern<bool> ("**.booted", false, v_p);
+ vp.insert_pattern<bool> ("**.loaded", false, v_p);
+ vp.insert_pattern<bool> ("**.configured", false, v_p);
+ }
+
+ {
+ auto v_p (variable_visibility::project);
+ auto v_t (variable_visibility::target);
+ auto v_q (variable_visibility::prereq);
+
+ var_src_root = &vp.insert<dir_path> ("src_root");
+ var_out_root = &vp.insert<dir_path> ("out_root");
+ var_src_base = &vp.insert<dir_path> ("src_base");
+ var_out_base = &vp.insert<dir_path> ("out_base");
+
+ var_forwarded = &vp.insert<bool> ("forwarded", v_p);
+
+ // Note that subprojects is not typed since the value requires
+ // pre-processing (see file.cxx).
+ //
+ var_project = &vp.insert<project_name> ("project", v_p);
+ var_amalgamation = &vp.insert<dir_path> ("amalgamation", v_p);
+ var_subprojects = &vp.insert ("subprojects", v_p);
+ var_version = &vp.insert<string> ("version", v_p);
+
+ var_project_url = &vp.insert<string> ("project.url", v_p);
+ var_project_summary = &vp.insert<string> ("project.summary", v_p);
+
+ var_import_target = &vp.insert<name> ("import.target");
+
+ var_clean = &vp.insert<bool> ("clean", v_t);
+ var_backlink = &vp.insert<string> ("backlink", v_t);
+ var_include = &vp.insert<string> ("include", v_q);
+
+ vp.insert<string> (var_extension, v_t);
+
+ // Backlink executables and (generated) documentation by default.
+ //
+ gs.target_vars[exe::static_type]["*"].assign (var_backlink) = "true";
+ gs.target_vars[doc::static_type]["*"].assign (var_backlink) = "true";
+
+ var_build_meta_operation = &vp.insert<string> ("build.meta_operation");
+ }
+
+ // Register builtin rules.
+ //
+ {
+ rule_map& r (gs.rules); // Note: global scope!
+
+ //@@ outer
+ 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<mtime_target> (perform_update_id, "file", file_rule::instance);
+ r.insert<mtime_target> (perform_clean_id, "file", file_rule::instance);
+ }
+
+ return vos;
+ }
+
+ dir_path
+ src_out (const dir_path& out, const scope& r)
+ {
+ assert (r.root ());
+ return src_out (out, r.out_path (), r.src_path ());
+ }
+
+ dir_path
+ out_src (const dir_path& src, const scope& r)
+ {
+ assert (r.root ());
+ return out_src (src, r.out_path (), r.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);
+ }
+
+ // diag_do(), etc.
+ //
+ string
+ diag_do (const action&)
+ {
+ const meta_operation_info& m (*current_mif);
+ const operation_info& io (*current_inner_oif);
+ const operation_info* oo (current_outer_oif);
+
+ string r;
+
+ // perform(update(x)) -> "update x"
+ // configure(update(x)) -> "configure updating x"
+ //
+ if (m.name_do.empty ())
+ r = io.name_do;
+ else
+ {
+ r = m.name_do;
+
+ if (io.name_doing[0] != '\0')
+ {
+ r += ' ';
+ r += io.name_doing;
+ }
+ }
+
+ if (oo != nullptr)
+ {
+ r += " (for ";
+ r += oo->name;
+ r += ')';
+ }
+
+ return r;
+ }
+
+ void
+ diag_do (ostream& os, const action& a, const target& t)
+ {
+ os << diag_do (a) << ' ' << t;
+ }
+
+ string
+ diag_doing (const action&)
+ {
+ const meta_operation_info& m (*current_mif);
+ const operation_info& io (*current_inner_oif);
+ const operation_info* oo (current_outer_oif);
+
+ string r;
+
+ // perform(update(x)) -> "updating x"
+ // configure(update(x)) -> "configuring updating x"
+ //
+ if (!m.name_doing.empty ())
+ r = m.name_doing;
+
+ if (io.name_doing[0] != '\0')
+ {
+ if (!r.empty ()) r += ' ';
+ r += io.name_doing;
+ }
+
+ if (oo != nullptr)
+ {
+ r += " (for ";
+ r += oo->name;
+ r += ')';
+ }
+
+ return r;
+ }
+
+ void
+ diag_doing (ostream& os, const action& a, const target& t)
+ {
+ os << diag_doing (a) << ' ' << t;
+ }
+
+ string
+ diag_did (const action&)
+ {
+ const meta_operation_info& m (*current_mif);
+ const operation_info& io (*current_inner_oif);
+ const operation_info* oo (current_outer_oif);
+
+ string r;
+
+ // perform(update(x)) -> "updated x"
+ // configure(update(x)) -> "configured updating x"
+ //
+ if (!m.name_did.empty ())
+ {
+ r = m.name_did;
+
+ if (io.name_doing[0] != '\0')
+ {
+ r += ' ';
+ r += io.name_doing;
+ }
+ }
+ else
+ r += io.name_did;
+
+ if (oo != nullptr)
+ {
+ r += " (for ";
+ r += oo->name;
+ r += ')';
+ }
+
+ return r;
+ }
+
+ void
+ diag_did (ostream& os, const action& a, const target& t)
+ {
+ os << diag_did (a) << ' ' << t;
+ }
+
+ void
+ diag_done (ostream& os, 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);
+
+ // 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[0] != '\0')
+ os << ' ' << io.name_done;
+
+ if (oo != nullptr)
+ os << " (for " << oo->name << ')';
+ }
+ else
+ {
+ if (io.name_doing[0] != '\0')
+ os << io.name_doing << ' ';
+
+ if (oo != nullptr)
+ os << "(for " << oo->name << ") ";
+
+ os << t << ' ' << m.name_done;
+ }
+ }
+}
diff --git a/libbuild2/context.hxx b/libbuild2/context.hxx
new file mode 100644
index 0000000..66874e7
--- /dev/null
+++ b/libbuild2/context.hxx
@@ -0,0 +1,572 @@
+// file : libbuild2/context.hxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef LIBBUILD2_CONTEXT_HXX
+#define LIBBUILD2_CONTEXT_HXX
+
+#include <libbuild2/types.hxx>
+#include <libbuild2/utility.hxx>
+
+#include <libbuild2/scope.hxx>
+#include <libbuild2/variable.hxx>
+#include <libbuild2/operation.hxx>
+#include <libbuild2/scheduler.hxx>
+
+#include <libbuild2/export.hxx>
+
+namespace build2
+{
+ // Main (and only) scheduler. Started up and shut down in main().
+ //
+ LIBBUILD2_SYMEXPORT extern scheduler sched;
+
+ // In order to perform each operation the build system goes through the
+ // following phases:
+ //
+ // load - load the buildfiles
+ // match - search prerequisites and match rules
+ // execute - execute the matched rule
+ //
+ // The build system starts with a "serial load" phase and then continues
+ // with parallel match and execute. Match, however, can be interrupted
+ // both with load and execute.
+ //
+ // Match can be interrupted with "exclusive load" in order to load
+ // additional buildfiles. Similarly, it can be interrupted with (parallel)
+ // execute in order to build targetd required to complete the match (for
+ // example, generated source code or source code generators themselves).
+ //
+ // Such interruptions are performed by phase change that is protected by
+ // phase_mutex (which is also used to synchronize the state changes between
+ // phases).
+ //
+ // Serial load can perform arbitrary changes to the build state. Exclusive
+ // load, however, can only perform "island appends". That is, it can create
+ // new "nodes" (variables, scopes, etc) but not (semantically) change
+ // already existing nodes or invalidate any references to such (the idea
+ // here is that one should be able to load additional buildfiles as long as
+ // they don't interfere with the existing build state). The "islands" are
+ // identified by the load_generation number (0 for the initial/serial
+ // load). It is incremented in case of a phase switch and can be stored in
+ // various "nodes" to verify modifications are only done "within the
+ // islands".
+ //
+ LIBBUILD2_SYMEXPORT extern run_phase phase;
+ LIBBUILD2_SYMEXPORT extern size_t load_generation;
+
+ // A "tri-mutex" that keeps all the threads in one of the three phases. When
+ // a thread wants to switch a phase, it has to wait for all the other
+ // threads to do the same (or release their phase locks). The load phase is
+ // exclusive.
+ //
+ // The interleaving match and execute is interesting: during match we read
+ // the "external state" (e.g., filesystem entries, modifications times, etc)
+ // and capture it in the "internal state" (our dependency graph). During
+ // execute we are modifying the external state with controlled modifications
+ // of the internal state to reflect the changes (e.g., update mtimes). If
+ // you think about it, it's pretty clear that we cannot safely perform both
+ // of these actions simultaneously. A good example would be running a code
+ // generator and header dependency extraction simultaneously: the extraction
+ // process may pick up headers as they are being generated. As a result, we
+ // either have everyone treat the external state as read-only or write-only.
+ //
+ // There is also one more complication: if we are returning from a load
+ // phase that has failed, then the build state could be seriously messed up
+ // (things like scopes not being setup completely, etc). And once we release
+ // the lock, other threads that are waiting will start relying on this
+ // messed up state. So a load phase can mark the phase_mutex as failed in
+ // which case all currently blocked and future lock()/relock() calls return
+ // false. Note that in this case we still switch to the desired phase. See
+ // the phase_{lock,switch,unlock} implementations for details.
+ //
+ class LIBBUILD2_SYMEXPORT phase_mutex
+ {
+ public:
+ // Acquire a phase lock potentially blocking (unless already in the
+ // desired phase) until switching to the desired phase is possible.
+ //
+ bool
+ lock (run_phase);
+
+ // Release the phase lock potentially allowing (unless there are other
+ // locks on this phase) switching to a different phase.
+ //
+ void
+ unlock (run_phase);
+
+ // Switch from one phase to another. Semantically, just unlock() followed
+ // by lock() but more efficient.
+ //
+ bool
+ relock (run_phase unlock, run_phase lock);
+
+ private:
+ friend struct phase_lock;
+ friend struct phase_unlock;
+ friend struct phase_switch;
+
+ phase_mutex ()
+ : fail_ (false), lc_ (0), mc_ (0), ec_ (0)
+ {
+ phase = run_phase::load;
+ }
+
+ static phase_mutex instance;
+
+ private:
+ // We have a counter for each phase which represents the number of threads
+ // in or waiting for this phase.
+ //
+ // We use condition variables to wait for a phase switch. The load phase
+ // is exclusive so we have a separate mutex to serialize it (think of it
+ // as a second level locking).
+ //
+ // When the mutex is unlocked (all three counters become zero, the phase
+ // is always changed to load (this is also the initial state).
+ //
+ mutex m_;
+
+ bool fail_;
+
+ size_t lc_;
+ size_t mc_;
+ size_t ec_;
+
+ condition_variable lv_;
+ condition_variable mv_;
+ condition_variable ev_;
+
+ mutex lm_;
+ };
+
+ // Grab a new phase lock releasing it on destruction. The lock can be
+ // "owning" or "referencing" (recursive).
+ //
+ // On the referencing semantics: If there is already an instance of
+ // phase_lock in this thread, then the new instance simply references it.
+ //
+ // The reason for this semantics is to support the following scheduling
+ // pattern (in actual code we use wait_guard to RAII it):
+ //
+ // atomic_count task_count (0);
+ //
+ // {
+ // phase_lock l (run_phase::match); // (1)
+ //
+ // for (...)
+ // {
+ // sched.async (task_count,
+ // [] (...)
+ // {
+ // phase_lock pl (run_phase::match); // (2)
+ // ...
+ // },
+ // ...);
+ // }
+ // }
+ //
+ // sched.wait (task_count); // (3)
+ //
+ // Here is what's going on here:
+ //
+ // 1. We first get a phase lock "for ourselves" since after the first
+ // iteration of the loop, things may become asynchronous (including
+ // attempts to switch the phase and modify the structure we are iteration
+ // upon).
+ //
+ // 2. The task can be queued or it can be executed synchronously inside
+ // async() (refer to the scheduler class for details on this semantics).
+ //
+ // If this is an async()-synchronous execution, then the task will create
+ // a referencing phase_lock. If, however, this is a queued execution
+ // (including wait()-synchronous), then the task will create a top-level
+ // phase_lock.
+ //
+ // Note that we only acquire the lock once the task starts executing
+ // (there is no reason to hold the lock while the task is sitting in the
+ // queue). This optimization assumes that whatever else we pass to the
+ // task (for example, a reference to a target) is stable (in other words,
+ // such a reference cannot become invalid).
+ //
+ // 3. Before calling wait(), we release our phase lock to allow switching
+ // the phase.
+ //
+ struct LIBBUILD2_SYMEXPORT phase_lock
+ {
+ explicit phase_lock (run_phase);
+ ~phase_lock ();
+
+ phase_lock (phase_lock&&) = delete;
+ phase_lock (const phase_lock&) = delete;
+
+ phase_lock& operator= (phase_lock&&) = delete;
+ phase_lock& operator= (const phase_lock&) = delete;
+
+ run_phase p;
+ };
+
+ // Assuming we have a lock on the current phase, temporarily release it
+ // and reacquire on destruction.
+ //
+ struct LIBBUILD2_SYMEXPORT phase_unlock
+ {
+ phase_unlock (bool unlock = true);
+ ~phase_unlock () noexcept (false);
+
+ phase_lock* l;
+ };
+
+ // Assuming we have a lock on the current phase, temporarily switch to a
+ // new phase and switch back on destruction.
+ //
+ struct LIBBUILD2_SYMEXPORT phase_switch
+ {
+ explicit phase_switch (run_phase);
+ ~phase_switch () noexcept (false);
+
+ run_phase o, n;
+ };
+
+ // Wait for a task count optionally and temporarily unlocking the phase.
+ //
+ struct wait_guard
+ {
+ ~wait_guard () noexcept (false);
+
+ wait_guard (); // Empty.
+
+ explicit
+ wait_guard (atomic_count& task_count,
+ bool phase = false);
+
+ wait_guard (size_t start_count,
+ atomic_count& task_count,
+ bool phase = false);
+
+ void
+ wait ();
+
+ // Note: move-assignable to empty only.
+ //
+ wait_guard (wait_guard&&);
+ wait_guard& operator= (wait_guard&&);
+
+ wait_guard (const wait_guard&) = delete;
+ wait_guard& operator= (const wait_guard&) = delete;
+
+ size_t start_count;
+ atomic_count* task_count;
+ bool phase;
+ };
+
+ // Cached variables.
+ //
+ // Note: consider printing in info meta-operation if adding anything here.
+ //
+ LIBBUILD2_SYMEXPORT extern const variable* var_src_root;
+ LIBBUILD2_SYMEXPORT extern const variable* var_out_root;
+ LIBBUILD2_SYMEXPORT extern const variable* var_src_base;
+ LIBBUILD2_SYMEXPORT extern const variable* var_out_base;
+ LIBBUILD2_SYMEXPORT extern const variable* var_forwarded;
+
+ LIBBUILD2_SYMEXPORT extern const variable* var_project;
+ LIBBUILD2_SYMEXPORT extern const variable* var_amalgamation;
+ LIBBUILD2_SYMEXPORT extern const variable* var_subprojects;
+ LIBBUILD2_SYMEXPORT extern const variable* var_version;
+
+ // project.url
+ //
+ LIBBUILD2_SYMEXPORT extern const variable* var_project_url;
+
+ // project.summary
+ //
+ LIBBUILD2_SYMEXPORT extern const variable* var_project_summary;
+
+ // import.target
+ //
+ LIBBUILD2_SYMEXPORT extern const variable* var_import_target;
+
+ // [bool] target visibility
+ //
+ LIBBUILD2_SYMEXPORT extern const variable* var_clean;
+
+ // Forwarded configuration backlink mode. Valid values are:
+ //
+ // false - no link.
+ // true - make a link using appropriate mechanism.
+ // symbolic - make a symbolic link.
+ // hard - make a hard link.
+ // copy - make a copy.
+ // overwrite - copy over but don't remove on clean (committed gen code).
+ //
+ // Note that it can be set by a matching rule as a rule-specific variable.
+ //
+ // [string] target visibility
+ //
+ LIBBUILD2_SYMEXPORT extern const variable* var_backlink;
+
+ // Prerequisite inclusion/exclusion. Valid values are:
+ //
+ // false - exclude.
+ // true - include.
+ // adhoc - include but treat as an ad hoc input.
+ //
+ // If a rule uses prerequisites as inputs (as opposed to just matching them
+ // with the "pass-through" semantics), then the adhoc value signals that a
+ // prerequisite is an ad hoc input. A rule should match and execute such a
+ // prerequisite (whether its target type is recognized as suitable input or
+ // not) and assume that the rest will be handled by the user (e.g., it will
+ // be passed via a command line argument or some such). Note that this
+ // mechanism can be used to both treat unknown prerequisite types as inputs
+ // (for example, linker scripts) as well as prevent treatment of known
+ // prerequisite types as such while still matching and executing them (for
+ // example, plugin libraries).
+ //
+ // A rule with the "pass-through" semantics should treat the adhoc value
+ // the same as true.
+ //
+ // To query this value in rule implementations use the include() helpers
+ // from prerequisites.hxx.
+ //
+ // [string] prereq visibility
+ //
+ LIBBUILD2_SYMEXPORT extern const variable* var_include;
+
+ LIBBUILD2_SYMEXPORT extern const char var_extension[10]; // "extension"
+
+ // The build.* namespace.
+ //
+ // .meta_operation
+ //
+ LIBBUILD2_SYMEXPORT extern const variable* var_build_meta_operation;
+
+ // Current action (meta/operation).
+ //
+ // The names unlike info are available during boot but may not yet be
+ // lifted. The name is always for an outer operation (or meta operation
+ // that hasn't been recognized as such yet).
+ //
+ LIBBUILD2_SYMEXPORT extern string current_mname;
+ LIBBUILD2_SYMEXPORT extern string current_oname;
+
+ LIBBUILD2_SYMEXPORT extern const meta_operation_info* current_mif;
+ LIBBUILD2_SYMEXPORT extern const operation_info* current_inner_oif;
+ LIBBUILD2_SYMEXPORT extern const operation_info* current_outer_oif;
+
+ // Current operation number (1-based) in the meta-operation batch.
+ //
+ LIBBUILD2_SYMEXPORT extern size_t current_on;
+
+ LIBBUILD2_SYMEXPORT extern execution_mode current_mode;
+
+ // Some diagnostics (for example output directory creation/removal by the
+ // fsdir rule) is just noise at verbosity level 1 unless it is the only
+ // thing that is printed. So we can only suppress it in certain situations
+ // (e.g., dist) where we know we have already printed something.
+ //
+ LIBBUILD2_SYMEXPORT extern bool current_diag_noise;
+
+ // Total number of dependency relationships and targets with non-noop
+ // recipe in the current action.
+ //
+ // Together with target::dependents the dependency count 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.
+ //
+ // The target count is incremented after a non-noop recipe is matched and
+ // decremented after such recipe has been executed. If such a recipe has
+ // skipped executing the operation, then it should increment the skip count.
+ // These two counters are used for progress monitoring and diagnostics.
+ //
+ LIBBUILD2_SYMEXPORT extern atomic_count dependency_count;
+ LIBBUILD2_SYMEXPORT extern atomic_count target_count;
+ LIBBUILD2_SYMEXPORT extern atomic_count skip_count;
+
+ inline void
+ set_current_mif (const meta_operation_info& mif)
+ {
+ if (current_mname != mif.name)
+ {
+ current_mname = mif.name;
+ global_scope->rw ().assign (var_build_meta_operation) = mif.name;
+ }
+
+ current_mif = &mif;
+ current_on = 0; // Reset.
+ }
+
+ inline void
+ set_current_oif (const operation_info& inner_oif,
+ const operation_info* outer_oif = nullptr,
+ bool diag_noise = true)
+ {
+ current_oname = (outer_oif == nullptr ? inner_oif : *outer_oif).name;
+ current_inner_oif = &inner_oif;
+ current_outer_oif = outer_oif;
+ current_on++;
+ current_mode = inner_oif.mode;
+ current_diag_noise = diag_noise;
+
+ // Reset counters (serial execution).
+ //
+ dependency_count.store (0, memory_order_relaxed);
+ target_count.store (0, memory_order_relaxed);
+ skip_count.store (0, memory_order_relaxed);
+ }
+
+ // Keep going flag.
+ //
+ // Note that setting it to false is not of much help unless we are running
+ // serially. In parallel we queue most of the things up before we see any
+ // failures.
+ //
+ LIBBUILD2_SYMEXPORT extern bool keep_going;
+
+ // Dry run flag (see --dry-run|-n).
+ //
+ // This flag is set only for the final execute phase (as opposed to those
+ // that interrupt match) by the perform meta operation's execute() callback.
+ //
+ // Note that for this mode to function properly we have to use fake mtimes.
+ // Specifically, a rule that pretends to update a target must set its mtime
+ // to system_clock::now() and everyone else must use this cached value. In
+ // other words, there should be no mtime re-query from the filesystem. The
+ // same is required for "logical clean" (i.e., dry-run 'clean update' in
+ // order to see all the command lines).
+ //
+ // At first, it may seem like we should also "dry-run" changes to depdb. But
+ // that would be both problematic (some rules update it in apply() during
+ // the match phase) and wasteful (why discard information). Also, depdb may
+ // serve as an input to some commands (for example, to provide C++ module
+ // mapping) which means that without updating it the commands we print might
+ // not be runnable (think of the compilation database).
+ //
+ // One thing we need to be careful about if we are updating depdb is to not
+ // render the target up-to-date. But in this case the depdb file will be
+ // older than the target which in our model is treated as an interrupted
+ // update (see depdb for details).
+ //
+ // Note also that sometimes it makes sense to do a bit more than absolutely
+ // necessary or to discard information in order to keep the rule logic sane.
+ // And some rules may choose to ignore this flag altogether. In this case,
+ // however, the rule should be careful not to rely on functions (notably
+ // from filesystem) that respect this flag in order not to end up with a
+ // job half done.
+ //
+ LIBBUILD2_SYMEXPORT extern bool dry_run;
+
+ // Config module entry points.
+ //
+ LIBBUILD2_SYMEXPORT extern void (*config_save_variable) (
+ scope&, const variable&, uint64_t flags);
+
+ LIBBUILD2_SYMEXPORT extern const string& (*config_preprocess_create) (
+ const variable_overrides&,
+ values&,
+ vector_view<opspec>&,
+ bool lifted,
+ const location&);
+
+ // Reset the build state. In particular, this removes all the targets,
+ // scopes, and variables.
+ //
+ LIBBUILD2_SYMEXPORT variable_overrides
+ reset (const strings& cmd_vars);
+
+ // Return the project name or empty string if unnamed.
+ //
+ inline const project_name&
+ project (const scope& root)
+ {
+ auto l (root[var_project]);
+ return l ? cast<project_name> (l) : empty_project_name;
+ }
+
+ // Return the src/out directory corresponding to the given out/src. The
+ // passed directory should be a sub-directory of out/src_root.
+ //
+ LIBBUILD2_SYMEXPORT dir_path
+ src_out (const dir_path& out, const scope& root);
+
+ LIBBUILD2_SYMEXPORT dir_path
+ src_out (const dir_path& out,
+ const dir_path& out_root, const dir_path& src_root);
+
+ LIBBUILD2_SYMEXPORT dir_path
+ out_src (const dir_path& src, const scope& root);
+
+ LIBBUILD2_SYMEXPORT dir_path
+ out_src (const dir_path& src,
+ const dir_path& out_root, const dir_path& src_root);
+
+ // Action phrases, e.g., "configure update exe{foo}", "updating exe{foo}",
+ // and "updating exe{foo} is configured". Use like this:
+ //
+ // info << "while " << diag_doing (a, t);
+ //
+ class target;
+
+ struct diag_phrase
+ {
+ const action& a;
+ const target& t;
+ void (*f) (ostream&, const action&, const target&);
+ };
+
+ inline ostream&
+ operator<< (ostream& os, const diag_phrase& p)
+ {
+ p.f (os, p.a, p.t);
+ return os;
+ }
+
+ LIBBUILD2_SYMEXPORT string
+ diag_do (const action&);
+
+ LIBBUILD2_SYMEXPORT void
+ diag_do (ostream&, const action&, const target&);
+
+ inline diag_phrase
+ diag_do (const action& a, const target& t)
+ {
+ return diag_phrase {a, t, &diag_do};
+ }
+
+ LIBBUILD2_SYMEXPORT string
+ diag_doing (const action&);
+
+ LIBBUILD2_SYMEXPORT void
+ diag_doing (ostream&, const action&, const target&);
+
+ inline diag_phrase
+ diag_doing (const action& a, const target& t)
+ {
+ return diag_phrase {a, t, &diag_doing};
+ }
+
+ LIBBUILD2_SYMEXPORT string
+ diag_did (const action&);
+
+ LIBBUILD2_SYMEXPORT void
+ diag_did (ostream&, const action&, const target&);
+
+ inline diag_phrase
+ diag_did (const action& a, const target& t)
+ {
+ return diag_phrase {a, t, &diag_did};
+ }
+
+ LIBBUILD2_SYMEXPORT void
+ diag_done (ostream&, const action&, const target&);
+
+ inline diag_phrase
+ diag_done (const action& a, const target& t)
+ {
+ return diag_phrase {a, t, &diag_done};
+ }
+}
+
+#include <libbuild2/context.ixx>
+
+#endif // LIBBUILD2_CONTEXT_HXX
diff --git a/libbuild2/context.ixx b/libbuild2/context.ixx
new file mode 100644
index 0000000..f947bd7
--- /dev/null
+++ b/libbuild2/context.ixx
@@ -0,0 +1,60 @@
+// file : libbuild2/context.ixx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+namespace build2
+{
+ // wait_guard
+ //
+ inline wait_guard::
+ wait_guard ()
+ : start_count (0), task_count (nullptr), phase (false)
+ {
+ }
+
+ inline wait_guard::
+ wait_guard (atomic_count& tc, bool p)
+ : wait_guard (0, tc, p)
+ {
+ }
+
+ inline wait_guard::
+ wait_guard (size_t sc, atomic_count& tc, bool p)
+ : start_count (sc), task_count (&tc), phase (p)
+ {
+ }
+
+ inline wait_guard::
+ ~wait_guard () noexcept (false)
+ {
+ if (task_count != nullptr)
+ wait ();
+ }
+
+ inline wait_guard::
+ wait_guard (wait_guard&& x)
+ : start_count (x.start_count), task_count (x.task_count), phase (x.phase)
+ {
+ x.task_count = nullptr;
+ }
+
+ inline wait_guard& wait_guard::
+ operator= (wait_guard&& x)
+ {
+ if (&x != this)
+ {
+ assert (task_count == nullptr);
+ start_count = x.start_count; task_count = x.task_count; phase = x.phase;
+ x.task_count = nullptr;
+ }
+ return *this;
+ }
+
+ inline void wait_guard::
+ wait ()
+ {
+ phase_unlock u (phase);
+ sched.wait (start_count, *task_count);
+ task_count = nullptr;
+ }
+}
diff --git a/libbuild2/depdb.cxx b/libbuild2/depdb.cxx
new file mode 100644
index 0000000..32e5916
--- /dev/null
+++ b/libbuild2/depdb.cxx
@@ -0,0 +1,399 @@
+// file : libbuild2/depdb.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <libbuild2/depdb.hxx>
+
+#ifdef _WIN32
+# include <libbutl/win32-utility.hxx>
+#endif
+
+#include <libbuild2/filesystem.hxx> // mtime()
+#include <libbuild2/diagnostics.hxx>
+
+using namespace std;
+using namespace butl;
+
+namespace build2
+{
+ depdb_base::
+ depdb_base (const path& p, timestamp mt)
+ {
+ fdopen_mode om (fdopen_mode::out | fdopen_mode::binary);
+ ifdstream::iostate em (ifdstream::badbit);
+
+ if (mt == timestamp_nonexistent)
+ {
+ state_ = state::write;
+ om |= fdopen_mode::create | fdopen_mode::exclusive;
+ em |= ifdstream::failbit;
+ }
+ else
+ {
+ state_ = state::read;
+ om |= fdopen_mode::in;
+ }
+
+ auto_fd fd;
+ try
+ {
+ fd = fdopen (p, om);
+ }
+ catch (const io_error&)
+ {
+ bool c (state_ == state::write);
+
+ diag_record dr (fail);
+ dr << "unable to " << (c ? "create" : "open") << ' ' << p;
+
+ if (c)
+ dr << info << "did you forget to add fsdir{} prerequisite for "
+ << "output directory?";
+
+ dr << endf;
+ }
+
+ // Open the corresponding stream. Note that if we throw after that, the
+ // corresponding member will not be destroyed. This is the reason for the
+ // depdb/base split.
+ //
+ if (state_ == state::read)
+ {
+ new (&is_) ifdstream (move (fd), em);
+ buf_ = static_cast<fdbuf*> (is_.rdbuf ());
+ }
+ else
+ {
+ new (&os_) ofdstream (move (fd), em);
+ buf_ = static_cast<fdbuf*> (os_.rdbuf ());
+ }
+ }
+
+ depdb::
+ depdb (path_type&& p, timestamp mt)
+ : depdb_base (p, mt),
+ path (move (p)),
+ mtime (mt != timestamp_nonexistent ? mt : timestamp_unknown),
+ touch (false)
+ {
+ // Read/write the database format version.
+ //
+ if (state_ == state::read)
+ {
+ string* l (read ());
+ if (l == nullptr || *l != "1")
+ write ('1');
+ }
+ else
+ write ('1');
+ }
+
+ depdb::
+ depdb (path_type p)
+ : depdb (move (p), build2::mtime (p))
+ {
+ }
+
+ void depdb::
+ change (bool trunc)
+ {
+ assert (state_ != state::write);
+
+ // Transfer the file descriptor from ifdstream to ofdstream. Note that the
+ // steps in this dance must be carefully ordered to make sure we don't
+ // call any destructors twice in the face of exceptions.
+ //
+ auto_fd fd (is_.release ());
+
+ // Consider this scenario: we are overwriting an old line (so it ends with
+ // a newline and the "end marker") but the operation failed half way
+ // through. Now we have the prefix from the new line, the suffix from the
+ // old, and everything looks valid. So what we need is to somehow
+ // invalidate the old content so that it can never combine with (partial)
+ // new content to form a valid line. One way to do that would be to
+ // truncate the file.
+ //
+ if (trunc)
+ try
+ {
+ fdtruncate (fd.get (), pos_);
+ }
+ catch (const io_error& e)
+ {
+ fail << "unable to truncate " << path << ": " << e;
+ }
+
+ // Note: the file descriptor position can be beyond the pos_ value due to
+ // the ifdstream buffering. That's why we need to seek to switch from
+ // reading to writing.
+ //
+ try
+ {
+ fdseek (fd.get (), pos_, fdseek_mode::set);
+ }
+ catch (const io_error& e)
+ {
+ fail << "unable to rewind " << path << ": " << e;
+ }
+
+ // @@ Strictly speaking, ofdstream can throw which will leave us in a
+ // non-destructible state. Unlikely but possible.
+ //
+ is_.~ifdstream ();
+ new (&os_) ofdstream (move (fd),
+ ofdstream::badbit | ofdstream::failbit,
+ pos_);
+ buf_ = static_cast<fdbuf*> (os_.rdbuf ());
+
+ state_ = state::write;
+ mtime = timestamp_unknown;
+ }
+
+ string* depdb::
+ read_ ()
+ {
+ // Save the start position of this line so that we can overwrite it.
+ //
+ pos_ = buf_->tellg ();
+
+ try
+ {
+ // Note that we intentionally check for eof after updating the write
+ // position.
+ //
+ if (state_ == state::read_eof)
+ return nullptr;
+
+ getline (is_, line_); // Calls line_.erase().
+
+ // The line should always end with a newline. If it doesn't, then this
+ // line (and the rest of the database) is assumed corrupted. Also peek
+ // at the character after the newline. We should either have the next
+ // line or '\0', which is our "end marker", that is, it indicates the
+ // database was properly closed.
+ //
+ ifdstream::int_type c;
+ if (is_.fail () || // Nothing got extracted.
+ is_.eof () || // Eof reached before delimiter.
+ (c = is_.peek ()) == ifdstream::traits_type::eof ())
+ {
+ // Preemptively switch to writing. While we could have delayed this
+ // until the user called write(), if the user calls read() again (for
+ // whatever misguided reason) we will mess up the overwrite position.
+ //
+ change ();
+ return nullptr;
+ }
+
+ // Handle the "end marker". Note that the caller can still switch to the
+ // write mode on this line. And, after calling read() again, write to
+ // the next line (i.e., start from the "end marker").
+ //
+ if (c == '\0')
+ state_ = state::read_eof;
+ }
+ catch (const io_error& e)
+ {
+ fail << "unable to read from " << path << ": " << e;
+ }
+
+ return &line_;
+ }
+
+ bool depdb::
+ skip ()
+ {
+ if (state_ == state::read_eof)
+ return true;
+
+ assert (state_ == state::read);
+
+ // The rest is pretty similar in logic to read_() above.
+ //
+ pos_ = buf_->tellg ();
+
+ try
+ {
+ // Keep reading lines checking for the end marker after each newline.
+ //
+ ifdstream::int_type c;
+ do
+ {
+ if ((c = is_.get ()) == '\n')
+ {
+ if ((c = is_.get ()) == '\0')
+ {
+ state_ = state::read_eof;
+ return true;
+ }
+ }
+ } while (c != ifdstream::traits_type::eof ());
+ }
+ catch (const io_error& e)
+ {
+ fail << "unable to read from " << path << ": " << e;
+ }
+
+ // Invalid database so change over to writing.
+ //
+ change ();
+ return false;
+ }
+
+ void depdb::
+ write (const char* s, size_t n, bool nl)
+ {
+ // Switch to writing if we are still reading.
+ //
+ if (state_ != state::write)
+ change ();
+
+ try
+ {
+ os_.write (s, static_cast<streamsize> (n));
+
+ if (nl)
+ os_.put ('\n');
+ }
+ catch (const io_error& e)
+ {
+ fail << "unable to write to " << path << ": " << e;
+ }
+ }
+
+ void depdb::
+ write (char c, bool nl)
+ {
+ // Switch to writing if we are still reading.
+ //
+ if (state_ != state::write)
+ change ();
+
+ try
+ {
+ os_.put (c);
+
+ if (nl)
+ os_.put ('\n');
+ }
+ catch (const io_error& e)
+ {
+ fail << "unable to write to " << path << ": " << e;
+ }
+ }
+
+ void depdb::
+ close ()
+ {
+ // If we are at eof, then it means all lines are good, there is the "end
+ // marker" at the end, and we don't need to do anything, except, maybe
+ // touch the file. Otherwise, if we are still in the read mode, truncate
+ // the rest, and then add the "end marker" (we cannot have anything in the
+ // write mode since we truncate in change()).
+ //
+ if (state_ == state::read_eof)
+ {
+ if (!touch)
+ try
+ {
+ is_.close ();
+ return;
+ }
+ catch (const io_error& e)
+ {
+ fail << "unable to close " << path << ": " << e;
+ }
+
+ // While there are utime(2)/utimensat(2) (and probably something similar
+ // for Windows), for now we just overwrite the "end marker". Hopefully
+ // no implementation will be smart enough to recognize this is a no-op
+ // and skip updating mtime (which would probably be incorrect, spec-
+ // wise). And this could even be faster since we already have the file
+ // descriptor. Or it might be slower since so far we've only been
+ // reading.
+ //
+ pos_ = buf_->tellg (); // The last line is accepted.
+ change (false /* truncate */); // Write end marker below.
+ }
+ else if (state_ != state::write)
+ {
+ pos_ = buf_->tellg (); // The last line is accepted.
+ change (true /* truncate */);
+ }
+
+ if (mtime_check ())
+ start_ = system_clock::now ();
+
+ try
+ {
+ os_.put ('\0'); // The "end marker".
+ os_.close ();
+ }
+ catch (const io_error& e)
+ {
+ fail << "unable to flush " << path << ": " << e;
+ }
+
+ // On some platforms (currently confirmed on FreeBSD running as VMs) one
+ // can sometimes end up with a modification time that is a bit after the
+ // call to close(). And in some tight cases this can mess with our
+ // "protocol" that a valid depdb should be no older than the target it is
+ // for.
+ //
+ // Note that this does not seem to be related to clock adjustments but
+ // rather feels like the modification time is set when the changes
+ // actually hit some lower-level layer (e.g., OS or filesystem
+ // driver). One workaround that appears to work is to query the
+ // mtime. This seems to force that layer to commit to a timestamp.
+ //
+#if defined(__FreeBSD__)
+ mtime = build2::mtime (path); // Save for debugging/check below.
+#endif
+ }
+
+ void depdb::
+ check_mtime_ (const path_type& t, timestamp e)
+ {
+ // We could call the static version but then we would have lost additional
+ // information for some platforms.
+ //
+ timestamp t_mt (build2::mtime (t));
+ timestamp d_mt (build2::mtime (path));
+
+ if (d_mt > t_mt)
+ {
+ if (e == timestamp_unknown)
+ e = system_clock::now ();
+
+ fail << "backwards modification times detected:\n"
+ << " " << start_ << " sequence start\n"
+#if defined(__FreeBSD__)
+ << " " << mtime << " close mtime\n"
+#endif
+ << " " << d_mt << " " << path.string () << '\n'
+ << " " << t_mt << " " << t.string () << '\n'
+ << " " << e << " sequence end";
+ }
+ }
+
+ void depdb::
+ check_mtime_ (timestamp s,
+ const path_type& d,
+ const path_type& t,
+ timestamp e)
+ {
+ using build2::mtime;
+
+ timestamp t_mt (mtime (t));
+ timestamp d_mt (mtime (d));
+
+ if (d_mt > t_mt)
+ {
+ fail << "backwards modification times detected:\n"
+ << " " << s << " sequence start\n"
+ << " " << d_mt << " " << d.string () << '\n'
+ << " " << t_mt << " " << t.string () << '\n'
+ << " " << e << " sequence end";
+ }
+ }
+}
diff --git a/libbuild2/depdb.hxx b/libbuild2/depdb.hxx
new file mode 100644
index 0000000..8a1cd1f
--- /dev/null
+++ b/libbuild2/depdb.hxx
@@ -0,0 +1,288 @@
+// file : libbuild2/depdb.hxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef LIBBUILD2_DEPDB_HXX
+#define LIBBUILD2_DEPDB_HXX
+
+#include <cstring> // strlen()
+
+#include <libbuild2/types.hxx>
+#include <libbuild2/utility.hxx>
+
+#include <libbuild2/export.hxx>
+
+namespace build2
+{
+ // Auxiliary dependency database (those .d files). Prints the diagnostics
+ // and fails on system and IO errors.
+ //
+ // This is a strange beast: a line-oriented, streaming database that can, at
+ // some point, be switched from reading to (over)writing. The idea is to
+ // store auxiliary/ad-hoc dependency information in the "invalidation"
+ // order. That is, if an earlier line is out of date, then all the
+ // subsequent ones are out of date as well.
+ //
+ // As an example, consider a dependency database for foo.o which is built
+ // from foo.cxx by the cxx.compile rule. The first line could be the rule
+ // name itself (perhaps with the version). If a different rule is now
+ // building foo.o, then any dep info that was saved by cxx.compile is
+ // probably useless. Next we can have the command line options that were
+ // used to build foo.o. Then could come the source file name followed by the
+ // extracted header dependencies. If the compile options or the source file
+ // name have changed, then the header dependencies are likely to have
+ // changed as well.
+ //
+ // As an example, here is what our foo.o.d could look like (the first line
+ // is the database format version and the last '\0' character is the end
+ // marker):
+ //
+ // 1
+ // cxx.compile 1
+ // g++-4.8 -I/tmp/foo -O3
+ // /tmp/foo/foo.cxx
+ // /tmp/foo/foo.hxx
+ // /usr/include/string.h
+ // /usr/include/stdlib.h
+ // /tmp/foo/bar.hxx
+ // ^@
+ //
+ // A race is possible between updating the database and the target. For
+ // example, we may detect a line mismatch that renders the target out-of-
+ // date (say, compile options in the above example). We update the database
+ // but before getting a chance to update the target, we get interrupted. On
+ // a subsequent re-run, because the database has been updated, we will miss
+ // the "target requires update" condition.
+ //
+ // If we assume that an update of the database also means an update of the
+ // target, then this "interrupted update" situation can be easily detected
+ // by comparing the database and target modification timestamps. This is
+ // also used to handle the dry-run mode where we essentially do the
+ // interruption ourselves.
+ //
+ struct LIBBUILD2_SYMEXPORT depdb_base
+ {
+ explicit
+ depdb_base (const path&, timestamp);
+
+ ~depdb_base ();
+
+ enum class state {read, read_eof, write} state_;
+
+ union
+ {
+ ifdstream is_; // read, read_eof
+ ofdstream os_; // write
+ };
+
+ butl::fdbuf* buf_; // Current buffer (for tellg()/tellp()).
+ };
+
+ class LIBBUILD2_SYMEXPORT depdb: private depdb_base
+ {
+ public:
+ using path_type = build2::path;
+
+ // The modification time of the database only makes sense while reading
+ // (in the write mode it will be set to timestamp_unknown).
+ //
+ // If touch is set to true, update the database modification time in
+ // close() even if otherwise no modifications are necessary (i.e., the
+ // database is in the read mode and is at eof).
+ //
+ path_type path;
+ timestamp mtime;
+ bool touch;
+
+ // Open the database for reading. Note that if the file does not exist,
+ // has wrong format version, or is corrupt, then the database will be
+ // immediately switched to writing.
+ //
+ // The failure commonly happens when the user tries to stash the target in
+ // a non-existent subdirectory but forgets to add the corresponding fsdir{}
+ // prerequisite. That's why the issued diagnostics may provide the
+ // corresponding hint.
+ //
+ explicit
+ depdb (path_type);
+
+ // Close the database. If this function is not called, then the database
+ // may be left in the old/currupt state. Note that in the read mode this
+ // function will "chop off" lines that haven't been read.
+ //
+ // Make sure to also call check_mtime() after updating the target to
+ // perform the target/database modification times sanity checks.
+ //
+ void
+ close ();
+
+ // Flush any unwritten data to disk. This is primarily useful when reusing
+ // a (partially written) database as an input to external programs (e.g.,
+ // as a module map).
+ //
+ void
+ flush ();
+
+ // Perform target/database modification times sanity check.
+ //
+ // Note that it would also be good to compare the target timestamp against
+ // the newest prerequisite. However, obtaining this information would cost
+ // extra (see execute_prerequisites()). So maybe later, if we get a case
+ // where this is a problem (in a sense, the database is a buffer between
+ // prerequisites and the target).
+ //
+ void
+ check_mtime (const path_type& target, timestamp end = timestamp_unknown);
+
+ static void
+ check_mtime (timestamp start,
+ const path_type& db,
+ const path_type& target,
+ timestamp end);
+
+ // Return true if mtime checks are enabled.
+ //
+ static bool
+ mtime_check ();
+
+ // Read the next line. If the result is not NULL, then it is a pointer to
+ // the next line in the database (which you are free to move from). If you
+ // then call write(), this line will be overwritten.
+ //
+ // If the result is NULL, then it means no next line is unavailable. This
+ // can be due to several reasons:
+ //
+ // - eof reached (you can detect this by calling more() before read())
+ // - database is already in the write mode
+ // - the next line (and the rest of the database are corrupt)
+ //
+ string*
+ read () {return state_ == state::write ? nullptr : read_ ();}
+
+ // Return true if the database is in the read mode and there is at least
+ // one more line available. Note that there is no guarantee that the line
+ // is not corrupt. In other words, read() can still return NULL, it just
+ // won't be because of eof.
+ //
+ bool
+ more () const {return state_ == state::read;}
+
+ bool
+ reading () const {return state_ != state::write;}
+
+ bool
+ writing () const {return state_ == state::write;}
+
+ // Skip to the end of the database and return true if it is valid.
+ // Otherwise, return false, in which case the database must be
+ // overwritten. Note that this function expects the database to be in the
+ // read state.
+ //
+ bool
+ skip ();
+
+ // Write the next line. If nl is false then don't write the newline yet.
+ // Note that this switches the database into the write mode and no further
+ // reading will be possible.
+ //
+ void
+ write (const string& l, bool nl = true) {write (l.c_str (), l.size (), nl);}
+
+ void
+ write (const path_type& p, bool nl = true) {write (p.string (), nl);}
+
+ void
+ write (const char* s, bool nl = true) {write (s, std::strlen (s), nl);}
+
+ void
+ write (const char*, size_t, bool nl = true);
+
+ void
+ write (char, bool nl = true);
+
+ // Mark the previously read line as to be overwritte.
+ //
+ void
+ write () {if (state_ != state::write) change ();}
+
+ // Read the next line and compare it to the expected value. If it matches,
+ // return NULL. Otherwise, overwrite it and return the old value (which
+ // could also be NULL). This strange-sounding result semantics is used to
+ // detect the "there is a value but it does not match" case for tracing:
+ //
+ // if (string* o = d.expect (...))
+ // l4 ([&]{trace << "X mismatch forcing update of " << t;});
+ //
+ string*
+ expect (const string& v)
+ {
+ string* l (read ());
+ if (l == nullptr || *l != v)
+ {
+ write (v);
+ return l;
+ }
+
+ return nullptr;
+ }
+
+ string*
+ expect (const path_type& v)
+ {
+ string* l (read ());
+ if (l == nullptr ||
+ path_type::traits_type::compare (*l, v.string ()) != 0)
+ {
+ write (v);
+ return l;
+ }
+
+ return nullptr;
+ }
+
+ string*
+ expect (const char* v)
+ {
+ string* l (read ());
+ if (l == nullptr || *l != v)
+ {
+ write (v);
+ return l;
+ }
+
+ return nullptr;
+ }
+
+ // Could be supported if required.
+ //
+ depdb (depdb&&) = delete;
+ depdb (const depdb&) = delete;
+
+ depdb& operator= (depdb&&) = delete;
+ depdb& operator= (const depdb&) = delete;
+
+ private:
+ depdb (path_type&&, timestamp);
+
+ void
+ change (bool truncate = true);
+
+ string*
+ read_ ();
+
+ void
+ check_mtime_ (const path_type&, timestamp);
+
+ static void
+ check_mtime_ (timestamp, const path_type&, const path_type&, timestamp);
+
+ private:
+ uint64_t pos_; // Start of the last returned line.
+ string line_; // Current line.
+ timestamp start_; // Sequence start (mtime check).
+ };
+}
+
+#include <libbuild2/depdb.ixx>
+
+#endif // LIBBUILD2_DEPDB_HXX
diff --git a/libbuild2/depdb.ixx b/libbuild2/depdb.ixx
new file mode 100644
index 0000000..9f73fcb
--- /dev/null
+++ b/libbuild2/depdb.ixx
@@ -0,0 +1,45 @@
+// file : libbuild2/depdb.ixx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+namespace build2
+{
+ inline depdb_base::
+ ~depdb_base ()
+ {
+ if (state_ != state::write)
+ is_.~ifdstream ();
+ else
+ os_.~ofdstream ();
+ }
+
+ inline void depdb::
+ flush ()
+ {
+ if (state_ == state::write)
+ os_.flush ();
+ }
+
+ inline bool depdb::
+ mtime_check ()
+ {
+ return mtime_check_option ? *mtime_check_option : LIBBUILD2_MTIME_CHECK;
+ }
+
+ inline void depdb::
+ check_mtime (const path_type& t, timestamp e)
+ {
+ if (state_ == state::write && mtime_check ())
+ check_mtime_ (t, e);
+ }
+
+ inline void depdb::
+ check_mtime (timestamp s,
+ const path_type& d,
+ const path_type& t,
+ timestamp e)
+ {
+ if (mtime_check ())
+ check_mtime_ (s, d, t, e);
+ }
+}
diff --git a/libbuild2/diagnostics.cxx b/libbuild2/diagnostics.cxx
new file mode 100644
index 0000000..eab3b78
--- /dev/null
+++ b/libbuild2/diagnostics.cxx
@@ -0,0 +1,138 @@
+// file : libbuild2/diagnostics.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <libbuild2/diagnostics.hxx>
+
+#include <cstring> // strchr()
+
+#include <libbutl/process-io.mxx>
+
+using namespace std;
+
+namespace build2
+{
+ // Diagnostics state (verbosity level, progress, etc). Keep disabled until
+ // set from options.
+ //
+ uint16_t verb = 0;
+
+ optional<bool> diag_progress_option;
+
+ bool diag_no_line = false;
+ bool diag_no_column = false;
+
+ bool stderr_term = false;
+
+ void
+ init_diag (uint16_t v, optional<bool> p, bool nl, bool nc, bool st)
+ {
+ verb = v;
+ diag_progress_option = p;
+ diag_no_line = nl;
+ diag_no_column = nc;
+ stderr_term = st;
+ }
+
+ // Stream verbosity.
+ //
+ const int stream_verb_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)
+ {
+ r << butl::process_args {args, n};
+ }
+
+ // Diagnostics stack.
+ //
+ static
+#ifdef __cpp_thread_local
+ thread_local
+#else
+ __thread
+#endif
+ const diag_frame* diag_frame_stack = nullptr;
+
+ const diag_frame* diag_frame::
+ stack () noexcept
+ {
+ return diag_frame_stack;
+ }
+
+ const diag_frame* diag_frame::
+ stack (const diag_frame* f) noexcept
+ {
+ const diag_frame* r (diag_frame_stack);
+ diag_frame_stack = f;
+ return r;
+ }
+
+ // Diagnostic facility, project specifics.
+ //
+
+ void simple_prologue_base::
+ operator() (const diag_record& r) const
+ {
+ stream_verb (r.os, sverb_);
+
+ if (type_ != nullptr)
+ r << type_ << ": ";
+
+ if (mod_ != nullptr)
+ r << mod_ << "::";
+
+ if (name_ != nullptr)
+ r << name_ << ": ";
+ }
+
+ void location_prologue_base::
+ operator() (const diag_record& r) const
+ {
+ stream_verb (r.os, sverb_);
+
+ if (!loc_.empty ())
+ {
+ r << *loc_.file << ':';
+
+ if (!diag_no_line)
+ {
+ if (loc_.line != 0)
+ {
+ r << loc_.line << ':';
+
+ if (!diag_no_column)
+ {
+ if (loc_.column != 0)
+ r << loc_.column << ':';
+ }
+ }
+ }
+
+ r << ' ';
+ }
+
+ if (type_ != nullptr)
+ r << type_ << ": ";
+
+ if (mod_ != nullptr)
+ r << mod_ << "::";
+
+ if (name_ != nullptr)
+ r << name_ << ": ";
+ }
+
+ const basic_mark error ("error");
+ const basic_mark warn ("warning");
+ const basic_mark info ("info");
+ const basic_mark text (nullptr, nullptr, nullptr); // No type/data/frame.
+ const fail_mark fail ("error");
+ const fail_end endf;
+}
diff --git a/libbuild2/diagnostics.hxx b/libbuild2/diagnostics.hxx
new file mode 100644
index 0000000..9ad18ff
--- /dev/null
+++ b/libbuild2/diagnostics.hxx
@@ -0,0 +1,436 @@
+// file : libbuild2/diagnostics.hxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef LIBBUILD2_DIAGNOSTICS_HXX
+#define LIBBUILD2_DIAGNOSTICS_HXX
+
+#include <libbutl/diagnostics.mxx>
+
+#include <libbuild2/types.hxx>
+#include <libbuild2/utility.hxx>
+
+#include <libbuild2/export.hxx>
+
+namespace build2
+{
+ using butl::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 {};
+
+ // 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
+ //
+ LIBBUILD2_SYMEXPORT void
+ print_process (diag_record&, const char* const* args, size_t n = 0);
+
+ LIBBUILD2_SYMEXPORT void
+ print_process (const char* const* args, size_t n = 0);
+
+ inline void
+ print_process (diag_record& dr, const cstrings& args, size_t n = 0)
+ {
+ print_process (dr, args.data (), n != 0 ? n : args.size ());
+ }
+
+ inline void
+ print_process (const cstrings& args, size_t n = 0)
+ {
+ print_process (args.data (), n != 0 ? n : args.size ());
+ }
+
+ // Program verbosity level (-v/--verbose).
+ //
+ // 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.
+ //
+
+ // Forward-declarated in utility.hxx.
+ //
+ // extern uint16_t verb;
+ // const uint16_t verb_never = 7;
+
+ template <typename F> inline void l1 (const F& f) {if (verb >= 1) f ();}
+ template <typename F> inline void l2 (const F& f) {if (verb >= 2) f ();}
+ template <typename F> inline void l3 (const F& f) {if (verb >= 3) f ();}
+ template <typename F> inline void l4 (const F& f) {if (verb >= 4) f ();}
+ template <typename F> inline void l5 (const F& f) {if (verb >= 5) f ();}
+ template <typename F> inline void l6 (const F& f) {if (verb >= 6) f ();}
+
+ // Stream verbosity level. Determined by the diagnostic type (e.g., trace
+ // always has maximum verbosity) as well as the program verbosity. It is
+ // used to decide whether to print relative/absolute paths and default
+ // target extensions.
+ //
+ // Currently we have the following program to stream verbosity mapping:
+ //
+ // fail/error/warn/info <2:{0,0} 2:{0,1} >2:{1,2}
+ // trace *:{1,2}
+ //
+ // A stream that hasn't been (yet) assigned any verbosity explicitly (e.g.,
+ // ostringstream) defaults to maximum.
+ //
+ struct stream_verbosity
+ {
+ union
+ {
+ struct
+ {
+ // 0 - print relative.
+ // 1 - print absolute.
+ //
+ uint16_t path: 1;
+
+ // 0 - don't print.
+ // 1 - print if specified.
+ // 2 - print as 'foo.?' if unspecified and 'foo.' if specified as
+ // "no extension" (empty).
+ //
+ uint16_t extension: 2;
+ };
+ uint16_t value_;
+ };
+
+ constexpr
+ stream_verbosity (uint16_t p, uint16_t e): path (p), extension (e) {}
+
+ explicit
+ stream_verbosity (uint16_t v = 0): value_ (v) {}
+ };
+
+ constexpr stream_verbosity stream_verb_max = {1, 2};
+
+ // Default program to stream verbosity mapping, as outlined above.
+ //
+ inline stream_verbosity
+ stream_verb_map ()
+ {
+ return
+ verb < 2 ? stream_verbosity (0, 0) :
+ verb > 2 ? stream_verbosity (1, 2) :
+ /* */ stream_verbosity (0, 1);
+ }
+
+ LIBBUILD2_SYMEXPORT extern const int stream_verb_index;
+
+ inline stream_verbosity
+ stream_verb (ostream& os)
+ {
+ long v (os.iword (stream_verb_index));
+ return v == 0
+ ? stream_verb_max
+ : stream_verbosity (static_cast<uint16_t> (v - 1));
+ }
+
+ inline void
+ stream_verb (ostream& os, stream_verbosity v)
+ {
+ os.iword (stream_verb_index) = static_cast<long> (v.value_) + 1;
+ }
+
+ // Progress reporting.
+ //
+ using butl::diag_progress;
+ using butl::diag_progress_lock;
+
+ // Return true if progress is to be shown. The max_verb argument is the
+ // maximum verbosity level that this type of progress should be shown by
+ // default.
+ //
+ inline bool
+ show_progress (uint16_t max_verb)
+ {
+ return diag_progress_option
+ ? *diag_progress_option
+ : stderr_term && verb >= 1 && verb <= max_verb;
+ }
+
+ // Diagnostic facility, base infrastructure.
+ //
+ using butl::diag_stream_lock;
+ using butl::diag_stream;
+ using butl::diag_epilogue;
+
+ // Diagnostics stack. Each frame is "applied" to the fail/error/warn/info
+ // diag record.
+ //
+ // Unfortunately most of our use-cases don't fit into the 2-pointer small
+ // object optimization of std::function. So we have to complicate things
+ // a bit here.
+ //
+ struct LIBBUILD2_SYMEXPORT diag_frame
+ {
+ explicit
+ diag_frame (void (*f) (const diag_frame&, const diag_record&))
+ : func_ (f)
+ {
+ if (func_ != nullptr)
+ prev_ = stack (this);
+ }
+
+ diag_frame (diag_frame&& x)
+ : func_ (x.func_)
+ {
+ if (func_ != nullptr)
+ {
+ prev_ = x.prev_;
+ stack (this);
+
+ x.func_ = nullptr;
+ }
+ }
+
+ diag_frame& operator= (diag_frame&&) = delete;
+
+ diag_frame (const diag_frame&) = delete;
+ diag_frame& operator= (const diag_frame&) = delete;
+
+ ~diag_frame ()
+ {
+ if (func_ != nullptr )
+ stack (prev_);
+ }
+
+ static void
+ apply (const diag_record& r)
+ {
+ for (const diag_frame* f (stack ()); f != nullptr; f = f->prev_)
+ f->func_ (*f, r);
+ }
+
+ // Tip of the stack.
+ //
+ static const diag_frame*
+ stack () noexcept;
+
+ // Set the new and return the previous tip of the stack.
+ //
+ static const diag_frame*
+ stack (const diag_frame*) noexcept;
+
+ struct stack_guard
+ {
+ explicit stack_guard (const diag_frame* s): s_ (stack (s)) {}
+ ~stack_guard () {stack (s_);}
+ const diag_frame* s_;
+ };
+
+ private:
+ void (*func_) (const diag_frame&, const diag_record&);
+ const diag_frame* prev_;
+ };
+
+ template <typename F>
+ struct diag_frame_impl: diag_frame
+ {
+ explicit
+ diag_frame_impl (F f): diag_frame (&thunk), func_ (move (f)) {}
+
+ private:
+ static void
+ thunk (const diag_frame& f, const diag_record& r)
+ {
+ static_cast<const diag_frame_impl&> (f).func_ (r);
+ }
+
+ const F func_;
+ };
+
+ template <typename F>
+ inline diag_frame_impl<F>
+ make_diag_frame (F f)
+ {
+ return diag_frame_impl<F> (move (f));
+ }
+
+ // Diagnostic facility, project specifics.
+ //
+ struct LIBBUILD2_SYMEXPORT simple_prologue_base
+ {
+ explicit
+ simple_prologue_base (const char* type,
+ const char* mod,
+ const char* name,
+ stream_verbosity sverb)
+ : type_ (type), mod_ (mod), name_ (name), sverb_ (sverb) {}
+
+ void
+ operator() (const diag_record& r) const;
+
+ private:
+ const char* type_;
+ const char* mod_;
+ const char* name_;
+ const stream_verbosity sverb_;
+ };
+
+ struct LIBBUILD2_SYMEXPORT location_prologue_base
+ {
+ location_prologue_base (const char* type,
+ const char* mod,
+ const char* name,
+ const location& l,
+ stream_verbosity sverb)
+ : type_ (type), mod_ (mod), name_ (name),
+ loc_ (l),
+ sverb_ (sverb) {}
+
+ location_prologue_base (const char* type,
+ const char* mod,
+ const char* name,
+ path&& f,
+ stream_verbosity sverb)
+ : type_ (type), mod_ (mod), name_ (name),
+ file_ (move (f)), loc_ (&file_),
+ sverb_ (sverb) {}
+
+ void
+ operator() (const diag_record& r) const;
+
+ private:
+ const char* type_;
+ const char* mod_;
+ const char* name_;
+ const path file_;
+ const location loc_;
+ const stream_verbosity sverb_;
+ };
+
+ struct basic_mark_base
+ {
+ using simple_prologue = butl::diag_prologue<simple_prologue_base>;
+ using location_prologue = butl::diag_prologue<location_prologue_base>;
+
+ explicit
+ basic_mark_base (const char* type,
+ const void* data = nullptr,
+ diag_epilogue* epilogue = &diag_frame::apply,
+ stream_verbosity (*sverb) () = &stream_verb_map,
+ const char* mod = nullptr,
+ const char* name = nullptr)
+ : sverb_ (sverb),
+ type_ (type), mod_ (mod), name_ (name), data_ (data),
+ epilogue_ (epilogue) {}
+
+ simple_prologue
+ operator() () const
+ {
+ return simple_prologue (epilogue_, type_, mod_, name_, sverb_ ());
+ }
+
+ location_prologue
+ operator() (const location& l) const
+ {
+ return location_prologue (epilogue_, type_, mod_, name_, l, sverb_ ());
+ }
+
+ // fail (relative (src)) << ...
+ //
+ location_prologue
+ operator() (path&& f) const
+ {
+ return location_prologue (
+ epilogue_, type_, mod_, name_, move (f), sverb_ ());
+ }
+
+ template <typename L>
+ location_prologue
+ operator() (const L& l) const
+ {
+ return location_prologue (
+ epilogue_, type_, mod_, name_, get_location (l, data_), sverb_ ());
+ }
+
+ protected:
+ stream_verbosity (*sverb_) ();
+ const char* type_;
+ const char* mod_;
+ const char* name_;
+ const void* data_;
+ diag_epilogue* const epilogue_;
+ };
+ using basic_mark = butl::diag_mark<basic_mark_base>;
+
+ LIBBUILD2_SYMEXPORT extern const basic_mark error;
+ LIBBUILD2_SYMEXPORT extern const basic_mark warn;
+ LIBBUILD2_SYMEXPORT extern const basic_mark info;
+ LIBBUILD2_SYMEXPORT extern const basic_mark text;
+
+ // trace
+ //
+ struct trace_mark_base: basic_mark_base
+ {
+ explicit
+ trace_mark_base (const char* name, const void* data = nullptr)
+ : trace_mark_base (nullptr, name, data) {}
+
+ trace_mark_base (const char* mod,
+ const char* name,
+ const void* data = nullptr)
+ : basic_mark_base ("trace",
+ data,
+ nullptr, // No diag stack.
+ []() {return stream_verb_max;},
+ mod,
+ name) {}
+ };
+ using trace_mark = butl::diag_mark<trace_mark_base>;
+ using tracer = trace_mark;
+
+ // fail
+ //
+ struct fail_mark_base: basic_mark_base
+ {
+ explicit
+ fail_mark_base (const char* type,
+ const void* data = nullptr)
+ : basic_mark_base (type,
+ data,
+ [](const diag_record& r)
+ {
+ diag_frame::apply (r);
+ r.flush ();
+ throw failed ();
+ },
+ &stream_verb_map,
+ nullptr,
+ nullptr) {}
+ };
+ using fail_mark = butl::diag_mark<fail_mark_base>;
+
+ struct fail_end_base
+ {
+ [[noreturn]] void
+ operator() (const diag_record& r) const
+ {
+ // If we just throw then the record's destructor will see an active
+ // exception and will not flush the record.
+ //
+ r.flush ();
+ throw failed ();
+ }
+ };
+ using fail_end = butl::diag_noreturn_end<fail_end_base>;
+
+ LIBBUILD2_SYMEXPORT extern const fail_mark fail;
+ LIBBUILD2_SYMEXPORT extern const fail_end endf;
+}
+
+#endif // LIBBUILD2_DIAGNOSTICS_HXX
diff --git a/libbuild2/dump.cxx b/libbuild2/dump.cxx
new file mode 100644
index 0000000..a866fe3
--- /dev/null
+++ b/libbuild2/dump.cxx
@@ -0,0 +1,491 @@
+// file : libbuild2/dump.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <libbuild2/dump.hxx>
+
+#include <libbuild2/scope.hxx>
+#include <libbuild2/target.hxx>
+#include <libbuild2/variable.hxx>
+#include <libbuild2/context.hxx>
+#include <libbuild2/diagnostics.hxx>
+
+using namespace std;
+
+namespace build2
+{
+ // If type is false, don't print the value's type (e.g., because it is the
+ // same as variable's).
+ //
+ static void
+ dump_value (ostream& os, const value& v, bool type)
+ {
+ // First print attributes if any.
+ //
+ bool a (!v || (type && v.type != nullptr));
+
+ if (a)
+ os << '[';
+
+ const char* s ("");
+
+ if (type && v.type != nullptr)
+ {
+ os << s << v.type->name;
+ s = " ";
+ }
+
+ if (!v)
+ {
+ os << s << "null";
+ s = " ";
+ }
+
+ if (a)
+ os << ']';
+
+ // Now the value if there is one.
+ //
+ if (v)
+ {
+ names storage;
+ os << (a ? " " : "") << reverse (v, storage);
+ }
+ }
+
+ enum class variable_kind {scope, tt_pat, target, rule, prerequisite};
+
+ static void
+ dump_variable (ostream& os,
+ const variable_map& vm,
+ const variable_map::const_iterator& vi,
+ const scope& s,
+ variable_kind k)
+ {
+ // Target type/pattern-specific prepends/appends are kept untyped and not
+ // overriden.
+ //
+ if (k == variable_kind::tt_pat && vi.extra () != 0)
+ {
+ // @@ Might be useful to dump the cache.
+ //
+ const auto& p (vi.untyped ());
+ const variable& var (p.first);
+ const value& v (p.second);
+ assert (v.type == nullptr);
+
+ os << var << (v.extra == 1 ? " =+ " : " += ");
+ dump_value (os, v, false);
+ }
+ else
+ {
+ const auto& p (*vi);
+ const variable& var (p.first);
+ const value& v (p.second);
+
+ if (var.type != nullptr)
+ os << '[' << var.type->name << "] ";
+
+ os << var << " = ";
+
+ // If this variable is overriden, print both the override and the
+ // original values.
+ //
+ // @@ The override semantics for prerequisite-specific variables
+ // is still fuzzy/unimplemented, so ignore it for now.
+ //
+ if (k != variable_kind::prerequisite)
+ {
+ if (var.overrides != nullptr && !var.override ())
+ {
+ lookup org (v, var, vm);
+
+ // The original is always from this scope/target, so depth is 1.
+ //
+ lookup l (
+ s.find_override (
+ var,
+ make_pair (org, 1),
+ k == variable_kind::target || k == variable_kind::rule,
+ k == variable_kind::rule).first);
+
+ assert (l.defined ()); // We at least have the original.
+
+ if (org != l)
+ {
+ dump_value (os, *l, l->type != var.type);
+ os << " # original: ";
+ }
+ }
+ }
+
+ dump_value (os, v, v.type != var.type);
+ }
+ }
+
+ static void
+ dump_variables (ostream& os,
+ string& ind,
+ const variable_map& vars,
+ const scope& s,
+ variable_kind k)
+ {
+ for (auto i (vars.begin ()), e (vars.end ()); i != e; ++i)
+ {
+ os << endl
+ << ind;
+
+ dump_variable (os, vars, i, s, k);
+ }
+ }
+
+ // Dump target type/pattern-specific variables.
+ //
+ static void
+ dump_variables (ostream& os,
+ string& ind,
+ const variable_type_map& vtm,
+ const scope& s)
+ {
+ 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, vars.begin (), s, variable_kind::tt_pat);
+ }
+ else
+ {
+ os << endl
+ << ind << '{';
+ ind += " ";
+ dump_variables (os, ind, vars, s, variable_kind::tt_pat);
+ ind.resize (ind.size () - 2);
+ os << endl
+ << ind << '}';
+ }
+ }
+ }
+ }
+
+ static void
+ dump_target (optional<action> a,
+ ostream& os,
+ string& ind,
+ const target& t,
+ const scope& s,
+ bool rel)
+ {
+ // If requested, print the target and its prerequisites relative to the
+ // scope. To achieve this we are going to temporarily lower the stream
+ // path verbosity to level 0.
+ //
+ stream_verbosity osv, nsv;
+ if (rel)
+ {
+ osv = nsv = stream_verb (os);
+ nsv.path = 0;
+ stream_verb (os, nsv);
+ }
+
+ if (t.group != nullptr)
+ os << ind << t << " -> " << *t.group << endl;
+
+ os << ind << t << ':';
+
+ // First print target/rule-specific variables, if any.
+ //
+ {
+ bool tv (!t.vars.empty ());
+ bool rv (a && !t.state[*a].vars.empty ());
+
+ if (tv || rv)
+ {
+ if (rel)
+ stream_verb (os, osv); // We want variable values in full.
+
+ os << endl
+ << ind << '{';
+ ind += " ";
+
+ if (tv)
+ dump_variables (os, ind, t.vars, s, variable_kind::target);
+
+ if (rv)
+ {
+ // To distinguish target and rule-specific variables, we put the
+ // latter into a nested block.
+ //
+ // @@ Maybe if we also print the rule name, then we could make
+ // the block associated with that?
+
+ if (tv)
+ os << endl;
+
+ os << endl
+ << ind << '{';
+ ind += " ";
+ dump_variables (os, ind, t.state[*a].vars, s, variable_kind::rule);
+ ind.resize (ind.size () - 2);
+ os << endl
+ << ind << '}';
+ }
+
+ ind.resize (ind.size () - 2);
+ os << endl
+ << ind << '}';
+
+ if (rel)
+ stream_verb (os, nsv);
+
+ os << endl
+ << ind << t << ':';
+ }
+ }
+
+ bool used (false); // Target header has been used to display prerequisites.
+
+ // If the target has been matched to a rule, first print resolved
+ // prerequisite targets.
+ //
+ // Note: running serial and task_count is 0 before any operation has
+ // started.
+ //
+ action inner; // @@ Only for the inner part of the action currently.
+
+ if (size_t c = t[inner].task_count.load (memory_order_relaxed))
+ {
+ if (c == target::count_applied () || c == target::count_executed ())
+ {
+ bool f (false);
+ for (const target* pt: t.prerequisite_targets[inner])
+ {
+ if (pt == nullptr) // Skipped.
+ continue;
+
+ os << ' ' << *pt;
+ f = true;
+ }
+
+ // Only omit '|' if we have no prerequisites nor targets.
+ //
+ if (f || !t.prerequisites ().empty ())
+ {
+ os << " |";
+ used = true;
+ }
+ }
+ }
+
+ // Print prerequisites. Those that have prerequisite-specific variables
+ // have to be printed as a separate dependency.
+ //
+ const prerequisites& ps (t.prerequisites ());
+ for (auto i (ps.begin ()), e (ps.end ()); i != e; )
+ {
+ const prerequisite& p (*i++);
+ bool ps (!p.vars.empty ()); // Has prerequisite-specific vars.
+
+ if (ps && used) // If it has been used, get a new header.
+ os << endl
+ << ind << t << ':';
+
+ // Print it as a target if one has been cached.
+ //
+ if (const target* t = p.target.load (memory_order_relaxed)) // Serial.
+ os << ' ' << *t;
+ else
+ os << ' ' << p;
+
+ if (ps)
+ {
+ if (rel)
+ stream_verb (os, osv); // We want variable values in full.
+
+ os << ':' << endl
+ << ind << '{';
+ ind += " ";
+ dump_variables (os, ind, p.vars, s, variable_kind::prerequisite);
+ ind.resize (ind.size () - 2);
+ os << endl
+ << ind << '}';
+
+ if (rel)
+ stream_verb (os, nsv);
+
+ if (i != e) // If we have another, get a new header.
+ os << endl
+ << ind << t << ':';
+ }
+
+ used = !ps;
+ }
+
+ if (rel)
+ stream_verb (os, osv);
+ }
+
+ static void
+ dump_scope (optional<action> a,
+ ostream& os,
+ string& ind,
+ scope_map::const_iterator& i,
+ bool rel)
+ {
+ const 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. Print the root
+ // scope path (represented by an empty one) as a platform-dependent path
+ // separator.
+ //
+ if (d.empty ())
+ os << ind << dir_path::traits_type::directory_separator;
+ else
+ {
+ const dir_path& rd (rel ? relative (d) : d);
+ os << ind << (rd.empty () ? dir_path (".") : rd);
+ }
+
+ os << endl
+ << ind << '{';
+
+ const dir_path* orb (relative_base);
+ relative_base = &d;
+
+ ind += " ";
+
+ bool vb (false), sb (false), tb (false); // Variable/scope/target block.
+
+ // Target type/pattern-sepcific variables.
+ //
+ if (!p.target_vars.empty ())
+ {
+ dump_variables (os, ind, p.target_vars, p);
+ vb = true;
+ }
+
+ // Scope variables.
+ //
+ if (!p.vars.empty ())
+ {
+ if (vb)
+ os << endl;
+
+ dump_variables (os, ind, p.vars, p, variable_kind::scope);
+ vb = true;
+ }
+
+ // Nested scopes of which we are an immediate parent.
+ //
+ for (auto e (scopes.end ()); i != e && i->second.parent_scope () == &p;)
+ {
+ if (vb)
+ {
+ os << endl;
+ vb = false;
+ }
+
+ if (sb)
+ os << endl; // Extra newline between scope blocks.
+
+ os << endl;
+ dump_scope (a, os, ind, i, true /* relative */);
+ sb = true;
+ }
+
+ // Targets.
+ //
+ // Since targets can occupy multiple lines, we separate them with a
+ // blank line.
+ //
+ for (const auto& pt: targets)
+ {
+ const target& t (*pt);
+
+ if (&p != &t.base_scope ())
+ continue;
+
+ if (vb || sb || tb)
+ {
+ os << endl;
+ vb = sb = false;
+ }
+
+ os << endl;
+ dump_target (a, os, ind, t, p, true /* relative */);
+ tb = true;
+ }
+
+ ind.resize (ind.size () - 2);
+ relative_base = orb;
+
+ os << endl
+ << ind << '}';
+ }
+
+ void
+ dump (optional<action> a)
+ {
+ auto i (scopes.cbegin ());
+ assert (&i->second == global_scope);
+
+ // We don't lock diag_stream here as dump() is supposed to be called from
+ // the main thread prior/after to any other threads being spawned.
+ //
+ string ind;
+ ostream& os (*diag_stream);
+ dump_scope (a, os, ind, i, false /* relative */);
+ os << endl;
+ }
+
+ void
+ dump (const scope& s, const char* cind)
+ {
+ const scope_map_base& m (scopes); // Iterator interface.
+ auto i (m.find (s.out_path ()));
+ assert (i != m.end () && &i->second == &s);
+
+ string ind (cind);
+ ostream& os (*diag_stream);
+ dump_scope (nullopt /* action */, os, ind, i, false /* relative */);
+ os << endl;
+ }
+
+ void
+ dump (const target& t, const char* cind)
+ {
+ string ind (cind);
+ ostream& os (*diag_stream);
+ dump_target (nullopt /* action */,
+ os,
+ ind,
+ t,
+ t.base_scope (),
+ false /* relative */);
+ os << endl;
+ }
+}
diff --git a/libbuild2/dump.hxx b/libbuild2/dump.hxx
new file mode 100644
index 0000000..fd1886b
--- /dev/null
+++ b/libbuild2/dump.hxx
@@ -0,0 +1,34 @@
+// file : libbuild2/dump.hxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef LIBBUILD2_DUMP_HXX
+#define LIBBUILD2_DUMP_HXX
+
+#include <libbuild2/types.hxx>
+#include <libbuild2/utility.hxx>
+
+#include <libbuild2/action.hxx>
+
+#include <libbuild2/export.hxx>
+
+namespace build2
+{
+ class scope;
+ class target;
+
+ // Dump the build state to diag_stream. If action is specified, then assume
+ // rules have been matched for this action and dump action-specific
+ // information (like rule-specific variables).
+ //
+ LIBBUILD2_SYMEXPORT void
+ dump (optional<action> = nullopt);
+
+ LIBBUILD2_SYMEXPORT void
+ dump (const scope&, const char* ind = "");
+
+ LIBBUILD2_SYMEXPORT void
+ dump (const target&, const char* ind = "");
+}
+
+#endif // LIBBUILD2_DUMP_HXX
diff --git a/libbuild2/export.hxx b/libbuild2/export.hxx
new file mode 100644
index 0000000..514c845
--- /dev/null
+++ b/libbuild2/export.hxx
@@ -0,0 +1,58 @@
+#pragma once
+
+// Normally we don't export class templates (but do complete specializations),
+// inline functions, and classes with only inline member functions. Exporting
+// classes that inherit from non-exported/imported bases (e.g., std::string)
+// will end up badly. The only known workarounds are to not inherit or to not
+// export. Also, MinGW GCC doesn't like seeing non-exported functions being
+// used before their inline definition. The workaround is to reorder code. In
+// the end it's all trial and error.
+//
+// Exportation of explicit template instantiations is even hairier: MinGW GCC
+// requires __declspec(dllexport) on the extern template declaration while VC
+// wants it on the definition. Use LIBBUILD2_{DEC,DEF}EXPORT for that.
+//
+
+#if defined(LIBBUILD2_STATIC) // Using static.
+# define LIBBUILD2_SYMEXPORT
+# define LIBBUILD2_DECEXPORT
+#elif defined(LIBBUILD2_STATIC_BUILD) // Building static.
+# define LIBBUILD2_SYMEXPORT
+# define LIBBUILD2_DECEXPORT
+# define LIBBUILD2_DEFEXPORT
+#elif defined(LIBBUILD2_SHARED) // Using shared.
+# ifdef _WIN32
+# define LIBBUILD2_SYMEXPORT __declspec(dllimport)
+# define LIBBUILD2_DECEXPORT __declspec(dllimport)
+# else
+# define LIBBUILD2_SYMEXPORT
+# define LIBBUILD2_DECEXPORT
+# endif
+#elif defined(LIBBUILD2_SHARED_BUILD) // Building shared.
+# ifdef _WIN32
+# define LIBBUILD2_SYMEXPORT __declspec(dllexport)
+# if defined(_MSC_VER)
+# define LIBBUILD2_DECEXPORT
+# define LIBBUILD2_DEFEXPORT __declspec(dllexport)
+# else
+# define LIBBUILD2_DECEXPORT __declspec(dllexport)
+# define LIBBUILD2_DEFEXPORT
+# endif
+# else
+# define LIBBUILD2_SYMEXPORT
+# define LIBBUILD2_DECEXPORT
+# define LIBBUILD2_DEFEXPORT
+# endif
+#else
+// If none of the above macros are defined, then we assume we are being used
+// by some third-party build system that cannot/doesn't signal the library
+// type. Note that this fallback works for both static and shared but in case
+// of shared will be sub-optimal compared to having dllimport. Also note that
+// bootstrap ends up here as well.
+//
+// Using static or shared.
+//
+# define LIBBUILD2_SYMEXPORT
+# define LIBBUILD2_DECEXPORT
+# define LIBBUILD2_DEFEXPORT
+#endif
diff --git a/libbuild2/file.cxx b/libbuild2/file.cxx
new file mode 100644
index 0000000..5966168
--- /dev/null
+++ b/libbuild2/file.cxx
@@ -0,0 +1,1660 @@
+// file : libbuild2/file.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <libbuild2/file.hxx>
+
+#include <iostream> // cin
+
+#include <libbuild2/scope.hxx>
+#include <libbuild2/target.hxx>
+#include <libbuild2/context.hxx>
+#include <libbuild2/filesystem.hxx> // exists()
+#include <libbuild2/prerequisite.hxx>
+#include <libbuild2/diagnostics.hxx>
+
+#include <libbuild2/token.hxx>
+#include <libbuild2/lexer.hxx>
+#include <libbuild2/parser.hxx>
+
+using namespace std;
+using namespace butl;
+
+namespace build2
+{
+ // Standard and alternative build file/directory naming schemes.
+ //
+ const dir_path std_build_dir ("build");
+ const dir_path std_root_dir (dir_path (std_build_dir) /= "root");
+ const dir_path std_bootstrap_dir (dir_path (std_build_dir) /= "bootstrap");
+
+ const path std_root_file (std_build_dir / "root.build");
+ const path std_bootstrap_file (std_build_dir / "bootstrap.build");
+ const path std_src_root_file (std_bootstrap_dir / "src-root.build");
+ const path std_out_root_file (std_bootstrap_dir / "out-root.build");
+ const path std_export_file (std_build_dir / "export.build");
+
+ const string std_build_ext ("build");
+ const path std_buildfile_file ("buildfile");
+ const path std_buildignore_file (".buildignore");
+
+ //
+
+ const dir_path alt_build_dir ("build2");
+ const dir_path alt_root_dir (dir_path (alt_build_dir) /= "root");
+ const dir_path alt_bootstrap_dir (dir_path (alt_build_dir) /= "bootstrap");
+
+ const path alt_root_file (alt_build_dir / "root.build2");
+ const path alt_bootstrap_file (alt_build_dir / "bootstrap.build2");
+ const path alt_src_root_file (alt_bootstrap_dir / "src-root.build2");
+ const path alt_out_root_file (alt_bootstrap_dir / "out-root.build2");
+ const path alt_export_file (alt_build_dir / "export.build2");
+
+ const string alt_build_ext ("build2");
+ const path alt_buildfile_file ("build2file");
+ const path alt_buildignore_file (".build2ignore");
+
+ ostream&
+ operator<< (ostream& os, const subprojects& sps)
+ {
+ for (auto b (sps.begin ()), i (b); os && i != sps.end (); ++i)
+ {
+ // See find_subprojects() for details.
+ //
+ const project_name& n (
+ path::traits_type::is_separator (i->first.string ().back ())
+ ? empty_project_name
+ : i->first);
+
+ os << (i != b ? " " : "") << n << '@' << i->second;
+ }
+
+ return os;
+ }
+
+ // Check if the standard/alternative file/directory exists, returning empty
+ // path if it does not.
+ //
+ template <typename T>
+ static T
+ exists (const dir_path& d, const T& s, const T& a, optional<bool>& altn)
+ {
+ T p;
+ bool e;
+
+ if (altn)
+ {
+ p = d / (*altn ? a : s);
+ e = exists (p);
+ }
+ else
+ {
+ // Check the alternative name first since it is more specific.
+ //
+ p = d / a;
+
+ if ((e = exists (p)))
+ altn = true;
+ else
+ {
+ p = d / s;
+
+ if ((e = exists (p)))
+ altn = false;
+ }
+ }
+
+ return e ? p : T ();
+ }
+
+ bool
+ is_src_root (const dir_path& d, optional<bool>& altn)
+ {
+ // We can't have root without bootstrap.build.
+ //
+ return !exists (d, std_bootstrap_file, alt_bootstrap_file, altn).empty ();
+ }
+
+ bool
+ is_out_root (const dir_path& d, optional<bool>& altn)
+ {
+ return !exists (d, std_src_root_file, alt_src_root_file, altn).empty ();
+ }
+
+ dir_path
+ find_src_root (const dir_path& b, optional<bool>& altn)
+ {
+ for (dir_path d (b); !d.root () && d != home; d = d.directory ())
+ {
+ if (is_src_root (d, altn))
+ return d;
+ }
+
+ return dir_path ();
+ }
+
+ pair<dir_path, bool>
+ find_out_root (const dir_path& b, optional<bool>& altn)
+ {
+ for (dir_path d (b); !d.root () && d != home; d = d.directory ())
+ {
+ bool s;
+ if ((s = is_src_root (d, altn)) || is_out_root (d, altn))
+ return make_pair (move (d), s);
+ }
+
+ return make_pair (dir_path (), false);
+ }
+
+ dir_path old_src_root;
+ dir_path new_src_root;
+
+ // Remap the src_root variable value if it is inside old_src_root.
+ //
+ static inline void
+ remap_src_root (value& v)
+ {
+ if (!old_src_root.empty ())
+ {
+ dir_path& d (cast<dir_path> (v));
+
+ if (d.sub (old_src_root))
+ d = new_src_root / d.leaf (old_src_root);
+ }
+ }
+
+ static void
+ source (scope& root, scope& base, const path& bf, bool boot)
+ {
+ tracer trace ("source");
+
+ try
+ {
+ bool sin (bf.string () == "-");
+
+ ifdstream ifs;
+
+ if (!sin)
+ ifs.open (bf);
+ else
+ cin.exceptions (ifdstream::failbit | ifdstream::badbit);
+
+ istream& is (sin ? cin : ifs);
+
+ l5 ([&]{trace << "sourcing " << bf;});
+
+ parser p (boot);
+ p.parse_buildfile (is, bf, root, base);
+ }
+ catch (const io_error& e)
+ {
+ fail << "unable to read buildfile " << bf << ": " << e;
+ }
+ }
+
+ void
+ source (scope& root, scope& base, const path& bf)
+ {
+ source (root, base, bf, false);
+ }
+
+ bool
+ source_once (scope& root, scope& base, const path& bf, scope& once)
+ {
+ tracer trace ("source_once");
+
+ if (!once.buildfiles.insert (bf).second)
+ {
+ l5 ([&]{trace << "skipping already sourced " << bf;});
+ return false;
+ }
+
+ source (root, base, bf);
+ return true;
+ }
+
+ // Source (once) pre-*.build (pre is true) or post-*.build (otherwise) hooks
+ // from the specified directory (build/{bootstrap,root}/ of out_root) which
+ // must exist.
+ //
+ static void
+ source_hooks (scope& root, const dir_path& d, bool pre)
+ {
+ // While we could have used the wildcard pattern matching functionality,
+ // our needs are pretty basic and performance is quite important, so let's
+ // handle this ourselves.
+ //
+ try
+ {
+ for (const dir_entry& de: dir_iterator (d, false /* ignore_dangling */))
+ {
+ // 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.
+ // So let's first check that the name matches and only then check the
+ // type.
+ //
+ const path& n (de.path ());
+
+ if (n.string ().compare (0,
+ pre ? 4 : 5,
+ pre ? "pre-" : "post-") != 0 ||
+ n.extension () != root.root_extra->build_ext)
+ continue;
+
+ path f (d / n);
+
+ try
+ {
+ if (de.type () != entry_type::regular)
+ continue;
+ }
+ catch (const system_error& e)
+ {
+ fail << "unable to read buildfile " << f << ": " << e;
+ }
+
+ source_once (root, root, f);
+ }
+ }
+ catch (const system_error& e)
+ {
+ fail << "unable to iterate over " << d << ": " << e;
+ }
+ }
+
+ scope_map::iterator
+ create_root (scope& l, const dir_path& out_root, const dir_path& src_root)
+ {
+ auto i (scopes.rw (l).insert (out_root, true /* root */));
+ scope& rs (i->second);
+
+ // Set out_path. Note that src_path is set in setup_root() below.
+ //
+ if (rs.out_path_ != &i->first)
+ {
+ assert (rs.out_path_ == nullptr);
+ rs.out_path_ = &i->first;
+ }
+
+ // If this is already a root scope, verify that things are consistent.
+ //
+ {
+ value& v (rs.assign (var_out_root));
+
+ if (!v)
+ v = out_root;
+ else
+ {
+ const dir_path& p (cast<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 (var_src_root));
+
+ if (!v)
+ v = src_root;
+ else
+ {
+ const dir_path& p (cast<dir_path> (v));
+
+ if (p != src_root)
+ fail << "new src_root " << src_root << " does not match "
+ << "existing " << p;
+ }
+ }
+
+ return i;
+ }
+
+ void
+ setup_root (scope& s, bool forwarded)
+ {
+ // The caller must have made sure src_root is set on this scope.
+ //
+ value& v (s.assign (var_src_root));
+ assert (v);
+ const dir_path& d (cast<dir_path> (v));
+
+ if (s.src_path_ == nullptr)
+ s.src_path_ = &d;
+ else
+ assert (s.src_path_ == &d);
+
+ s.assign (var_forwarded) = forwarded;
+ }
+
+ scope&
+ setup_base (scope_map::iterator i,
+ const dir_path& out_base,
+ const dir_path& src_base)
+ {
+ scope& s (i->second);
+
+ // Set src/out_base variables.
+ //
+ value& ov (s.assign (var_out_base));
+
+ if (!ov)
+ ov = out_base;
+ else
+ assert (cast<dir_path> (ov) == out_base);
+
+ value& sv (s.assign (var_src_base));
+
+ if (!sv)
+ sv = src_base;
+ else
+ assert (cast<dir_path> (sv) == src_base);
+
+ // Set src/out_path. The key (i->first) is out_base.
+ //
+ if (s.out_path_ == nullptr)
+ s.out_path_ = &i->first;
+ else
+ assert (*s.out_path_ == out_base);
+
+ if (s.src_path_ == nullptr)
+ s.src_path_ = &cast<dir_path> (sv);
+ else
+ assert (*s.src_path_ == src_base);
+
+ return s;
+ }
+
+ pair<scope&, scope*>
+ switch_scope (scope& root, const dir_path& p)
+ {
+ // 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.rw (root).insert (p));
+ scope& base (i->second);
+ scope* rs (base.root_scope ());
+
+ if (rs != nullptr)
+ {
+ // 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)
+ rs = nrs;
+ }
+
+ // Switch to the new root scope.
+ //
+ if (rs != &root)
+ load_root (*rs); // Load new root(s) recursively.
+
+ // 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));
+ }
+
+ return pair<scope&, scope*> (base, rs);
+ }
+
+ dir_path
+ bootstrap_fwd (const dir_path& src_root, optional<bool>& altn)
+ {
+ path f (exists (src_root, std_out_root_file, alt_out_root_file, altn));
+
+ if (f.empty ())
+ return src_root;
+
+ // We cannot just source the buildfile since there is no scope to do
+ // this on yet.
+ //
+ auto p (extract_variable (f, *var_out_root));
+
+ if (!p.second)
+ fail << "variable out_root expected as first line in " << f;
+
+ try
+ {
+ return convert<dir_path> (move (p.first));
+ }
+ catch (const invalid_argument& e)
+ {
+ fail << "invalid out_root value in " << f << ": " << e << endf;
+ }
+ }
+
+ static void
+ setup_root_extra (scope& root, optional<bool>& altn)
+ {
+ assert (altn && root.root_extra == nullptr);
+ bool a (*altn);
+
+ root.root_extra = unique_ptr<scope::root_data> (
+ new scope::root_data {
+ a,
+ a ? alt_build_ext : std_build_ext,
+ a ? alt_build_dir : std_build_dir,
+ a ? alt_buildfile_file : std_buildfile_file,
+ a ? alt_buildignore_file : std_buildignore_file,
+ a ? alt_root_dir : std_root_dir,
+ a ? alt_bootstrap_dir : std_bootstrap_dir,
+ a ? alt_bootstrap_file : std_bootstrap_file,
+ a ? alt_root_file : std_root_file,
+ a ? alt_export_file : std_export_file,
+ a ? alt_src_root_file : std_src_root_file,
+ a ? alt_out_root_file : std_out_root_file,
+ {}, /* meta_operations */
+ {}, /* operations */
+ {}, /* modules */
+ {} /* override_cache */});
+
+ // 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.
+ //
+ root.insert_meta_operation (noop_id, mo_noop);
+ root.insert_meta_operation (perform_id, mo_perform);
+ root.insert_meta_operation (info_id, mo_info);
+
+ root.insert_operation (default_id, op_default);
+ root.insert_operation (update_id, op_update);
+ root.insert_operation (clean_id, op_clean);
+ }
+
+ void
+ bootstrap_out (scope& root, optional<bool>& altn)
+ {
+ const dir_path& out_root (root.out_path ());
+
+ path f (exists (out_root, std_src_root_file, alt_src_root_file, altn));
+
+ if (f.empty ())
+ return;
+
+ if (root.root_extra == nullptr)
+ setup_root_extra (root, altn);
+
+ //@@ TODO: if bootstrap files can source other bootstrap files (for
+ // example, as a 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) in buildfiles.
+ //
+ source_once (root, root, f);
+ }
+
+ pair<value, bool>
+ extract_variable (const path& bf, const variable& var)
+ {
+ try
+ {
+ ifdstream ifs (bf);
+
+ lexer lex (ifs, bf);
+ token t (lex.next ());
+ token_type tt;
+
+ if (t.type != token_type::word || t.value != var.name ||
+ ((tt = lex.next ().type) != token_type::assign &&
+ tt != token_type::prepend &&
+ tt != token_type::append))
+ {
+ return make_pair (value (), false);
+ }
+
+ parser p;
+ temp_scope tmp (global_scope->rw ());
+ p.parse_variable (lex, tmp, var, tt);
+
+ value* v (tmp.vars.find_to_modify (var).first);
+ assert (v != nullptr);
+
+ // Steal the value, the scope is going away.
+ //
+ return make_pair (move (*v), true);
+ }
+ catch (const io_error& e)
+ {
+ fail << "unable to read buildfile " << bf << ": " << e << endf;
+ }
+ }
+
+ // Extract the project name from bootstrap.build.
+ //
+ static project_name
+ find_project_name (const dir_path& out_root,
+ const dir_path& fallback_src_root,
+ optional<bool> out_src, // True if out_root is src_root.
+ optional<bool>& altn)
+ {
+ tracer trace ("find_project_name");
+
+ // First check if the root scope for this project has already been setup
+ // in which case we will have src_root and maybe even the name.
+ //
+ const dir_path* src_root (nullptr);
+ const scope& s (scopes.find (out_root));
+
+ if (s.root_scope () == &s && s.out_path () == out_root)
+ {
+ if (s.root_extra != nullptr)
+ {
+ if (!altn)
+ altn = s.root_extra->altn;
+ else
+ assert (*altn == s.root_extra->altn);
+ }
+
+ if (lookup l = s.vars[var_project])
+ return cast<project_name> (l);
+
+ src_root = s.src_path_;
+ }
+
+ // 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.
+ //
+ value src_root_v; // Need it to live until the end.
+
+ if (src_root == nullptr)
+ {
+ if (out_src ? *out_src : is_src_root (out_root, altn))
+ src_root = &out_root;
+ else
+ {
+ path f (exists (out_root, std_src_root_file, alt_src_root_file, altn));
+
+ if (f.empty ())
+ {
+ // Note: the same diagnostics as in main().
+ //
+ if (fallback_src_root.empty ())
+ fail << "no bootstrapped src_root for " << out_root <<
+ info << "consider reconfiguring this out_root";
+
+ src_root = &fallback_src_root;
+ }
+ else
+ {
+ auto p (extract_variable (f, *var_src_root));
+
+ if (!p.second)
+ fail << "variable src_root expected as first line in " << f;
+
+ src_root_v = move (p.first);
+ remap_src_root (src_root_v); // Remap if inside old_src_root.
+ src_root = &cast<dir_path> (src_root_v);
+
+ l5 ([&]{trace << "extracted src_root " << *src_root
+ << " for " << out_root;});
+ }
+ }
+ }
+
+ project_name name;
+ {
+ path f (exists (*src_root, std_bootstrap_file, alt_bootstrap_file, altn));
+
+ if (f.empty ())
+ fail << "no build/bootstrap.build in " << *src_root;
+
+ auto p (extract_variable (f, *var_project));
+
+ if (!p.second)
+ fail << "variable " << var_project->name << " expected "
+ << "as a first line in " << f;
+
+ name = cast<project_name> (move (p.first));
+ }
+
+ l5 ([&]{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.
+ //
+ static void
+ find_subprojects (subprojects& sps,
+ const dir_path& d,
+ const dir_path& root,
+ bool out)
+ {
+ tracer trace ("find_subprojects");
+
+ try
+ {
+ for (const dir_entry& de: dir_iterator (d, true /* ignore_dangling */))
+ {
+ if (de.type () != entry_type::directory)
+ continue;
+
+ dir_path sd (d / path_cast<dir_path> (de.path ()));
+
+ bool src (false);
+ optional<bool> altn;
+
+ if (!((out && is_out_root (sd, altn)) ||
+ (src = is_src_root (sd, altn))))
+ {
+ // We used to scan for subproject recursively but this is probably
+ // too loose (think of some tests laying around). In the future we
+ // should probably allow specifying something like extra/* or
+ // extra/** in subprojects.
+ //
+ //find_subprojects (sps, sd, root, out);
+ //
+ continue;
+ }
+
+ // Calculate relative subdirectory for this subproject.
+ //
+ dir_path dir (sd.leaf (root));
+ l5 ([&]{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.
+ //
+ project_name name (find_project_name (sd, dir_path (), src, altn));
+
+ // 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 a trailing directory separator to it.
+ //
+ if (name.empty ())
+ name = project_name (dir.posix_string () + '/',
+ project_name::raw_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;
+
+ l6 ([&]{trace << "skipping duplicate";});
+ }
+ }
+ }
+ catch (const system_error& e)
+ {
+ fail << "unable to iterate over " << d << ": " << e;
+ }
+ }
+
+ bool
+ bootstrap_src (scope& root, optional<bool>& altn)
+ {
+ tracer trace ("bootstrap_src");
+
+ bool r (false);
+
+ const dir_path& out_root (root.out_path ());
+ const dir_path& src_root (root.src_path ());
+
+ {
+ path f (exists (src_root, std_bootstrap_file, alt_bootstrap_file, altn));
+
+ if (root.root_extra == nullptr)
+ {
+ // If nothing so far has indicated the naming, assume standard.
+ //
+ if (!altn)
+ altn = false;
+
+ setup_root_extra (root, altn);
+ }
+
+ if (!f.empty ())
+ {
+ // 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 (f).second)
+ source (root, root, f, true);
+ else
+ l5 ([&]{trace << "skipping already sourced " << f;});
+
+ 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.
+ //
+ // Note: the amalgamation variable value is always a relative directory.
+ //
+ {
+ auto rp (root.vars.insert (*var_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 (cast<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.
+ //
+ l5 ([&]{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.
+ //
+ optional<bool> altn;
+ const dir_path& ad (find_out_root (out_root.directory (), altn).first);
+
+ if (!ad.empty ())
+ {
+ dir_path rd (ad.relative (out_root));
+ l5 ([&]{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 [project@]directory 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.
+ //
+ {
+ auto rp (root.vars.insert (*var_subprojects)); // 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 (exists (out_root))
+ {
+ l5 ([&]{trace << "looking for subprojects in " << out_root;});
+ find_subprojects (sps, out_root, out_root, true);
+ }
+
+ if (out_root != src_root)
+ {
+ l5 ([&]{trace << "looking for subprojects in " << 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
+ {
+ // Scan the (untyped) value and convert it to the "canonical" form,
+ // that is, a list of name@dir pairs.
+ //
+ subprojects sps;
+ names& ns (cast<names> (v));
+
+ for (auto i (ns.begin ()); i != ns.end (); ++i)
+ {
+ // Project name.
+ //
+ project_name n;
+ if (i->pair)
+ {
+ if (i->pair != '@')
+ fail << "unexpected pair style in variable subprojects";
+
+ try
+ {
+ n = convert<project_name> (move (*i));
+
+ if (n.empty ())
+ fail << "empty project name in variable subprojects";
+ }
+ catch (const invalid_argument&)
+ {
+ fail << "expected project name instead of '" << *i << "' in "
+ << "variable subprojects";
+ }
+
+ ++i; // Got to have the second half of the pair.
+ }
+
+ // Directory.
+ //
+ dir_path d;
+ try
+ {
+ d = convert<dir_path> (move (*i));
+
+ if (d.empty ())
+ fail << "empty directory in variable subprojects";
+ }
+ catch (const invalid_argument&)
+ {
+ fail << "expected directory instead of '" << *i << "' in "
+ << "variable subprojects";
+ }
+
+ // Figure out the project name if the user didn't specify one.
+ //
+ if (n.empty ())
+ {
+ optional<bool> altn;
+
+ // Pass fallback src_root since this is a subproject that was
+ // specified by the user so it is most likely in our src.
+ //
+ n = find_project_name (out_root / d,
+ src_root / d,
+ nullopt /* out_src */,
+ altn);
+
+ // See find_subprojects() for details on unnamed projects.
+ //
+ if (n.empty ())
+ n = project_name (d.posix_string () + '/',
+ project_name::raw_string);
+ }
+
+ sps.emplace (move (n), move (d));
+ }
+
+ // Change the value to the typed map.
+ //
+ v = move (sps);
+ }
+ }
+ }
+
+ return r;
+ }
+
+ void
+ bootstrap_pre (scope& root, optional<bool>& altn)
+ {
+ const dir_path& out_root (root.out_path ());
+
+ // This test is a bit loose in a sense that there can be a stray
+ // build/bootstrap/ directory that will make us mis-treat a project as
+ // following the standard naming scheme (the other way, while also
+ // possible, is a lot less likely). If this does becomes a problem, we can
+ // always tighten the test by also looking for a hook file with the
+ // correct extension.
+ //
+ dir_path d (exists (out_root, std_bootstrap_dir, alt_bootstrap_dir, altn));
+
+ if (!d.empty ())
+ {
+ if (root.root_extra == nullptr)
+ setup_root_extra (root, altn);
+
+ source_hooks (root, d, true /* pre */);
+ }
+ }
+
+ void
+ bootstrap_post (scope& root)
+ {
+ const dir_path& out_root (root.out_path ());
+
+ dir_path d (out_root / root.root_extra->bootstrap_dir);
+
+ if (exists (d))
+ source_hooks (root, d, false /* pre */);
+ }
+
+ bool
+ bootstrapped (scope& root)
+ {
+ // Use the subprojects variable set by bootstrap_src() as an indicator.
+ // It should either be NULL or typed (so we assume that the user will
+ // never set it to NULL).
+ //
+ auto l (root.vars[var_subprojects]);
+ return l.defined () && (l->null || l->type != nullptr);
+ }
+
+ // Return true if the inner/outer project (identified by out/src_root) of
+ // the 'origin' project (identified by orig) should be forwarded.
+ //
+ static inline bool
+ forwarded (const scope& orig,
+ const dir_path& out_root,
+ const dir_path& src_root,
+ optional<bool>& altn)
+ {
+ // The conditions are:
+ //
+ // 1. Origin is itself forwarded.
+ //
+ // 2. Inner/outer src_root != out_root.
+ //
+ // 3. Inner/outer out-root.build exists in src_root and refers out_root.
+ //
+ return (out_root != src_root &&
+ cast_false<bool> (orig.vars[var_forwarded]) &&
+ bootstrap_fwd (src_root, altn) == out_root);
+ }
+
+ void
+ create_bootstrap_outer (scope& root)
+ {
+ auto l (root.vars[var_amalgamation]);
+
+ if (!l)
+ return;
+
+ const dir_path& d (cast<dir_path> (l));
+ dir_path out_root (root.out_path () / d);
+ out_root.normalize (); // No need to actualize (d is a bunch of ..)
+
+ // 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 (root, out_root, dir_path ())->second);
+
+ bool bstrapped (bootstrapped (rs));
+
+ optional<bool> altn;
+ if (!bstrapped)
+ {
+ bootstrap_out (rs, altn); // #3 happens here (or it can be #1).
+
+ value& v (rs.assign (var_src_root));
+
+ if (!v)
+ {
+ if (is_src_root (out_root, altn)) // #2
+ v = out_root;
+ else // #1
+ {
+ dir_path src_root (root.src_path () / d);
+ src_root.normalize (); // No need to actualize (as above).
+ v = move (src_root);
+ }
+ }
+ else
+ remap_src_root (v); // Remap if inside old_src_root.
+
+ setup_root (rs, forwarded (root, out_root, v.as<dir_path> (), altn));
+ bootstrap_pre (rs, altn);
+ bootstrap_src (rs, altn);
+ // bootstrap_post() delayed until after create_bootstrap_outer().
+ }
+ else
+ {
+ altn = rs.root_extra->altn;
+
+ if (forwarded (root, rs.out_path (), rs.src_path (), altn))
+ rs.assign (var_forwarded) = true; // Only upgrade (see main()).
+ }
+
+ create_bootstrap_outer (rs);
+
+ if (!bstrapped)
+ bootstrap_post (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)
+ {
+ scope* r (&root);
+
+ if (auto l = root.vars[var_subprojects])
+ {
+ for (const auto& p: cast<subprojects> (l))
+ {
+ dir_path out_root (root.out_path () / p.second);
+
+ if (!out_base.empty () && !out_base.sub (out_root))
+ continue;
+
+ // The same logic to src_root as in create_bootstrap_outer().
+ //
+ scope& rs (create_root (root, out_root, dir_path ())->second);
+
+ optional<bool> altn;
+ if (!bootstrapped (rs))
+ {
+ bootstrap_out (rs, altn);
+
+ value& v (rs.assign (var_src_root));
+
+ if (!v)
+ {
+ v = is_src_root (out_root, altn)
+ ? out_root
+ : (root.src_path () / p.second);
+ }
+ else
+ remap_src_root (v); // Remap if inside old_src_root.
+
+ setup_root (rs, forwarded (root, out_root, v.as<dir_path> (), altn));
+ bootstrap_pre (rs, altn);
+ bootstrap_src (rs, altn);
+ bootstrap_post (rs);
+ }
+ else
+ {
+ altn = rs.root_extra->altn;
+ if (forwarded (root, rs.out_path (), rs.src_path (), altn))
+ rs.assign (var_forwarded) = true; // Only upgrade (see main()).
+ }
+
+ // 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.
+ //
+ r = &create_bootstrap_inner (rs, out_base);
+
+ if (!out_base.empty ())
+ break; // We have found our subproject.
+ }
+ }
+
+ return *r;
+ }
+
+ void
+ load_root (scope& root)
+ {
+ tracer trace ("load_root");
+
+ const dir_path& out_root (root.out_path ());
+ const dir_path& src_root (root.src_path ());
+
+ // As an optimization, check if we have already loaded root.build. If
+ // that's the case, then we have already been called for this project.
+ //
+ path f (src_root / root.root_extra->root_file);
+
+ if (root.buildfiles.find (f) != root.buildfiles.end ())
+ return;
+
+ // First load outer roots, if any.
+ //
+ if (scope* rs = root.parent_scope ()->root_scope ())
+ load_root (*rs);
+
+ // Finish off loading bootstrapped modules.
+ //
+ for (auto& p: root.root_extra->modules)
+ {
+ module_state& s (p.second);
+
+ if (s.boot && s.first)
+ load_module (root, root, p.first, s.loc);
+ }
+
+ for (auto& p: root.root_extra->modules)
+ {
+ module_state& s (p.second);
+
+ if (s.boot && !s.first)
+ load_module (root, root, p.first, s.loc);
+ }
+
+ // Load hooks and root.build.
+ //
+ // We can load the pre hooks before finishing off loading the bootstrapped
+ // modules (which, in case of config would load config.build) or after and
+ // one can come up with a plausible use-case for either approach. Note,
+ // however, that one can probably achieve adequate pre-modules behavior
+ // with a post-bootstrap hook.
+ //
+ dir_path hd (out_root / root.root_extra->root_dir);
+ bool he (exists (hd));
+
+ if (he) source_hooks (root, hd, true /* pre */);
+ if (exists (f)) source_once (root, root, f);
+ if (he) source_hooks (root, hd, false /* pre */);
+ }
+
+ scope&
+ load_project (scope& lock,
+ const dir_path& out_root,
+ const dir_path& src_root,
+ bool forwarded,
+ bool load)
+ {
+ assert (!forwarded || out_root != src_root);
+
+ auto i (create_root (lock, out_root, src_root));
+ scope& rs (i->second);
+
+ if (!bootstrapped (rs))
+ {
+ optional<bool> altn;
+ bootstrap_out (rs, altn);
+ setup_root (rs, forwarded);
+ bootstrap_pre (rs, altn);
+ bootstrap_src (rs, altn);
+ bootstrap_post (rs);
+ }
+ else
+ {
+ if (forwarded)
+ rs.assign (var_forwarded) = true; // Only upgrade (see main()).
+ }
+
+ if (load)
+ {
+ load_root (rs);
+ setup_base (i, out_root, src_root); // Setup as base.
+ }
+
+ return rs;
+ }
+
+ names
+ import (scope& ibase, name target, const location& loc)
+ {
+ tracer trace ("import");
+
+ l5 ([&]{trace << target << " from " << ibase;});
+
+ // 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 ();
+ return names {move (target)};
+ }
+
+ // Otherwise, get the project name and convert the target to unqualified.
+ //
+ project_name proj (move (*target.proj));
+ target.proj = nullopt;
+
+ scope& iroot (*ibase.root_scope ());
+
+ // Figure out this project's out_root.
+ //
+ dir_path out_root;
+
+ // First try the config.import.* mechanism. The idea is that if the user
+ // explicitly told us the project's location, then we should prefer that
+ // over anything that we may discover. In particular, we will prefer it
+ // over any bundled subprojects.
+ //
+ auto& vp (var_pool.rw (iroot));
+
+ for (;;) // Break-out loop.
+ {
+ string n ("config.import." + proj.variable ());
+
+ // config.import.<proj>
+ //
+ {
+ // Note: pattern-typed in context.cxx:reset() as an overridable
+ // variable of type abs_dir_path (path auto-completion).
+ //
+ const variable& var (vp.insert (n));
+
+ if (auto l = iroot[var])
+ {
+ out_root = cast<dir_path> (l); // Normalized and actualized.
+
+ // Mark as part of config.
+ //
+ if (config_save_variable != nullptr)
+ config_save_variable (iroot, var, 0 /* flags */);
+
+ // Empty config.import.* value means don't look in subprojects or
+ // amalgamations and go straight to the rule-specific import (e.g.,
+ // to use system-installed).
+ //
+ if (out_root.empty ())
+ {
+ target.proj = move (proj);
+ l5 ([&]{trace << "skipping " << target;});
+ return names {move (target)};
+ }
+
+ break;
+ }
+ }
+
+ // config.import.<proj>.<name>.<type>
+ // config.import.<proj>.<name>
+ //
+ // For example: config.import.build2.b.exe=/opt/build2/bin/b
+ //
+ if (!target.value.empty ())
+ {
+ auto lookup = [&iroot, &vp, &loc] (string name) -> path
+ {
+ // Note: pattern-typed in context.cxx:reset() as an overridable
+ // variable of type path.
+ //
+ const variable& var (vp.insert (move (name)));
+
+ path r;
+ if (auto l = iroot[var])
+ {
+ r = cast<path> (l);
+
+ if (r.empty ())
+ fail (loc) << "empty path in " << var.name;
+
+ if (config_save_variable != nullptr)
+ config_save_variable (iroot, var, 0 /* flags */);
+ }
+
+ return r;
+ };
+
+ // First try .<name>.<type>, then just .<name>.
+ //
+ path p;
+ if (target.typed ())
+ p = lookup (n + '.' + target.value + '.' + target.type);
+
+ if (p.empty ())
+ p = lookup (n + '.' + target.value);
+
+ if (!p.empty ())
+ {
+ // If the path is relative, then keep it project-qualified assuming
+ // import phase 2 knows what to do with it. Think:
+ //
+ // config.import.build2.b=b-boot
+ //
+ if (p.relative ())
+ target.proj = move (proj);
+
+ target.dir = p.directory ();
+ target.value = p.leaf ().string ();
+
+ return names {move (target)};
+ }
+ }
+
+ // Otherwise 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 ())
+ {
+ l5 ([&]{trace << "looking in " << *r;});
+
+ // First check the amalgamation itself.
+ //
+ if (r != &iroot && cast<project_name> (r->vars[var_project]) == proj)
+ {
+ out_root = r->out_path ();
+ break;
+ }
+
+ if (auto l = r->vars[var_subprojects])
+ {
+ const auto& m (cast<subprojects> (l));
+ auto i (m.find (proj));
+
+ if (i != m.end ())
+ {
+ const dir_path& d ((*i).second);
+ out_root = r->out_path () / d;
+ break;
+ }
+ }
+
+ if (!r->vars[var_amalgamation])
+ break;
+ }
+
+ break;
+ }
+
+ // If we couldn't find the project, convert it back into qualified target
+ // and return to let someone else (e.g., a rule) take a stab at it.
+ //
+ if (out_root.empty ())
+ {
+ target.proj = move (proj);
+ l5 ([&]{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.
+ //
+ // The user can also specify the out_root of the amalgamation that contains
+ // our project. For now we only consider top-level sub-projects.
+ //
+ scope* root;
+ dir_path src_root;
+
+ // See if this is a forwarded configuration. For top-level project we want
+ // to use the same logic as in main() while for inner subprojects -- as in
+ // create_bootstrap_inner().
+ //
+ bool fwd (false);
+ optional<bool> altn;
+ if (is_src_root (out_root, altn))
+ {
+ src_root = move (out_root);
+ out_root = bootstrap_fwd (src_root, altn);
+ fwd = (src_root != out_root);
+ }
+
+ for (const scope* proot (nullptr); ; proot = root)
+ {
+ bool top (proot == nullptr);
+
+ root = &create_root (iroot, out_root, src_root)->second;
+
+ bool bstrapped (bootstrapped (*root));
+
+ if (!bstrapped)
+ {
+ bootstrap_out (*root, altn);
+
+ // Check that the bootstrap process set src_root.
+ //
+ auto l (root->vars[*var_src_root]);
+ if (l)
+ {
+ // Note that unlike main() here we fail hard. The idea is that if
+ // the project we are importing is misconfigured, then it should be
+ // fixed first.
+ //
+ const dir_path& p (cast<dir_path> (l));
+
+ if (!src_root.empty () && p != src_root)
+ fail (loc) << "configured src_root " << p << " does not match "
+ << "discovered " << src_root;
+ }
+ else
+ fail (loc) << "unable to determine src_root for imported " << proj <<
+ info << "consider configuring " << out_root;
+
+ setup_root (*root,
+ (top
+ ? fwd
+ : forwarded (*proot, out_root, l->as<dir_path> (), altn)));
+
+ bootstrap_pre (*root, altn);
+ bootstrap_src (*root, altn);
+ if (!top)
+ bootstrap_post (*root);
+ }
+ else
+ {
+ altn = root->root_extra->altn;
+
+ if (src_root.empty ())
+ src_root = root->src_path ();
+
+ if (top ? fwd : forwarded (*proot, out_root, src_root, altn))
+ root->assign (var_forwarded) = true; // Only upgrade (see main()).
+ }
+
+ if (top)
+ {
+ create_bootstrap_outer (*root);
+
+ if (!bstrapped)
+ bootstrap_post (*root);
+ }
+
+ // Now we know this project's name as well as all its subprojects.
+ //
+ if (cast<project_name> (root->vars[var_project]) == proj)
+ break;
+
+ if (auto l = root->vars[var_subprojects])
+ {
+ const auto& m (cast<subprojects> (l));
+ auto i (m.find (proj));
+
+ if (i != m.end ())
+ {
+ const dir_path& d ((*i).second);
+ altn = nullopt;
+ out_root = root->out_path () / d;
+ src_root = is_src_root (out_root, altn) ? out_root : dir_path ();
+ continue;
+ }
+ }
+
+ fail (loc) << out_root << " is not out_root for " << proj;
+ }
+
+ // Load the imported root scope.
+ //
+ load_root (*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 (var_out_root) = move (out_root);
+ ts.assign (var_src_root) = move (src_root);
+
+ // Also pass the target being imported in the import.target variable.
+ //
+ {
+ value& v (ts.assign (var_import_target));
+
+ if (!target.empty ()) // Otherwise leave NULL.
+ v = target; // Can't move (need for diagnostics below).
+ }
+
+ // 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 () / root->root_extra->export_file);
+
+ try
+ {
+ ifdstream ifs (es);
+
+ l5 ([&]{trace << "importing " << es;});
+
+ // @@ Should we verify these are all unqualified names? Or maybe
+ // there is a use-case for the export stub to return a qualified
+ // name?
+ //
+ parser p;
+ names v (p.parse_export_stub (ifs, es, iroot, ts));
+
+ // If there were no export directive executed in an export stub, assume
+ // the target is not exported.
+ //
+ if (v.empty () && !target.empty ())
+ fail (loc) << "target " << target << " is not exported by project "
+ << proj;
+
+ return v;
+ }
+ catch (const io_error& e)
+ {
+ fail (loc) << "unable to read buildfile " << es << ": " << e;
+ }
+
+ return names (); // Never reached.
+ }
+
+ const target*
+ import (const prerequisite_key& pk, bool existing)
+ {
+ tracer trace ("import");
+
+ assert (pk.proj);
+ const project_name& proj (*pk.proj);
+
+ // Target type-specific search.
+ //
+ const target_key& tk (pk.tk);
+ const target_type& tt (*tk.type);
+
+ // Try to find the executable in PATH (or CWD if relative).
+ //
+ if (tt.is_a<exe> ())
+ {
+ path n (*tk.dir);
+ n /= *tk.name;
+ if (tk.ext)
+ {
+ n += '.';
+ n += *tk.ext;
+ }
+
+ // Only search in PATH (or CWD).
+ //
+ process_path pp (process::try_path_search (n, true, dir_path (), true));
+
+ if (!pp.empty ())
+ {
+ path& p (pp.effect);
+ assert (!p.empty ()); // We searched for a simple name.
+
+ const exe* t (
+ !existing
+ ? &targets.insert<exe> (tt,
+ p.directory (),
+ dir_path (), // No out (out of project).
+ p.leaf ().base ().string (),
+ p.extension (), // Always specified.
+ trace)
+ : targets.find<exe> (tt,
+ p.directory (),
+ dir_path (),
+ p.leaf ().base ().string (),
+ p.extension (),
+ trace));
+
+ if (t != nullptr)
+ {
+ if (!existing)
+ t->path (move (p));
+ else
+ assert (t->path () == p);
+
+ return t;
+ }
+ }
+ }
+
+ if (existing)
+ return nullptr;
+
+ // @@ 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...
+ //
+ diag_record dr;
+ dr << fail << "unable to import target " << pk;
+
+ if (proj.empty ())
+ dr << info << "consider adding its installation location" <<
+ info << "or explicitly specify its project name";
+ else
+ dr << info << "use config.import." << proj.variable ()
+ << " command line variable to specify its project out_root";
+
+ dr << endf;
+ }
+}
diff --git a/libbuild2/file.hxx b/libbuild2/file.hxx
new file mode 100644
index 0000000..e2e8aaa
--- /dev/null
+++ b/libbuild2/file.hxx
@@ -0,0 +1,243 @@
+// file : libbuild2/file.hxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef LIBBUILD2_FILE_HXX
+#define LIBBUILD2_FILE_HXX
+
+#include <map>
+
+#include <libbuild2/types.hxx>
+#include <libbuild2/utility.hxx>
+
+#include <libbuild2/scope.hxx>
+#include <libbuild2/variable.hxx> // list_value
+
+#include <libbuild2/export.hxx>
+
+namespace build2
+{
+ class target;
+ class location;
+ class prerequisite_key;
+
+ using subprojects = std::map<project_name, dir_path>;
+
+ LIBBUILD2_SYMEXPORT ostream&
+ operator<< (ostream&, const subprojects&); // Print as name@dir sequence.
+
+ LIBBUILD2_SYMEXPORT extern const dir_path std_build_dir; // build/
+
+ // build/root.build
+ //
+ LIBBUILD2_SYMEXPORT extern const path std_root_file;
+
+ // build/bootstrap.build
+ //
+ LIBBUILD2_SYMEXPORT extern const path std_bootstrap_file;
+
+ LIBBUILD2_SYMEXPORT extern const path std_buildfile_file; // buildfile
+ LIBBUILD2_SYMEXPORT extern const path alt_buildfile_file; // build2file
+
+ // If the altn argument value is present, then it indicates whether we are
+ // using the standard or the alternative build file/directory naming.
+ //
+ // The overall plan is to run various "file exists" tests using the standard
+ // and the alternative names. The first test that succeeds determines the
+ // naming scheme (by setting altn) and from then on all the remaining tests
+ // only look for things in this scheme.
+ //
+ LIBBUILD2_SYMEXPORT bool
+ is_src_root (const dir_path&, optional<bool>& altn);
+
+ LIBBUILD2_SYMEXPORT bool
+ is_out_root (const dir_path&, optional<bool>& altn);
+
+ // 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. Note
+ // that if the input is normalized/actualized, then the output will be as
+ // well.
+ //
+ LIBBUILD2_SYMEXPORT dir_path
+ find_src_root (const dir_path&, optional<bool>& altn);
+
+ // 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 with
+ // the result returned as the second half of the pair. Note also that if the
+ // input is normalized/actualized, then the output will be as well.
+ //
+ LIBBUILD2_SYMEXPORT pair<dir_path, bool>
+ find_out_root (const dir_path&, optional<bool>& altn);
+
+ // The old/new src_root paths. See main() (where they are set) for details.
+ //
+ LIBBUILD2_SYMEXPORT extern dir_path old_src_root;
+ LIBBUILD2_SYMEXPORT extern dir_path new_src_root;
+
+ // If buildfile is '-', then read from STDIN.
+ //
+ LIBBUILD2_SYMEXPORT void
+ source (scope& root, scope& base, const path&);
+
+ // As above but first check if this buildfile has already been sourced for
+ // the base scope. Return false if the file has already been sourced.
+ //
+ bool
+ source_once (scope& root, scope& base, const path&);
+
+ // As above but checks against the specified scope rather than base.
+ //
+ LIBBUILD2_SYMEXPORT bool
+ source_once (scope& root, scope& base, const path&, scope& once);
+
+ // Create project's root scope. Only set the src_root variable if the passed
+ // src_root value is not empty. The scope argument is only used as proof of
+ // lock.
+ //
+ LIBBUILD2_SYMEXPORT scope_map::iterator
+ create_root (scope&, const dir_path& out_root, const dir_path& src_root);
+
+ // Setup root scope. Note that it assumes the src_root variable has already
+ // been set.
+ //
+ LIBBUILD2_SYMEXPORT void
+ setup_root (scope&, bool forwarded);
+
+ // Setup the base scope (set *_base variables, etc).
+ //
+ LIBBUILD2_SYMEXPORT scope&
+ setup_base (scope_map::iterator,
+ const dir_path& out_base,
+ const dir_path& src_base);
+
+ // Return a scope for the specified directory (first). Note that switching
+ // to this scope might also involve switch to a new root scope (second) if
+ // the new scope is in another project. If the new scope is not in any
+ // project, then NULL is returned in second.
+ //
+ LIBBUILD2_SYMEXPORT pair<scope&, scope*>
+ switch_scope (scope& root, const dir_path&);
+
+ // Bootstrap and optionally load an ad hoc (sub)project (i.e., the kind that
+ // is not discovered and loaded automatically by bootstrap/load functions
+ // above).
+ //
+ // Note that we expect the outer project (if any) to be bootstrapped and
+ // loaded and currently we do not add the newly loaded subproject to the
+ // outer project's subprojects map.
+ //
+ // The scope argument is only used as proof of lock.
+ //
+ LIBBUILD2_SYMEXPORT scope&
+ load_project (scope&,
+ const dir_path& out_root,
+ const dir_path& src_root,
+ bool forwarded,
+ bool load = true);
+
+ // Bootstrap the project's forward. Return the forwarded-to out_root or
+ // src_root if there is no forward. See is_{src,out}_root() for the altn
+ // argument semantics.
+ //
+ LIBBUILD2_SYMEXPORT dir_path
+ bootstrap_fwd (const dir_path& src_root, optional<bool>& altn);
+
+ // Bootstrap the project's root scope, the out part.
+ //
+ LIBBUILD2_SYMEXPORT void
+ bootstrap_out (scope& root, optional<bool>& altn);
+
+ // Bootstrap the project's root scope, the src part. Return true if we
+ // loaded anything (which confirms the src_root is not bogus).
+ //
+ LIBBUILD2_SYMEXPORT bool
+ bootstrap_src (scope& root, optional<bool>& altn);
+
+ // Return true if this scope has already been bootstrapped, that is, the
+ // following calls have already been made:
+ //
+ // bootstrap_out()
+ // setup_root()
+ // bootstrap_src()
+ //
+ LIBBUILD2_SYMEXPORT bool
+ bootstrapped (scope& root);
+
+ // Execute pre/post-bootstrap hooks. Similar to bootstrap_out/sr(), should
+ // only be called once per project bootstrap.
+ //
+ LIBBUILD2_SYMEXPORT void
+ bootstrap_pre (scope& root, optional<bool>& altn);
+
+ LIBBUILD2_SYMEXPORT void
+ bootstrap_post (scope& root);
+
+ // Create and bootstrap outer root scopes, if any. Loading is done by
+ // load_root().
+ //
+ LIBBUILD2_SYMEXPORT void
+ create_bootstrap_outer (scope& root);
+
+ // Create and bootstrap inner root scopes, if any, recursively.
+ //
+ // If out_base is not empty, then only bootstrap scope between root and base
+ // returning the innermost created root scope or root if none were created.
+ //
+ // Note that loading is done by load_root().
+ //
+ LIBBUILD2_SYMEXPORT scope&
+ create_bootstrap_inner (scope& root, const dir_path& out_base = dir_path ());
+
+ // Load project's root.build (and root pre/post hooks) unless already
+ // loaded. Also make sure all outer root scopes are loaded prior to loading
+ // this root scope.
+ //
+ LIBBUILD2_SYMEXPORT void
+ load_root (scope& 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 or any variable overrides. Return
+ // an indication of whether the variable was found.
+ //
+ LIBBUILD2_SYMEXPORT pair<value, bool>
+ extract_variable (const path&, const variable&);
+
+ // 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?"
+ //
+ LIBBUILD2_SYMEXPORT names
+ import (scope& base, name, const location&);
+
+ const target&
+ import (const prerequisite_key&);
+
+ // As above but only imports as an already existing target. Unlike the above
+ // version, this one can be called during the execute phase.
+ //
+ // Note: similar to search_existing().
+ //
+ const target*
+ import_existing (const prerequisite_key&);
+}
+
+#include <libbuild2/file.ixx>
+
+#endif // LIBBUILD2_FILE_HXX
diff --git a/libbuild2/file.ixx b/libbuild2/file.ixx
new file mode 100644
index 0000000..f8a79be
--- /dev/null
+++ b/libbuild2/file.ixx
@@ -0,0 +1,31 @@
+// file : libbuild2/file.ixx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <libbuild2/export.hxx>
+
+namespace build2
+{
+ inline bool
+ source_once (scope& root, scope& base, const path& bf)
+ {
+ return source_once (root, base, bf, base);
+ }
+
+ LIBBUILD2_SYMEXPORT const target*
+ import (const prerequisite_key&, bool existing);
+
+ inline const target&
+ import (const prerequisite_key& pk)
+ {
+ assert (phase == run_phase::match);
+ return *import (pk, false);
+ }
+
+ inline const target*
+ import_existing (const prerequisite_key& pk)
+ {
+ assert (phase == run_phase::match || phase == run_phase::execute);
+ return import (pk, true);
+ }
+}
diff --git a/libbuild2/filesystem.cxx b/libbuild2/filesystem.cxx
new file mode 100644
index 0000000..83408fa
--- /dev/null
+++ b/libbuild2/filesystem.cxx
@@ -0,0 +1,274 @@
+// file : libbuild2/filesystem.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <libbuild2/filesystem.hxx>
+
+#include <libbuild2/context.hxx>
+#include <libbuild2/diagnostics.hxx>
+
+using namespace std;
+using namespace butl;
+
+namespace build2
+{
+ void
+ touch (const path& p, bool create, uint16_t v)
+ {
+ if (verb >= v)
+ text << "touch " << p;
+
+ if (dry_run)
+ return;
+
+ try
+ {
+ touch_file (p, create);
+ }
+ catch (const system_error& e)
+ {
+ fail << "unable to touch file " << p << ": " << e << endf;
+ }
+ }
+
+ timestamp
+ mtime (const char* p)
+ {
+ try
+ {
+ return file_mtime (p);
+ }
+ catch (const system_error& e)
+ {
+ fail << "unable to obtain file " << p << " modification time: " << e
+ << endf;
+ }
+ }
+
+ fs_status<mkdir_status>
+ mkdir (const dir_path& d, uint16_t v)
+ {
+ // 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 >= v)
+ text << "mkdir " << d;
+
+ fail << "unable to create directory " << d << ": " << e << endf;
+ }
+
+ if (ms == mkdir_status::success)
+ {
+ if (verb >= v)
+ text << "mkdir " << d;
+ }
+
+ return ms;
+ }
+
+ fs_status<mkdir_status>
+ mkdir_p (const dir_path& d, uint16_t v)
+ {
+ // 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 >= v)
+ text << "mkdir -p " << d;
+
+ fail << "unable to create directory " << d << ": " << e << endf;
+ }
+
+ if (ms == mkdir_status::success)
+ {
+ if (verb >= v)
+ text << "mkdir -p " << d;
+ }
+
+ return ms;
+ }
+
+ fs_status<rmfile_status>
+ rmsymlink (const path& p, bool d, uint16_t v)
+ {
+ auto print = [&p, v] ()
+ {
+ if (verb >= v)
+ text << "rm " << p.string ();
+ };
+
+ rmfile_status rs;
+
+ try
+ {
+ rs = dry_run
+ ? (butl::entry_exists (p)
+ ? rmfile_status::success
+ : rmfile_status::not_exist)
+ : try_rmsymlink (p, d);
+ }
+ catch (const system_error& e)
+ {
+ print ();
+ fail << "unable to remove symlink " << p.string () << ": " << e << endf;
+ }
+
+ if (rs == rmfile_status::success)
+ print ();
+
+ return rs;
+ }
+
+ fs_status<butl::rmdir_status>
+ rmdir_r (const dir_path& d, bool dir, uint16_t v)
+ {
+ using namespace butl;
+
+ if (work.sub (d)) // Don't try to remove working directory.
+ return rmdir_status::not_empty;
+
+ if (!build2::entry_exists (d))
+ return rmdir_status::not_exist;
+
+ if (verb >= v)
+ text << "rmdir -r " << d;
+
+ if (!dry_run)
+ {
+ try
+ {
+ butl::rmdir_r (d, dir);
+ }
+ catch (const system_error& e)
+ {
+ fail << "unable to remove directory " << d << ": " << e;
+ }
+ }
+
+ return rmdir_status::success;
+ }
+
+ bool
+ exists (const path& f, bool fs, bool ie)
+ {
+ try
+ {
+ return file_exists (f, fs, ie);
+ }
+ catch (const system_error& e)
+ {
+ fail << "unable to stat path " << f << ": " << e << endf;
+ }
+ }
+
+ bool
+ exists (const dir_path& d, bool ie)
+ {
+ try
+ {
+ return dir_exists (d, ie);
+ }
+ catch (const system_error& e)
+ {
+ fail << "unable to stat path " << d << ": " << e << endf;
+ }
+ }
+
+ bool
+ entry_exists (const path& p, bool fs, bool ie)
+ {
+ try
+ {
+ return butl::entry_exists (p, fs, ie);
+ }
+ catch (const system_error& e)
+ {
+ fail << "unable to stat path " << p << ": " << e << endf;
+ }
+ }
+
+ bool
+ empty (const dir_path& d)
+ {
+ try
+ {
+ return dir_empty (d);
+ }
+ catch (const system_error& e)
+ {
+ fail << "unable to scan directory " << d << ": " << e << endf;
+ }
+ }
+
+ fs_status<mkdir_status>
+ mkdir_buildignore (const dir_path& d, const path& n, uint16_t verbosity)
+ {
+ fs_status<mkdir_status> r (mkdir (d, verbosity));
+
+ // Create the .buildignore file if the directory was created (and so is
+ // empty) or the file doesn't exist.
+ //
+ path p (d / n);
+ if (r || !exists (p))
+ touch (p, true /* create */, verbosity);
+
+ return r;
+ }
+
+ bool
+ empty_buildignore (const dir_path& d, const path& n)
+ {
+ try
+ {
+ for (const dir_entry& de: dir_iterator (d, false /* ignore_dangling */))
+ {
+ // The .buildignore filesystem entry should be of the regular file
+ // type.
+ //
+ if (de.path () != n || de.ltype () != entry_type::regular)
+ return false;
+ }
+ }
+ catch (const system_error& e)
+ {
+ fail << "unable to scan directory " << d << ": " << e;
+ }
+
+ return true;
+ }
+
+ fs_status<rmdir_status>
+ rmdir_buildignore (const dir_path& d, const path& n, uint16_t verbosity)
+ {
+ // We should remove the .buildignore file only if the subsequent rmdir()
+ // will succeed. In other words if the directory stays after the function
+ // call then the .buildignore file must stay also, if present. Thus, we
+ // first check that the directory is otherwise empty and doesn't contain
+ // the working directory.
+ //
+ path p (d / n);
+ if (exists (p) && empty_buildignore (d, n) && !work.sub (d))
+ rmfile (p, verbosity);
+
+ // Note that in case of a system error the directory is likely to stay with
+ // the .buildignore file already removed. Trying to restore it feels like
+ // an overkill here.
+ //
+ return rmdir (d, verbosity);
+ }
+}
diff --git a/libbuild2/filesystem.hxx b/libbuild2/filesystem.hxx
new file mode 100644
index 0000000..6dca528
--- /dev/null
+++ b/libbuild2/filesystem.hxx
@@ -0,0 +1,182 @@
+// file : libbuild2/filesystem.hxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef LIBBUILD2_FILESYSTEM_HXX
+#define LIBBUILD2_FILESYSTEM_HXX
+
+#include <libbutl/filesystem.mxx>
+
+#include <libbuild2/types.hxx>
+#include <libbuild2/utility.hxx>
+
+#include <libbuild2/export.hxx>
+
+// Higher-level filesystem utilities built on top of <libbutl/filesystem.mxx>.
+//
+// Compared to the libbutl's versions, these handle errors and issue
+// diagnostics. Some of them also print the corresponding command line
+// equivalent at the specified verbosity level. Note that most of such
+// functions also respect the dry_run flag.
+//
+namespace build2
+{
+ using butl::auto_rmfile;
+ using butl::auto_rmdir;
+
+ // 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 <libbutl/filesystem.mxx>
+ //
+ 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;}
+ };
+
+ // Set the file access and modification times (unless dry-run) to the
+ // current time printing the standard diagnostics starting from the
+ // specified verbosity level. If the file does not exist and create is true,
+ // create it and fail otherwise.
+ //
+ LIBBUILD2_SYMEXPORT void
+ touch (const path&, bool create, uint16_t verbosity = 1);
+
+ // Return the modification time for an existing regular file and
+ // timestamp_nonexistent otherwise. Print the diagnostics and fail on system
+ // error.
+ //
+ LIBBUILD2_SYMEXPORT timestamp
+ mtime (const char*);
+
+ inline timestamp
+ mtime (const path& p)
+ {
+ return mtime (p.string ().c_str ());
+ }
+
+ // Create the directory and print the standard diagnostics starting from the
+ // specified verbosity level.
+ //
+ // Note that these functions ignore the dry_run flag (we might need to save
+ // something in such a directory, such as depdb, ignoring dry_run). Overall,
+ // it feels like we should establish the structure even for dry-run.
+ //
+ // Note that the implementation may not be suitable if the performance is
+ // important and it is expected that the directory will exist in most cases.
+ // See the fsdir{} rule for details.
+ //
+ using mkdir_status = butl::mkdir_status;
+
+ LIBBUILD2_SYMEXPORT fs_status<mkdir_status>
+ mkdir (const dir_path&, uint16_t verbosity = 1);
+
+ LIBBUILD2_SYMEXPORT fs_status<mkdir_status>
+ mkdir_p (const dir_path&, uint16_t verbosity = 1);
+
+ // Remove the file (unless dry-run) and print the standard diagnostics
+ // starting from the specified verbosity level. 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.
+ //
+ using rmfile_status = butl::rmfile_status;
+
+ template <typename T>
+ fs_status<rmfile_status>
+ rmfile (const path&, const T& target, uint16_t verbosity = 1);
+
+ inline fs_status<rmfile_status>
+ rmfile (const path& f, int verbosity = 1) // Literal overload (int).
+ {
+ return rmfile (f, f, static_cast<uint16_t> (verbosity));
+ }
+
+ inline fs_status<rmfile_status>
+ rmfile (const path& f, uint16_t verbosity) // Overload (verb_never).
+ {
+ return rmfile (f, f, verbosity);
+ }
+
+ // Similar to rmfile() but for symlinks.
+ //
+ LIBBUILD2_SYMEXPORT fs_status<rmfile_status>
+ rmsymlink (const path&, bool dir, uint16_t verbosity);
+
+ // Similar to rmfile() but for directories (note: not -r).
+ //
+ using rmdir_status = butl::rmdir_status;
+
+ template <typename T>
+ fs_status<rmdir_status>
+ rmdir (const dir_path&, const T& target, uint16_t verbosity = 1);
+
+ inline fs_status<rmdir_status>
+ rmdir (const dir_path& d, int verbosity = 1) // Literal overload (int).
+ {
+ return rmdir (d, d, static_cast<uint16_t> (verbosity));
+ }
+
+ inline fs_status<rmdir_status>
+ rmdir (const dir_path& d, uint16_t verbosity) // Overload (verb_never).
+ {
+ return rmdir (d, d, verbosity);
+ }
+
+ // Remove the directory recursively (unless dry-run) and print the standard
+ // diagnostics starting from the specified verbosity level. Note that this
+ // function returns not_empty if we try to remove a working directory. If
+ // the dir argument is false, then the directory itself is not removed.
+ //
+ // @@ Collides (via ADL) with butl::rmdir_r(), which sucks.
+ //
+ LIBBUILD2_SYMEXPORT fs_status<rmdir_status>
+ rmdir_r (const dir_path&, bool dir = true, uint16_t verbosity = 1);
+
+ // Check for a file, directory or filesystem entry existence. Print the
+ // diagnostics and fail on system error, unless ignore_error is true.
+ //
+ LIBBUILD2_SYMEXPORT bool
+ exists (const path&, bool follow_symlinks = true, bool ignore_error = false);
+
+ LIBBUILD2_SYMEXPORT bool
+ exists (const dir_path&, bool ignore_error = false);
+
+ LIBBUILD2_SYMEXPORT bool
+ entry_exists (const path&,
+ bool follow_symlinks = false,
+ bool ignore_error = false);
+
+ // Check for a directory emptiness. Print the diagnostics and fail on system
+ // error.
+ //
+ LIBBUILD2_SYMEXPORT bool
+ empty (const dir_path&);
+
+ // Directories containing .buildignore (or .build2ignore in the alternative
+ // naming scheme) file are automatically ignored by recursive name patterns.
+ // For now the file is just a marker and its contents don't matter. Note
+ // that these functions ignore dry-run.
+
+ // Create a directory containing an empty .buildignore file.
+ //
+ LIBBUILD2_SYMEXPORT fs_status<mkdir_status>
+ mkdir_buildignore (const dir_path&, const path&, uint16_t verbosity = 1);
+
+ // Return true if the directory is empty or only contains the .buildignore
+ // file. Fail if the directory doesn't exist.
+ //
+ LIBBUILD2_SYMEXPORT bool
+ empty_buildignore (const dir_path&, const path&);
+
+ // Remove a directory if it is empty or only contains the .buildignore file.
+ //
+ LIBBUILD2_SYMEXPORT fs_status<rmdir_status>
+ rmdir_buildignore (const dir_path&, const path&, uint16_t verbosity = 1);
+}
+
+#include <libbuild2/filesystem.txx>
+
+#endif // LIBBUILD2_FILESYSTEM_HXX
diff --git a/libbuild2/filesystem.txx b/libbuild2/filesystem.txx
new file mode 100644
index 0000000..6166082
--- /dev/null
+++ b/libbuild2/filesystem.txx
@@ -0,0 +1,111 @@
+// file : libbuild2/filesystem.txx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <type_traits> // is_base_of
+
+#include <libbuild2/context.hxx>
+#include <libbuild2/diagnostics.hxx>
+
+namespace build2
+{
+ template <typename T>
+ fs_status<butl::rmfile_status>
+ rmfile (const path& f, const T& t, uint16_t v)
+ {
+ 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.
+ //
+ auto print = [&f, &t, v] ()
+ {
+ if (verb >= v)
+ {
+ if (verb >= 2)
+ text << "rm " << f;
+ else if (verb)
+ text << "rm " << t;
+ }
+ };
+
+ rmfile_status rs;
+
+ try
+ {
+ rs = dry_run
+ ? file_exists (f) ? rmfile_status::success : rmfile_status::not_exist
+ : try_rmfile (f);
+ }
+ catch (const system_error& e)
+ {
+ print ();
+ fail << "unable to remove file " << f << ": " << e << endf;
+ }
+
+ if (rs == rmfile_status::success)
+ print ();
+
+ return rs;
+ }
+
+ template <typename T>
+ fs_status<butl::rmdir_status>
+ rmdir (const dir_path& d, const T& t, uint16_t v)
+ {
+ using namespace butl;
+
+ // 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.
+ //
+ auto print = [&d, &t, v] ()
+ {
+ if (verb >= v)
+ {
+ if (verb >= 2)
+ text << "rmdir " << d;
+ else if (verb)
+ text << (std::is_base_of<dir_path, T>::value ? "rmdir " : "rm ") << t;
+ }
+ };
+
+ bool w (false); // Don't try to remove working directory.
+ rmdir_status rs;
+ try
+ {
+ rs = dry_run
+ ? dir_exists (d) ? rmdir_status::success : rmdir_status::not_exist
+ : !(w = work.sub (d)) ? try_rmdir (d) : rmdir_status::not_empty;
+ }
+ catch (const system_error& e)
+ {
+ print ();
+ fail << "unable to remove directory " << d << ": " << e << endf;
+ }
+
+ switch (rs)
+ {
+ case rmdir_status::success:
+ {
+ print ();
+ break;
+ }
+ case rmdir_status::not_empty:
+ {
+ if (verb >= v && verb >= 2)
+ {
+ text << d << " is "
+ << (w ? "current working directory" : "not empty")
+ << ", not removing";
+ }
+ break;
+ }
+ case rmdir_status::not_exist:
+ break;
+ }
+
+ return rs;
+ }
+}
diff --git a/libbuild2/function+call.test.testscript b/libbuild2/function+call.test.testscript
new file mode 100644
index 0000000..755572e
--- /dev/null
+++ b/libbuild2/function+call.test.testscript
@@ -0,0 +1,161 @@
+# file : libbuild2/function+call.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+: qual-implicit
+:
+$* <'print $dummy.dummy0()' >'abc'
+
+: qual-explicit
+:
+$* <'print $dummy.qual()' >'abc'
+
+: qual-fail
+:
+$* <'print $qual()' 2>>EOE != 0
+buildfile:1:8: error: unmatched call to qual()
+ info: candidate: dummy.qual()
+EOE
+
+: derived-base
+: Test derived-to-base overload resolution
+:
+$* <'print $dummy.abs([dir_path] .)' >'false';
+$* <'print $dummy.abs([abs_dir_path] .)' >'true'
+
+: variadic
+:
+$* <'print $variadic([bool] true, foo, bar)' >'3'
+
+: fail
+:
+$* <'$fail()' 2>>EOE != 0
+error: failed
+buildfile:1:2: info: while calling fail()
+EOE
+
+: fail-invalid-arg
+:
+$* <'$fail_arg(abc)' 2>>EOE != 0
+error: invalid argument: invalid uint64 value: 'abc'
+buildfile:1:2: info: while calling fail_arg(<untyped>)
+EOE
+
+: no-match-name
+:
+$* <'$bogus()' 2>>EOE != 0
+buildfile:1:2: error: unmatched call to bogus()
+EOE
+
+: no-match-count
+:
+$* <'$dummy0(abc)' 2>>EOE != 0
+buildfile:1:2: error: unmatched call to dummy0(<untyped>)
+ info: candidate: dummy0(), qualified name dummy.dummy0
+EOE
+
+: no-match-type
+:
+$* <'$dummy1([uint64] 123)' 2>>EOE != 0
+buildfile:1:2: error: unmatched call to dummy1(uint64)
+ info: candidate: dummy1(string), qualified name dummy.dummy1
+EOE
+
+: ambig
+:
+$* <'$ambig(abc)' 2>>~/EOE/ != 0
+buildfile:1:2: error: ambiguous call to ambig(<untyped>)
+/((
+ info: candidate: ambig(<untyped> [, uint64]), qualified name dummy.ambig
+ info: candidate: ambig(<untyped> [, string]), qualified name dummy.ambig
+/)|(
+ info: candidate: ambig(<untyped> [, string]), qualified name dummy.ambig
+ info: candidate: ambig(<untyped> [, uint64]), qualified name dummy.ambig
+/))
+EOE
+
+: unmatched
+:
+$* <'$ambig(abc, def)' 2>>~/EOE/ != 0
+buildfile:1:2: error: unmatched call to ambig(<untyped>, <untyped>)
+/((
+ info: candidate: ambig(<untyped> [, uint64]), qualified name dummy.ambig
+ info: candidate: ambig(<untyped> [, string]), qualified name dummy.ambig
+/)|(
+ info: candidate: ambig(<untyped> [, string]), qualified name dummy.ambig
+ info: candidate: ambig(<untyped> [, uint64]), qualified name dummy.ambig
+/))
+EOE
+
+: reverse
+:
+$* <'print $reverse([string] abc)' >'abc'
+
+: optional-absent
+:
+$* <'print $optional()' >'true'
+
+: optional-present
+:
+$* <'print $optional(abc)' >'false'
+
+: null-true
+:
+$* <'print $nullable([null])' >'true'
+
+: null-false
+:
+$* <'print $nullable(nonull)' >'false'
+
+: null-fail
+:
+$* <'$dummy1([string null])' 2>>EOE != 0
+error: invalid argument: null value
+buildfile:1:2: info: while calling dummy1(string)
+EOE
+
+: print-call-1-untyped
+:
+$* <'$bogus(abc)' 2>>EOE != 0
+buildfile:1:2: error: unmatched call to bogus(<untyped>)
+EOE
+
+: print-call-1-typed
+:
+$* <'$bogus([uint64] 123)' 2>>EOE != 0
+buildfile:1:2: error: unmatched call to bogus(uint64)
+EOE
+
+: print-call-2
+:
+$* <'$bogus(abc, [uint64] 123)' 2>>EOE != 0
+buildfile:1:2: error: unmatched call to bogus(<untyped>, uint64)
+EOE
+
+: print-fovl
+:
+$* <'$ambig([bool] true)' 2>>~/EOE/ != 0
+buildfile:1:2: error: ambiguous call to ambig(bool)
+/((
+ info: candidate: ambig(<untyped> [, uint64]), qualified name dummy.ambig
+ info: candidate: ambig(<untyped> [, string]), qualified name dummy.ambig
+/)|(
+ info: candidate: ambig(<untyped> [, string]), qualified name dummy.ambig
+ info: candidate: ambig(<untyped> [, uint64]), qualified name dummy.ambig
+/))
+EOE
+
+: print-fovl-variadic
+:
+$* <'$variadic(abc)' 2>>EOE != 0
+buildfile:1:2: error: unmatched call to variadic(<untyped>)
+ info: candidate: variadic(bool [, ...])
+EOE
+
+: member-function
+:
+$* <'print $dummy.length([path] abc)' >'3'
+
+: data-member
+:
+$* <'print $dummy.type([name] cxx{foo})' >'cxx'
diff --git a/libbuild2/function+syntax.test.testscript b/libbuild2/function+syntax.test.testscript
new file mode 100644
index 0000000..f8240f3
--- /dev/null
+++ b/libbuild2/function+syntax.test.testscript
@@ -0,0 +1,29 @@
+# file : libbuild2/function+syntax.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+$* <'$dump()' >:'' : none
+$* <'$dump( )' >:'' : none-in-spaces
+$* <'$dump("")' >'{}' : one-empty
+$* <'$dump(a)' >'a' : one-single
+$* <'$dump(a b c)' >'a b c' : one-list
+$* <'$dump(d/t{x y z})' >'d/t{x} d/t{y} d/t{z}' : one-names
+
+$* <'print a$dummy1([string] b)c' >'abc' : concat
+$* <'print $dummy2([uint64] 123, [uint64] 321)' >'444' : multi-arg
+
+: quoting
+: Verify we can inhibit function call with quoting
+:
+$* <<EOI >>EOO
+foo = FOO
+bar = BAR
+
+print $foo"($bar)"
+print "$foo"($bar)
+print "$foo""($bar)"
+EOI
+FOOBAR
+FOOBAR
+FOOBAR
+EOO
diff --git a/libbuild2/function.cxx b/libbuild2/function.cxx
new file mode 100644
index 0000000..2d4dce9
--- /dev/null
+++ b/libbuild2/function.cxx
@@ -0,0 +1,400 @@
+// file : libbuild2/function.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <libbuild2/function.hxx>
+
+#include <cstring> // strchr()
+
+using namespace std;
+
+namespace build2
+{
+ ostream&
+ operator<< (ostream& os, const function_overload& f)
+ {
+ os << f.name << '(';
+
+ bool v (f.arg_max == function_overload::arg_variadic);
+ size_t n (v ? max (f.arg_min, f.arg_types.size ()): f.arg_max);
+
+ // Handle variadic tail as the last pseudo-argument.
+ //
+ for (size_t i (0); i != n + (v ? 1 : 0); ++i)
+ {
+ if (i == f.arg_min)
+ os << (i != 0 ? " [" : "[");
+
+ os << (i != 0 ? ", " : "");
+
+ if (i == n) // Variadic tail (last).
+ os << "...";
+ else
+ {
+ // If count is greater than f.arg_typed, then we assume the rest are
+ // valid but untyped.
+ //
+ const optional<const value_type*> t (
+ i < f.arg_types.size () ? f.arg_types[i] : nullopt);
+
+ os << (t ? (*t != nullptr ? (*t)->name : "<untyped>") : "<anytype>");
+ }
+ }
+
+ if (n + (v ? 1 : 0) > f.arg_min)
+ os << ']';
+
+ os << ')';
+
+ if (f.alt_name != nullptr)
+ {
+ auto k (strchr (f.alt_name, '.') == nullptr
+ ? "unqualified"
+ : "qualified");
+
+ os << ", " << k << " name " << f.alt_name;
+ }
+
+ return os;
+ }
+
+ bool function_map::
+ defined (const string& name) const
+ {
+ assert (!name.empty ());
+
+ // If this is a qualified function name then check if it is already
+ // defined.
+ //
+ if (name.back () != '.')
+ return map_.find (name) != map_.end ();
+
+ // If any function of the specified family is already defined, then one of
+ // them should be the first element that is greater than the dot-terminated
+ // family name. Here we rely on the fact that the dot character is less
+ // than any character of unqualified function and family names.
+ //
+ size_t n (name.size ());
+ assert (n > 1);
+
+ auto i (map_.upper_bound (name));
+ return i != map_.end () && i->first.compare (0, n, name) == 0;
+ }
+
+ auto function_map::
+ insert (string name, function_overload f) -> iterator
+ {
+ // Sanity checks.
+ //
+ assert (f.arg_min <= f.arg_max &&
+ f.arg_types.size () <= f.arg_max &&
+ f.impl != nullptr);
+
+ auto i (map_.emplace (move (name), move (f)));
+
+ i->second.name = i->first.c_str ();
+ return i;
+ }
+
+ pair<value, bool> function_map::
+ call (const scope* base,
+ const string& name,
+ vector_view<value> args,
+ const location& loc,
+ bool fa) const
+ {
+ auto print_call = [&name, &args] (ostream& os)
+ {
+ os << name << '(';
+
+ for (size_t i (0); i != args.size (); ++i)
+ {
+ const value_type* t (args[i].type);
+ os << (i != 0 ? ", " : "") << (t != nullptr ? t->name : "<untyped>");
+ }
+
+ os << ')';
+ };
+
+ // Overload resolution.
+ //
+ // Ours is pretty simple: we sort all the overloads into three ranks:
+ //
+ // 0 -- all the arguments match exactly (perfect match)
+ // 1 -- one or more arguments match via the derived-to-base conversion
+ // 2 -- one or more arguments match via the reversal to untyped
+ //
+ // More than one match of the same rank is ambiguous.
+ //
+ auto ip (map_.equal_range (name));
+
+ size_t rank (~0);
+ small_vector<const function_overload*, 2> ovls;
+ {
+ size_t count (args.size ());
+
+ for (auto it (ip.first); it != ip.second; ++it)
+ {
+ const function_overload& f (it->second);
+
+ // Argument count match.
+ //
+ if (count < f.arg_min || count > f.arg_max)
+ continue;
+
+ // Argument types match.
+ //
+ size_t r (0);
+ {
+ size_t i (0), n (min (count, f.arg_types.size ()));
+ for (; i != n; ++i)
+ {
+ if (!f.arg_types[i]) // Anytyped.
+ continue;
+
+ const value_type* at (args[i].type);
+ const value_type* ft (*f.arg_types[i]);
+
+ if (at == ft) // Types match perfectly.
+ continue;
+
+ if (at != nullptr && ft != nullptr)
+ {
+ while ((at = at->base_type) != nullptr && at != ft) ;
+
+ if (at != nullptr) // Types match via derived-to-base.
+ {
+ if (r < 1)
+ r = 1;
+ continue;
+ }
+ }
+
+ if (ft == nullptr) // Types match via reversal to untyped.
+ {
+ if (r < 2)
+ r = 2;
+ continue;
+ }
+
+ break; // No match.
+ }
+
+ if (i != n)
+ continue; // No match.
+ }
+
+ // Better or just as good a match?
+ //
+ if (r <= rank)
+ {
+ if (r < rank) // Better.
+ {
+ rank = r;
+ ovls.clear ();
+ }
+
+ ovls.push_back (&f);
+ }
+
+ // Continue looking to detect ambiguities.
+ }
+ }
+
+ switch (ovls.size ())
+ {
+ case 1:
+ {
+ // Print the call location in case the function fails.
+ //
+ auto g (
+ make_exception_guard (
+ [fa, &loc, &print_call] ()
+ {
+ if (fa && verb != 0)
+ {
+ diag_record dr (info (loc));
+ dr << "while calling "; print_call (dr.os);
+ }
+ }));
+
+ auto f (ovls.back ());
+
+ // If one or more arguments match via the reversal to untyped (rank 2),
+ // then we need to go over the overload's arguments one more time an
+ // untypify() those that we need to reverse.
+ //
+ if (rank == 2)
+ {
+ size_t n (args.size ());
+ assert (n <= f->arg_types.size ());
+
+ for (size_t i (0); i != n; ++i)
+ {
+ if (f->arg_types[i] &&
+ *f->arg_types[i] == nullptr &&
+ args[i].type != nullptr)
+ untypify (args[i]);
+ }
+ }
+
+ try
+ {
+ return make_pair (f->impl (base, move (args), *f), true);
+ }
+ catch (const invalid_argument& e)
+ {
+ diag_record dr (fail);
+ dr << "invalid argument";
+
+ if (*e.what () != '\0')
+ dr << ": " << e;
+
+ dr << endf;
+ }
+ }
+ case 0:
+ {
+ if (!fa)
+ return make_pair (value (nullptr), false);
+
+ // No match.
+ //
+ diag_record dr;
+
+ dr << fail (loc) << "unmatched call to "; print_call (dr.os);
+
+ for (auto i (ip.first); i != ip.second; ++i)
+ dr << info << "candidate: " << i->second;
+
+ // If this is an unqualified name, then also print qualified
+ // functions that end with this name. But skip functions that we
+ // have already printed in the previous loop.
+ //
+ if (name.find ('.') == string::npos)
+ {
+ size_t n (name.size ());
+
+ for (auto i (functions.begin ()); i != functions.end (); ++i)
+ {
+ const string& q (i->first);
+ const function_overload& f (i->second);
+
+ if ((f.alt_name == nullptr || f.alt_name != name) &&
+ q.size () > n)
+ {
+ size_t p (q.size () - n);
+ if (q[p - 1] == '.' && q.compare (p, n, name) == 0)
+ dr << info << "candidate: " << i->second;
+ }
+ }
+ }
+
+ dr << endf;
+ }
+ default:
+ {
+ // Ambigous match.
+ //
+ diag_record dr;
+ dr << fail (loc) << "ambiguous call to "; print_call (dr.os);
+
+ for (auto f: ovls)
+ dr << info << "candidate: " << *f;
+
+ dr << endf;
+ }
+ }
+ }
+
+ value function_family::
+ default_thunk (const scope* base,
+ vector_view<value> args,
+ const function_overload& f)
+ {
+ // Call the cast thunk.
+ //
+ struct cast_data // Prefix of function_cast::data.
+ {
+ value (*const thunk) (const scope*, vector_view<value>, const void*);
+ };
+
+ auto d (reinterpret_cast<const cast_data*> (&f.data));
+ return d->thunk (base, move (args), d);
+ }
+
+#if !defined(_WIN32)
+ constexpr const optional<const value_type*>* function_args<>::types;
+#else
+ const optional<const value_type*>* const function_args<>::types = nullptr;
+#endif
+
+ void function_family::entry::
+ insert (string n, function_overload f) const
+ {
+ // Figure out qualification.
+ //
+ string qn;
+ size_t p (n.find ('.'));
+
+ if (p == string::npos)
+ {
+ if (!qual.empty ())
+ {
+ qn = qual;
+ qn += '.';
+ qn += n;
+ }
+ }
+ else if (p == 0)
+ {
+ assert (!qual.empty ());
+ n.insert (0, qual);
+ }
+
+ auto i (qn.empty () ? functions.end () : functions.insert (move (qn), f));
+ auto j (functions.insert (move (n), move (f)));
+
+ // If we have both, then set alternative names.
+ //
+ if (i != functions.end ())
+ {
+ i->second.alt_name = j->first.c_str ();
+ j->second.alt_name = i->first.c_str ();
+ }
+ }
+
+ // Static-initialize the function map and populate with builtin functions.
+ //
+ function_map functions;
+
+ void builtin_functions (); // functions-builtin.cxx
+ void filesystem_functions (); // functions-filesystem.cxx
+ void name_functions (); // functions-name.cxx
+ void path_functions (); // functions-path.cxx
+ void process_functions (); // functions-process.cxx
+ void process_path_functions (); // functions-process-path.cxx
+ void regex_functions (); // functions-regex.cxx
+ void string_functions (); // functions-string.cxx
+ void target_triplet_functions (); // functions-target-triplet.cxx
+ void project_name_functions (); // functions-target-triplet.cxx
+
+ struct functions_init
+ {
+ functions_init ()
+ {
+ builtin_functions ();
+ filesystem_functions ();
+ name_functions ();
+ path_functions ();
+ process_functions ();
+ process_path_functions ();
+ regex_functions ();
+ string_functions ();
+ target_triplet_functions ();
+ project_name_functions ();
+ }
+ };
+
+ static const functions_init init_;
+}
diff --git a/libbuild2/function.hxx b/libbuild2/function.hxx
new file mode 100644
index 0000000..6b2bfe1
--- /dev/null
+++ b/libbuild2/function.hxx
@@ -0,0 +1,905 @@
+// file : libbuild2/function.hxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef LIBBUILD2_FUNCTION_HXX
+#define LIBBUILD2_FUNCTION_HXX
+
+#include <map>
+#include <utility> // index_sequence
+#include <type_traits> // aligned_storage
+
+#include <libbuild2/types.hxx>
+#include <libbuild2/utility.hxx>
+
+#include <libbuild2/variable.hxx>
+#include <libbuild2/diagnostics.hxx>
+
+#include <libbuild2/export.hxx>
+
+namespace build2
+{
+ // Functions can be overloaded based on types of their arguments but
+ // arguments can be untyped and a function can elect to accept an argument
+ // of any type.
+ //
+ // Functions can be qualified (e.g, string.length(), path.directory()) and
+ // unqualified (e.g., length(), directory()). Only functions overloaded on
+ // static types can be unqualified plus they should also define a qualified
+ // alias.
+ //
+ // Low-level function implementation would be called with a list of values
+ // as arguments. There is also higher-level, more convenient support for
+ // defining functions as pointers to functions (including capture-less
+ // lambdas), pointers to member functions (e.g., string::size()), or
+ // pointers to data members (e.g., name::type). In this case the build2
+ // function types are automatically matched to C++ function types according
+ // to these rules:
+ //
+ // T - statically-typed (value_traits<T> must be defined)
+ // names - untyped
+ // value - any type
+ // T* - NULL-able argument (here T can be names)
+ // value* - NULL-able any type (never NULL itself, use value::null)
+ // optional<T> - optional argument (here T can be T*, names, value)
+ //
+ // Optional arguments must be last. In case of a failure the function is
+ // expected to issue diagnostics and throw failed. Note that the arguments
+ // are conceptually "moved" and can be reused by the implementation.
+ //
+ // A function can also optionally receive the current scope by having the
+ // first argument of the const scope* type. It may be NULL if the function
+ // is called out of any scope (e.g., command line).
+ //
+ // Note also that we don't pass the location to the function instead
+ // printing the info message pointing to the call site.
+ //
+ // A function can return value or anything that can be converted to value.
+ // In particular, if a function returns optional<T>, then the result will be
+ // either NULL or value of type T.
+ //
+ // Normally functions come in families that share a common qualification
+ // (e.g., string. or path.). The function_family class is a "registrar"
+ // that simplifies handling of function families. For example:
+ //
+ // function_family f ("string");
+ //
+ // // Register length() and string.length().
+ // //
+ // f["length"] = &string::size;
+ //
+ // // Register string.max_size().
+ // //
+ // f[".max_size"] = []() {return string ().max_size ();};
+ //
+ // For more examples/ideas, study the existing function families (reside
+ // in the functions-*.cxx files).
+ //
+ // Note that normally there will be a function overload that has all the
+ // parameters untyped with an implementation that falls back to one of the
+ // overloads that have all the parameters typed, possibly inferring the type
+ // from the argument value "syntax" (e.g., presence of a trailing slash for
+ // a directory path).
+ //
+ struct function_overload;
+
+ using function_impl = value (const scope*,
+ vector_view<value>,
+ const function_overload&);
+
+ struct LIBBUILD2_SYMEXPORT function_overload
+ {
+ const char* name; // Set to point to key by insert() below.
+ const char* alt_name; // Alternative name, NULL if none. This is the
+ // qualified name for unqualified or vice verse.
+
+ // Arguments.
+ //
+ // A function can have a number of optional arguments. Arguments can also
+ // be typed. A non-existent entry in arg_types means a value of any type.
+ // A NULL entry means an untyped value.
+ //
+ // If arg_max equals to arg_variadic, then the function takes an unlimited
+ // number of arguments. In this case the semantics of arg_min and
+ // arg_types is unchanged.
+ //
+ static const size_t arg_variadic = size_t (~0);
+
+ using types = vector_view<const optional<const value_type*>>;
+
+ const size_t arg_min;
+ const size_t arg_max;
+ const types arg_types;
+
+ // Function implementation.
+ //
+ function_impl* const impl;
+
+ // Auxiliary data storage. Note that it is assumed to be POD (no
+ // destructors, bitwise copy, etc).
+ //
+ std::aligned_storage<sizeof (void*) * 3>::type data;
+ static const size_t data_size = sizeof (decltype (data));
+
+ function_overload (const char* an,
+ size_t mi, size_t ma, types ts,
+ function_impl* im)
+ : alt_name (an),
+ arg_min (mi), arg_max (ma), arg_types (move (ts)),
+ impl (im) {}
+
+ template <typename D>
+ function_overload (const char* an,
+ size_t mi, size_t ma, types ts,
+ function_impl* im,
+ D d)
+ : function_overload (an, mi, ma, move (ts), im)
+ {
+ // std::is_pod appears to be broken in VC16 and also in GCC up to
+ // 5 (pointers to members).
+ //
+#if !((defined(_MSC_VER) && _MSC_VER < 2000) || \
+ (defined(__GNUC__) && !defined(__clang__) && __GNUC__ <= 5))
+ static_assert (std::is_pod<D>::value, "type is not POD");
+#endif
+ static_assert (sizeof (D) <= data_size, "insufficient space");
+ new (&data) D (move (d));
+ }
+ };
+
+ LIBBUILD2_SYMEXPORT ostream&
+ operator<< (ostream&, const function_overload&); // Print signature.
+
+ class LIBBUILD2_SYMEXPORT function_map
+ {
+ public:
+ using map_type = std::multimap<string, function_overload>;
+ using iterator = map_type::iterator;
+ using const_iterator = map_type::const_iterator;
+
+ iterator
+ insert (string name, function_overload);
+
+ void
+ erase (iterator i) {map_.erase (i);}
+
+ value
+ call (const scope* base,
+ const string& name,
+ vector_view<value> args,
+ const location& l) const
+ {
+ return call (base, name, args, l, true).first;
+ }
+
+ // As above but do not fail if no match was found (but still do if the
+ // match is ambiguous). Instead return an indication of whether the call
+ // was made. Used to issue custom diagnostics when calling internal
+ // functions.
+ //
+ pair<value, bool>
+ try_call (const scope* base,
+ const string& name,
+ vector_view<value> args,
+ const location& l) const
+ {
+ return call (base, name, args, l, false);
+ }
+
+ iterator
+ begin () {return map_.begin ();}
+
+ iterator
+ end () {return map_.end ();}
+
+ const_iterator
+ begin () const {return map_.begin ();}
+
+ const_iterator
+ end () const {return map_.end ();}
+
+ // Return true if the function with this name is already defined. If the
+ // name ends with '.', then instead check if any function with this prefix
+ // (which we call a family) is already defined.
+ //
+ bool
+ defined (const string&) const;
+
+ private:
+ pair<value, bool>
+ call (const scope*,
+ const string&,
+ vector_view<value>,
+ const location&,
+ bool fail) const;
+
+ map_type map_;
+ };
+
+ LIBBUILD2_SYMEXPORT extern function_map functions;
+
+ class LIBBUILD2_SYMEXPORT function_family
+ {
+ public:
+ // The call() function above catches invalid_argument and issues
+ // diagnostics by assuming it is related to function arguments and
+ // contains useful description.
+ //
+ // In order to catch additional exceptions, you can implement a custom
+ // thunk which would normally call this default implementation.
+ //
+ static value
+ default_thunk (const scope*, vector_view<value>, const function_overload&);
+
+ // A function family uses a common qualification (though you can pass
+ // empty string to supress it). For an unqualified name (doesn't not
+ // contain a dot) the qualified version is added automatically. A name
+ // containing a leading dot is a shortcut notation for a qualified-only
+ // name.
+ //
+ explicit
+ function_family (string qual, function_impl* thunk = &default_thunk)
+ : qual_ (qual), thunk_ (thunk) {}
+
+ struct entry;
+
+ entry
+ operator[] (string name) const;
+
+ static bool
+ defined (string qual)
+ {
+ qual += '.';
+ return functions.defined (qual);
+ }
+
+ private:
+ const string qual_;
+ function_impl* thunk_;
+ };
+
+ // Implementation details. If you can understand and explain all of this,
+ // then you are hired ;-)!
+ //
+
+ template <typename T>
+ struct function_arg
+ {
+ static const bool null = false;
+ static const bool opt = false;
+
+ static constexpr optional<const value_type*>
+ type () {return &value_traits<T>::value_type;}
+
+ static T&&
+ cast (value* v)
+ {
+ if (v->null)
+ throw invalid_argument ("null value");
+
+ // Use fast but unchecked cast since the caller matched the types.
+ //
+ return move (v->as<T> ());
+ }
+ };
+
+ template <>
+ struct LIBBUILD2_SYMEXPORT function_arg<names> // Untyped.
+ {
+ static const bool null = false;
+ static const bool opt = false;
+
+ static constexpr optional<const value_type*>
+ type () {return nullptr;}
+
+ static names&&
+ cast (value* v)
+ {
+ if (v->null)
+ throw invalid_argument ("null value");
+
+ return move (v->as<names> ());
+ }
+ };
+
+ template <>
+ struct LIBBUILD2_SYMEXPORT function_arg<value> // Anytyped.
+ {
+ static const bool null = false;
+ static const bool opt = false;
+
+ static constexpr optional<const value_type*>
+ type () {return nullopt;}
+
+ static value&&
+ cast (value* v)
+ {
+ if (v->null)
+ throw invalid_argument ("null value");
+
+ return move (*v);
+ }
+ };
+
+ template <typename T>
+ struct function_arg<T*>: function_arg<T>
+ {
+ static const bool null = true;
+
+ static T*
+ cast (value* v)
+ {
+ if (v->null)
+ return nullptr;
+
+ // This looks bizarre but makes sense. The cast() that we are calling
+ // returns an r-value reference to (what's inside) v. And it has to
+ // return an r-value reference to that the value is moved into by-value
+ // arguments.
+ //
+ T&& r (function_arg<T>::cast (v));
+ return &r;
+ }
+ };
+
+ template <>
+ struct LIBBUILD2_SYMEXPORT function_arg<value*>: function_arg<value>
+ {
+ static const bool null = true;
+
+ static value*
+ cast (value* v) {return v;} // NULL indicator in value::null.
+ };
+
+ template <typename T>
+ struct function_arg<optional<T>>: function_arg<T>
+ {
+ static const bool opt = true;
+
+ static optional<T>
+ cast (value* v)
+ {
+ return v != nullptr ? optional<T> (function_arg<T>::cast (v)) : nullopt;
+ }
+ };
+
+ // Number of optional arguments. Note that we currently don't check that
+ // they are all at the end.
+ //
+ template <typename A0, typename... A>
+ struct function_args_opt
+ {
+ static const size_t count = (function_arg<A0>::opt ? 1 : 0) +
+ function_args_opt<A...>::count;
+ };
+
+ template <typename A0>
+ struct function_args_opt<A0>
+ {
+ static const size_t count = (function_arg<A0>::opt ? 1 : 0);
+ };
+
+ // Argument counts/types.
+ //
+ template <typename... A>
+ struct function_args
+ {
+ static const size_t max = sizeof...(A);
+ static const size_t min = max - function_args_opt<A...>::count;
+
+ // VC15 doesn't realize that a pointer to static object (in our case it is
+ // &value_trair<T>::value_type) is constexpr.
+ //
+ // Note that during the library split we discovered that the constexpr
+ // variant causes compilation/linkage issues for both MinGW GCC and
+ // VC. Thus we now only use it for POSIX systems.
+ //
+ // #if !defined(_MSC_VER) || _MSC_VER > 1910
+ //
+#if !defined(_WIN32)
+ static constexpr const optional<const value_type*> types[max] = {
+ function_arg<A>::type ()...};
+#else
+ static const optional<const value_type*> types[max];
+#endif
+ };
+
+ template <typename... A>
+#if !defined(_WIN32)
+ constexpr const optional<const value_type*>
+ function_args<A...>::types[function_args<A...>::max];
+#else
+ const optional<const value_type*>
+ function_args<A...>::types[function_args<A...>::max] = {
+ function_arg<A>::type ()...};
+#endif
+
+ // Specialization for no arguments.
+ //
+ template <>
+ struct LIBBUILD2_SYMEXPORT function_args<>
+ {
+ static const size_t max = 0;
+ static const size_t min = 0;
+
+#if !defined(_WIN32)
+ static constexpr const optional<const value_type*>* types = nullptr;
+#else
+ static const optional<const value_type*>* const types;
+#endif
+ };
+
+ // Cast data/thunk.
+ //
+ template <typename R, typename... A>
+ struct function_cast
+ {
+ // A pointer to a standard layout struct is a pointer to its first data
+ // member, which in our case is the cast thunk.
+ //
+ struct data
+ {
+ value (*const thunk) (const scope*, vector_view<value>, const void*);
+ R (*const impl) (A...);
+ };
+
+ static value
+ thunk (const scope*, vector_view<value> args, const void* d)
+ {
+ return thunk (move (args),
+ static_cast<const data*> (d)->impl,
+ std::index_sequence_for<A...> ());
+ }
+
+ template <size_t... i>
+ static value
+ thunk (vector_view<value> args,
+ R (*impl) (A...),
+ std::index_sequence<i...>)
+ {
+ return value (
+ impl (
+ function_arg<A>::cast (
+ i < args.size () ? &args[i] : nullptr)...));
+ }
+ };
+
+ // Specialization for functions that expect the current scope as a first
+ // argument.
+ //
+ template <typename R, typename... A>
+ struct function_cast<R, const scope*, A...>
+ {
+ struct data
+ {
+ value (*const thunk) (const scope*, vector_view<value>, const void*);
+ R (*const impl) (const scope*, A...);
+ };
+
+ static value
+ thunk (const scope* base, vector_view<value> args, const void* d)
+ {
+ return thunk (base, move (args),
+ static_cast<const data*> (d)->impl,
+ std::index_sequence_for<A...> ());
+ }
+
+ template <size_t... i>
+ static value
+ thunk (const scope* base, vector_view<value> args,
+ R (*impl) (const scope*, A...),
+ std::index_sequence<i...>)
+ {
+ return value (
+ impl (base,
+ function_arg<A>::cast (
+ i < args.size () ? &args[i] : nullptr)...));
+ }
+ };
+
+ // Specialization for void return type. In this case we return NULL value.
+ //
+ template <typename... A>
+ struct function_cast<void, A...>
+ {
+ struct data
+ {
+ value (*const thunk) (const scope*, vector_view<value>, const void*);
+ void (*const impl) (A...);
+ };
+
+ static value
+ thunk (const scope*, vector_view<value> args, const void* d)
+ {
+ thunk (move (args),
+ static_cast<const data*> (d)->impl,
+ std::index_sequence_for<A...> ());
+ return value (nullptr);
+ }
+
+ template <size_t... i>
+ static void
+ thunk (vector_view<value> args,
+ void (*impl) (A...),
+ std::index_sequence<i...>)
+ {
+ impl (function_arg<A>::cast (i < args.size () ? &args[i] : nullptr)...);
+ }
+ };
+
+ template <typename... A>
+ struct function_cast<void, const scope*, A...>
+ {
+ struct data
+ {
+ value (*const thunk) (const scope*, vector_view<value>, const void*);
+ void (*const impl) (const scope*, A...);
+ };
+
+ static value
+ thunk (const scope* base, vector_view<value> args, const void* d)
+ {
+ thunk (base, move (args),
+ static_cast<const data*> (d)->impl,
+ std::index_sequence_for<A...> ());
+ return value (nullptr);
+ }
+
+ template <size_t... i>
+ static void
+ thunk (const scope* base, vector_view<value> args,
+ void (*impl) (const scope*, A...),
+ std::index_sequence<i...>)
+ {
+ impl (base,
+ function_arg<A>::cast (i < args.size () ? &args[i] : nullptr)...);
+ }
+ };
+
+ // Customization for coerced lambdas (see below).
+ //
+#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 6
+ template <typename L, typename R, typename... A>
+ struct function_cast_lamb
+ {
+ struct data
+ {
+ value (*const thunk) (const scope*, vector_view<value>, const void*);
+ R (L::*const impl) (A...) const;
+ };
+
+ static value
+ thunk (const scope*, vector_view<value> args, const void* d)
+ {
+ return thunk (move (args),
+ static_cast<const data*> (d)->impl,
+ std::index_sequence_for<A...> ());
+ }
+
+ template <size_t... i>
+ static value
+ thunk (vector_view<value> args,
+ R (L::*impl) (A...) const,
+ std::index_sequence<i...>)
+ {
+ const L* l (nullptr); // Undefined behavior.
+
+ return value (
+ (l->*impl) (
+ function_arg<A>::cast (
+ i < args.size () ? &args[i] : nullptr)...));
+ }
+ };
+
+ template <typename L, typename R, typename... A>
+ struct function_cast_lamb<L, R, const scope*, A...>
+ {
+ struct data
+ {
+ value (*const thunk) (const scope*, vector_view<value>, const void*);
+ R (L::*const impl) (const scope*, A...) const;
+ };
+
+ static value
+ thunk (const scope* base, vector_view<value> args, const void* d)
+ {
+ return thunk (base, move (args),
+ static_cast<const data*> (d)->impl,
+ std::index_sequence_for<A...> ());
+ }
+
+ template <size_t... i>
+ static value
+ thunk (const scope* base, vector_view<value> args,
+ R (L::*impl) (const scope*, A...) const,
+ std::index_sequence<i...>)
+ {
+ const L* l (nullptr); // Undefined behavior.
+
+ return value (
+ (l->*impl) (base,
+ function_arg<A>::cast (
+ i < args.size () ? &args[i] : nullptr)...));
+ }
+ };
+
+ template <typename L, typename... A>
+ struct function_cast_lamb<L, void, A...>
+ {
+ struct data
+ {
+ value (*const thunk) (const scope*, vector_view<value>, const void*);
+ void (L::*const impl) (A...) const;
+ };
+
+ static value
+ thunk (const scope*, vector_view<value> args, const void* d)
+ {
+ thunk (move (args),
+ static_cast<const data*> (d)->impl,
+ std::index_sequence_for<A...> ());
+ return value (nullptr);
+ }
+
+ template <size_t... i>
+ static void
+ thunk (vector_view<value> args,
+ void (L::*impl) (A...) const,
+ std::index_sequence<i...>)
+ {
+ const L* l (nullptr);
+ (l->*impl) (
+ function_arg<A>::cast (
+ i < args.size () ? &args[i] : nullptr)...);
+ }
+ };
+
+ template <typename L, typename... A>
+ struct function_cast_lamb<L, void, const scope*, A...>
+ {
+ struct data
+ {
+ value (*const thunk) (const scope*, vector_view<value>, const void*);
+ void (L::*const impl) (const scope*, A...) const;
+ };
+
+ static value
+ thunk (const scope* base, vector_view<value> args, const void* d)
+ {
+ thunk (base, move (args),
+ static_cast<const data*> (d)->impl,
+ std::index_sequence_for<A...> ());
+ return value (nullptr);
+ }
+
+ template <size_t... i>
+ static void
+ thunk (const scope* base, vector_view<value> args,
+ void (L::*impl) (const scope*, A...) const,
+ std::index_sequence<i...>)
+ {
+ const L* l (nullptr);
+ (l->*impl) (base,
+ function_arg<A>::cast (
+ i < args.size () ? &args[i] : nullptr)...);
+ }
+ };
+#endif
+
+ // Customization for member functions.
+ //
+ template <typename R, typename T>
+ struct function_cast_memf
+ {
+ struct data
+ {
+ value (*const thunk) (const scope*, vector_view<value>, const void*);
+ R (T::*const impl) () const;
+ };
+
+ static value
+ thunk (const scope*, vector_view<value> args, const void* d)
+ {
+ auto mf (static_cast<const data*> (d)->impl);
+ return value ((function_arg<T>::cast (&args[0]).*mf) ());
+ }
+ };
+
+ template <typename T>
+ struct function_cast_memf<void, T>
+ {
+ struct data
+ {
+ value (*const thunk) (const scope*, vector_view<value>, const void*);
+ void (T::*const impl) () const;
+ };
+
+ static value
+ thunk (const scope*, vector_view<value> args, const void* d)
+ {
+ auto mf (static_cast<const data*> (d)->impl);
+ (function_arg<T>::cast (args[0]).*mf) ();
+ return value (nullptr);
+ }
+ };
+
+ // Customization for data members.
+ //
+ template <typename R, typename T>
+ struct function_cast_memd
+ {
+ struct data
+ {
+ value (*const thunk) (const scope*, vector_view<value>, const void*);
+ R T::*const impl;
+ };
+
+ static value
+ thunk (const scope*, vector_view<value> args, const void* d)
+ {
+ auto dm (static_cast<const data*> (d)->impl);
+ return value (move (function_arg<T>::cast (&args[0]).*dm));
+ }
+ };
+
+ struct LIBBUILD2_SYMEXPORT function_family::entry
+ {
+ string name;
+ const string& qual;
+ function_impl* thunk;
+
+ template <typename R, typename... A>
+ void
+ operator= (R (*impl) (A...)) &&
+ {
+ using args = function_args<A...>;
+ using cast = function_cast<R, A...>;
+
+ insert (move (name),
+ function_overload (
+ nullptr,
+ args::min,
+ args::max,
+ function_overload::types (args::types, args::max),
+ thunk,
+ typename cast::data {&cast::thunk, impl}));
+ }
+
+ template <typename R, typename... A>
+ void
+ operator= (R (*impl) (const scope*, A...)) &&
+ {
+ using args = function_args<A...>;
+ using cast = function_cast<R, const scope*, A...>;
+
+ insert (move (name),
+ function_overload (
+ nullptr,
+ args::min,
+ args::max,
+ function_overload::types (args::types, args::max),
+ thunk,
+ typename cast::data {&cast::thunk, impl}));
+ }
+
+ // Support for assigning a (capture-less) lambda.
+ //
+ // GCC up until version 6 has a bug (#62052) that is triggered by calling
+ // a lambda that takes a by-value argument via its "decayed" function
+ // pointer. To work around this we are not going to decay it and instead
+ // will call its operator() on NULL pointer; yes, undefined behavior, but
+ // better than a guaranteed crash.
+ //
+#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 6
+ template <typename L>
+ void
+ operator= (const L&) &&
+ {
+ move (*this).coerce_lambda (&L::operator());
+ }
+
+ template <typename L, typename R, typename... A>
+ void
+ coerce_lambda (R (L::*op) (A...) const) &&
+ {
+ using args = function_args<A...>;
+ using cast = function_cast_lamb<L, R, A...>;
+
+ insert (move (name),
+ function_overload (
+ nullptr,
+ args::min,
+ args::max,
+ function_overload::types (args::types, args::max),
+ thunk,
+ typename cast::data {&cast::thunk, op}));
+ }
+
+ template <typename L, typename R, typename... A>
+ void
+ coerce_lambda (R (L::*op) (const scope*, A...) const) &&
+ {
+ using args = function_args<A...>;
+ using cast = function_cast_lamb<L, R, const scope*, A...>;
+
+ insert (move (name),
+ function_overload (
+ nullptr,
+ args::min,
+ args::max,
+ function_overload::types (args::types, args::max),
+ thunk,
+ typename cast::data {&cast::thunk, op}));
+ }
+#else
+ template <typename L>
+ void
+ operator= (const L& l) &&
+ {
+ move (*this).operator= (decay_lambda (&L::operator(), l));
+ }
+
+ template <typename L, typename R, typename... A>
+ static auto
+ decay_lambda (R (L::*) (A...) const, const L& l) -> R (*) (A...)
+ {
+ return static_cast<R (*) (A...)> (l);
+ }
+#endif
+
+ // Support for assigning a pointer to member function (e.g. an accessor).
+ //
+ // For now we don't support passing additional (to this) arguments though
+ // we could probably do that. The issues would be the argument passing
+ // semantics (e.g., what if it's const&) and the optional/default argument
+ // handling.
+ //
+ template <typename R, typename T>
+ void
+ operator= (R (T::*mf) () const) &&
+ {
+ using args = function_args<T>;
+ using cast = function_cast_memf<R, T>;
+
+ insert (move (name),
+ function_overload (
+ nullptr,
+ args::min,
+ args::max,
+ function_overload::types (args::types, args::max),
+ thunk,
+ typename cast::data {&cast::thunk, mf}));
+ }
+
+ // Support for assigning a pointer to data member.
+ //
+ template <typename R, typename T>
+ void
+ operator= (R T::*dm) &&
+ {
+ using args = function_args<T>;
+ using cast = function_cast_memd<R, T>;
+
+ insert (move (name),
+ function_overload (
+ nullptr,
+ args::min,
+ args::max,
+ function_overload::types (args::types, args::max),
+ thunk,
+ typename cast::data {&cast::thunk, dm}));
+ }
+
+ private:
+ void
+ insert (string, function_overload) const;
+ };
+
+ inline auto function_family::
+ operator[] (string name) const -> entry
+ {
+ return entry {move (name), qual_, thunk_};
+ }
+}
+
+#endif // LIBBUILD2_FUNCTION_HXX
diff --git a/libbuild2/function.test.cxx b/libbuild2/function.test.cxx
new file mode 100644
index 0000000..5e442a3
--- /dev/null
+++ b/libbuild2/function.test.cxx
@@ -0,0 +1,134 @@
+// file : libbuild2/function.test.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <iostream>
+
+#include <libbuild2/types.hxx>
+#include <libbuild2/utility.hxx>
+
+#include <libbuild2/parser.hxx>
+#include <libbuild2/context.hxx>
+#include <libbuild2/function.hxx>
+#include <libbuild2/variable.hxx>
+#include <libbuild2/diagnostics.hxx>
+
+using namespace std;
+
+namespace build2
+{
+ static const optional<const value_type*> arg_bool[1] =
+ {
+ &value_traits<bool>::value_type
+ };
+
+ static dir_path
+ scoped (const scope*, dir_path d)
+ {
+ return d;
+ }
+
+ static void
+ scoped_void (const scope*, dir_path)
+ {
+ }
+
+ int
+ main (int, char* argv[])
+ {
+ // Fake build system driver, default verbosity.
+ //
+ init_diag (1);
+ init (argv[0]);
+ reset (strings ()); // No command line variables.
+
+ function_family f ("dummy");
+
+ f["fail"] = []() {fail << "failed" << endf;};
+ f["fail_arg"] = [](names a) {return convert<uint64_t> (move (a[0]));};
+
+ f["nullable"] = [](names* a) {return a == nullptr;};
+ f["optional"] = [](optional<names> a) {return !a;};
+
+ f["dummy0"] = []() {return "abc";};
+ f["dummy1"] = [](string s) {return s;};
+ f["dummy2"] = [](uint64_t x, uint64_t y) {return x + y;};
+
+ f["ambig"] = [](names a, optional<string>) {return a;};
+ f["ambig"] = [](names a, optional<uint64_t>) {return a;};
+
+ f["reverse"] = [](names a) {return a;};
+
+ f["scoped"] = [](const scope*, names a) {return a;};
+ f["scoped_void"] = [](const scope*, names) {};
+ f["scoped"] = &scoped;
+ f["scoped_void"] = &scoped_void;
+
+ f[".qual"] = []() {return "abc";};
+
+ f[".length"] = &path::size; // Member function.
+ f[".type"] = &name::type; // Data member.
+
+ f[".abs"] = [](dir_path d) {return d.absolute ();};
+
+ // Variadic function with first required argument of type bool. Returns
+ // number of arguments passed.
+ //
+ functions.insert (
+ "variadic",
+ function_overload (
+ nullptr,
+ 1,
+ function_overload::arg_variadic,
+ function_overload::types (arg_bool, 1),
+ [] (const scope*, vector_view<value> args, const function_overload&)
+ {
+ return value (static_cast<uint64_t> (args.size ()));
+ }));
+
+ // Dump arguments.
+ //
+ functions.insert (
+ "dump",
+ function_overload (
+ nullptr,
+ 0,
+ function_overload::arg_variadic,
+ function_overload::types (),
+ [] (const scope*, vector_view<value> args, const function_overload&)
+ {
+ for (value& a: args)
+ {
+ if (a.null)
+ cout << "[null]";
+ else if (!a.empty ())
+ {
+ names storage;
+ cout << reverse (a, storage);
+ }
+ cout << endl;
+ }
+ return value (nullptr);
+ }));
+
+ try
+ {
+ scope& s (*scope::global_);
+
+ parser p;
+ p.parse_buildfile (cin, path ("buildfile"), s, s);
+ }
+ catch (const failed&)
+ {
+ return 1;
+ }
+
+ return 0;
+ }
+}
+
+int
+main (int argc, char* argv[])
+{
+ return build2::main (argc, argv);
+}
diff --git a/libbuild2/functions-builtin.cxx b/libbuild2/functions-builtin.cxx
new file mode 100644
index 0000000..44ae534
--- /dev/null
+++ b/libbuild2/functions-builtin.cxx
@@ -0,0 +1,56 @@
+// file : libbuild2/functions-builtin.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <libbuild2/function.hxx>
+#include <libbuild2/variable.hxx>
+
+namespace build2
+{
+ // Return NULL value if an environment variable is not set, untyped value
+ // otherwise.
+ //
+ static inline value
+ getenvvar (const string& name)
+ {
+ optional<string> v (getenv (name));
+
+ if (!v)
+ return value ();
+
+ names r;
+ r.emplace_back (to_name (move (*v)));
+ return value (move (r));
+ }
+
+ void
+ builtin_functions ()
+ {
+ function_family f ("builtin");
+
+ f["type"] = [](value* v) {return v->type != nullptr ? v->type->name : "";};
+
+ f["null"] = [](value* v) {return v->null;};
+ f["empty"] = [](value* v) {return v->null || v->empty ();};
+
+ f["identity"] = [](value* v) {return move (*v);};
+
+ // string
+ //
+ f["string"] = [](bool b) {return b ? "true" : "false";};
+ f["string"] = [](uint64_t i) {return to_string (i);};
+ f["string"] = [](name n) {return to_string (n);};
+
+ // getenv
+ //
+ f["getenv"] = [](string name)
+ {
+ return getenvvar (name);
+ };
+
+ f["getenv"] = [](names name)
+ {
+ return getenvvar (convert<string> (move (name)));
+ };
+ }
+}
diff --git a/libbuild2/functions-filesystem.cxx b/libbuild2/functions-filesystem.cxx
new file mode 100644
index 0000000..d98c75d
--- /dev/null
+++ b/libbuild2/functions-filesystem.cxx
@@ -0,0 +1,220 @@
+// file : libbuild2/functions-filesystem.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <libbutl/filesystem.mxx>
+
+#include <libbuild2/function.hxx>
+#include <libbuild2/variable.hxx>
+
+using namespace std;
+
+namespace build2
+{
+ // Return paths of filesystem entries that match the pattern. See
+ // path_search() overloads (below) for details.
+ //
+ static names
+ path_search (const path& pattern, const optional<dir_path>& start)
+ {
+ names r;
+ auto add = [&r] (path&& p, const std::string&, bool interm) -> bool
+ {
+ // Canonicalizing paths seems to be the right thing to do. Otherwise, we
+ // can end up with different separators in the same path on Windows.
+ //
+ if (!interm)
+ r.emplace_back (
+ value_traits<path>::reverse (move (p.canonicalize ())));
+
+ return true;
+ };
+
+ // Print paths "as is" in the diagnostics.
+ //
+ try
+ {
+ if (pattern.absolute ())
+ path_search (pattern, add);
+ else
+ {
+ // An absolute start directory must be specified for the relative
+ // pattern.
+ //
+ if (!start || start->relative ())
+ {
+ diag_record dr (fail);
+
+ if (!start)
+ dr << "start directory is not specified";
+ else
+ dr << "start directory '" << start->representation ()
+ << "' is relative";
+
+ dr << info << "pattern '" << pattern.representation ()
+ << "' is relative";
+ }
+
+ path_search (pattern, add, *start);
+ }
+ }
+ catch (const system_error& e)
+ {
+ diag_record d (fail);
+ d << "unable to scan";
+
+ // If the pattern is absolute, then the start directory is not used, and
+ // so printing it would be misleading.
+ //
+ if (start && pattern.relative ())
+ d << " '" << start->representation () << "'";
+
+ d << ": " << e
+ << info << "pattern: '" << pattern.representation () << "'";
+ }
+
+ return r;
+ }
+
+ using butl::path_match;
+
+ // Return true if a path for a filesystem entry matches the pattern. See
+ // path_match() overloads (below) for details.
+ //
+ static bool
+ path_match (const path& pattern,
+ const path& entry,
+ const optional<dir_path>& start)
+ {
+ // If pattern and entry are both either absolute or relative and
+ // non-empty, and the first pattern component is not a self-matching
+ // wildcard, then ignore the start directory.
+ //
+ bool rel (pattern.relative () == entry.relative () &&
+ !pattern.empty () && !entry.empty ());
+
+ bool self (!pattern.empty () &&
+ (*pattern.begin ()).find ("***") != string::npos);
+
+ if (rel && !self)
+ return path_match (pattern, entry);
+
+ // The start directory must be specified and be absolute.
+ //
+ if (!start || start->relative ())
+ {
+ diag_record dr (fail);
+
+ // Print paths "as is".
+ //
+ if (!start)
+ dr << "start directory is not specified";
+ else
+ dr << "start directory path '" << start->representation ()
+ << "' is relative";
+
+ dr << info << "pattern: '" << pattern.representation () << "'"
+ << info << "entry: '" << entry.representation () << "'";
+ }
+
+ return path_match (pattern, entry, *start);
+ }
+
+ void
+ filesystem_functions ()
+ {
+ function_family f ("filesystem");
+
+ // path_search
+ //
+ // Return filesystem paths that match the pattern. If the pattern is an
+ // absolute path, then the start directory is ignored (if present).
+ // Otherwise, the start directory must be specified and be absolute.
+ //
+ f["path_search"] = [](path pattern, optional<dir_path> start)
+ {
+ return path_search (pattern, start);
+ };
+
+ f["path_search"] = [](path pattern, names start)
+ {
+ return path_search (pattern, convert<dir_path> (move (start)));
+ };
+
+ f["path_search"] = [](names pattern, optional<dir_path> start)
+ {
+ return path_search (convert<path> (move (pattern)), start);
+ };
+
+ f["path_search"] = [](names pattern, names start)
+ {
+ return path_search (convert<path> (move (pattern)),
+ convert<dir_path> (move (start)));
+ };
+
+ // path_match
+ //
+ // Match a filesystem entry name against a name pattern (both are strings),
+ // or a filesystem entry path against a path pattern. For the latter case
+ // the start directory may also be required (see below). The semantics of
+ // the pattern and name/entry arguments is determined according to the
+ // following rules:
+ //
+ // - The arguments must be of the string or path types, or be untyped.
+ //
+ // - If one of the arguments is typed, then the other one must be of the
+ // same type or be untyped. In the later case, an untyped argument is
+ // converted to the type of the other argument.
+ //
+ // - If both arguments are untyped and the start directory is specified,
+ // then the arguments are converted to the path type.
+ //
+ // - If both arguments are untyped and the start directory is not
+ // specified, then, if one of the arguments is syntactically a path (the
+ // value contains a directory separator), convert them to the path type,
+ // otherwise to the string type (match as names).
+ //
+ // If pattern and entry paths are both either absolute or relative and
+ // non-empty, and the first pattern component is not a self-matching
+ // wildcard (doesn't contain ***), then the start directory is not
+ // required, and is ignored if specified. Otherwise, the start directory
+ // must be specified and be an absolute path.
+ //
+ // Name matching.
+ //
+ f["path_match"] = [](string pattern, string name)
+ {
+ return path_match (pattern, name);
+ };
+
+ // Path matching.
+ //
+ f["path_match"] = [](path pat, path ent, optional<dir_path> start)
+ {
+ return path_match (pat, ent, start);
+ };
+
+ // The semantics depends on the presence of the start directory or the
+ // first two argument syntactic representation.
+ //
+ f["path_match"] = [](names pat, names ent, optional<names> start)
+ {
+ auto path_arg = [] (const names& a) -> bool
+ {
+ return a.size () == 1 &&
+ (a[0].directory () ||
+ a[0].value.find_first_of (path::traits_type::directory_separators) !=
+ string::npos);
+ };
+
+ return start || path_arg (pat) || path_arg (ent)
+ ? path_match (convert<path> (move (pat)), // Match as paths.
+ convert<path> (move (ent)),
+ start
+ ? convert<dir_path> (move (*start))
+ : optional<dir_path> ())
+ : path_match (convert<string> (move (pat)), // Match as strings.
+ convert<string> (move (ent)));
+ };
+ }
+}
diff --git a/libbuild2/functions-name.cxx b/libbuild2/functions-name.cxx
new file mode 100644
index 0000000..a8e08b6
--- /dev/null
+++ b/libbuild2/functions-name.cxx
@@ -0,0 +1,109 @@
+// file : libbuild2/functions-name.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <libbuild2/scope.hxx>
+#include <libbuild2/function.hxx>
+#include <libbuild2/variable.hxx>
+
+using namespace std;
+
+namespace build2
+{
+ // Convert name to target'ish name (see below for the 'ish part). Return
+ // raw/unprocessed data in case this is an unknown target type (or called
+ // out of scope). See scope::find_target_type() for details.
+ //
+ static pair<name, optional<string>>
+ to_target (const scope* s, name&& n)
+ {
+ optional<string> e;
+
+ if (s != nullptr)
+ {
+ auto rp (s->find_target_type (n, location ()));
+
+ if (rp.first != nullptr)
+ n.type = rp.first->name;
+
+ e = move (rp.second);
+ }
+
+ return make_pair (move (n), move (e));
+ }
+
+ void
+ name_functions ()
+ {
+ function_family f ("name");
+
+ // These functions treat a name as a target/prerequisite name.
+ //
+ // While on one hand it feels like calling them target.name(), etc., would
+ // have been more appropriate, on the other hand they can also be called
+ // on prerequisite names. They also won't always return the same result as
+ // if we were interrogating an actual target (e.g., the directory may be
+ // relative).
+ //
+ f["name"] = [](const scope* s, name n)
+ {
+ return to_target (s, move (n)).first.value;
+ };
+ f["name"] = [](const scope* s, names ns)
+ {
+ return to_target (s, convert<name> (move (ns))).first.value;
+ };
+
+ // Note: returns NULL if extension is unspecified (default) and empty if
+ // specified as no extension.
+ //
+ f["extension"] = [](const scope* s, name n)
+ {
+ return to_target (s, move (n)).second;
+ };
+ f["extension"] = [](const scope* s, names ns)
+ {
+ return to_target (s, convert<name> (move (ns))).second;
+ };
+
+ f["directory"] = [](const scope* s, name n)
+ {
+ return to_target (s, move (n)).first.dir;
+ };
+ f["directory"] = [](const scope* s, names ns)
+ {
+ return to_target (s, convert<name> (move (ns))).first.dir;
+ };
+
+ f["target_type"] = [](const scope* s, name n)
+ {
+ return to_target (s, move (n)).first.type;
+ };
+ f["target_type"] = [](const scope* s, names ns)
+ {
+ return to_target (s, convert<name> (move (ns))).first.type;
+ };
+
+ // Note: returns NULL if no project specified.
+ //
+ f["project"] = [](const scope* s, name n)
+ {
+ return to_target (s, move (n)).first.proj;
+ };
+ f["project"] = [](const scope* s, names ns)
+ {
+ return to_target (s, convert<name> (move (ns))).first.proj;
+ };
+
+ // Name-specific overloads from builtins.
+ //
+ function_family b ("builtin");
+
+ b[".concat"] = [](dir_path d, name n)
+ {
+ d /= n.dir;
+ n.dir = move (d);
+ return n;
+ };
+ }
+}
diff --git a/libbuild2/functions-path.cxx b/libbuild2/functions-path.cxx
new file mode 100644
index 0000000..6e39812
--- /dev/null
+++ b/libbuild2/functions-path.cxx
@@ -0,0 +1,361 @@
+// file : libbuild2/functions-path.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <libbuild2/function.hxx>
+#include <libbuild2/variable.hxx>
+
+using namespace std;
+
+namespace build2
+{
+ static value
+ path_thunk (const scope* base,
+ vector_view<value> args,
+ const function_overload& f)
+ try
+ {
+ return function_family::default_thunk (base, move (args), f);
+ }
+ catch (const invalid_path& e)
+ {
+ fail << "invalid path: '" << e.path << "'" << endf;
+ }
+
+ static value
+ concat_path_string (path l, string sr)
+ {
+ if (path::traits_type::is_separator (sr[0])) // '\0' if empty.
+ {
+ sr.erase (0, 1);
+ path pr (move (sr));
+ pr.canonicalize (); // Convert to canonical directory separators.
+
+ // If RHS is syntactically a directory (ends with a trailing slash),
+ // then return it as dir_path, not path.
+ //
+ if (pr.to_directory () || pr.empty ())
+ return value (
+ path_cast<dir_path> (move (l)) /= path_cast<dir_path> (move (pr)));
+ else
+ l /= pr;
+ }
+ else
+ l += sr;
+
+ return value (move (l));
+ }
+
+ static value
+ concat_dir_path_string (dir_path l, string sr)
+ {
+ if (path::traits_type::is_separator (sr[0])) // '\0' if empty.
+ sr.erase (0, 1);
+
+ path pr (move (sr));
+ pr.canonicalize (); // Convert to canonical directory separators.
+
+ // If RHS is syntactically a directory (ends with a trailing slash), then
+ // return it as dir_path, not path.
+ //
+ return pr.to_directory () || pr.empty ()
+ ? value (move (l /= path_cast<dir_path> (move (pr))))
+ : value (path_cast<path> (move (l)) /= pr);
+ }
+
+ // Return untyped value or NULL value if extension is not present.
+ //
+ static inline value
+ extension (path p)
+ {
+ const char* e (p.extension_cstring ());
+
+ if (e == nullptr)
+ return value ();
+
+ names r;
+ r.emplace_back (e);
+ return value (move (r));
+ }
+
+ template <typename P>
+ static inline P
+ leaf (const P& p, const optional<dir_path>& d)
+ {
+ if (!d)
+ return p.leaf ();
+
+ try
+ {
+ return p.leaf (*d);
+ }
+ catch (const invalid_path&)
+ {
+ fail << "'" << *d << "' is not a prefix of '" << p << "'" << endf;
+ }
+ }
+
+ void
+ path_functions ()
+ {
+ function_family f ("path", &path_thunk);
+
+ // string
+ //
+ f["string"] = [](path p) {return move (p).string ();};
+
+ f["string"] = [](paths v)
+ {
+ strings r;
+ for (auto& p: v)
+ r.push_back (move (p).string ());
+ return r;
+ };
+
+ f["string"] = [](dir_paths v)
+ {
+ strings r;
+ for (auto& p: v)
+ r.push_back (move (p).string ());
+ return r;
+ };
+
+ // representation
+ //
+ f["representation"] = [](path p) {return move (p).representation ();};
+
+ f["representation"] = [](paths v)
+ {
+ strings r;
+ for (auto& p: v)
+ r.push_back (move (p).representation ());
+ return r;
+ };
+
+ f["representation"] = [](dir_paths v)
+ {
+ strings r;
+ for (auto& p: v)
+ r.push_back (move (p).representation ());
+ return r;
+ };
+
+ // canonicalize
+ //
+ f["canonicalize"] = [](path p) {p.canonicalize (); return p;};
+ f["canonicalize"] = [](dir_path p) {p.canonicalize (); return p;};
+
+ f["canonicalize"] = [](paths v)
+ {
+ for (auto& p: v)
+ p.canonicalize ();
+ return v;
+ };
+
+ f["canonicalize"] = [](dir_paths v)
+ {
+ for (auto& p: v)
+ p.canonicalize ();
+ return v;
+ };
+
+ f[".canonicalize"] = [](names ns)
+ {
+ // For each path decide based on the presence of a trailing slash
+ // whether it is a directory. Return as untyped list of (potentially
+ // mixed) paths.
+ //
+ for (name& n: ns)
+ {
+ if (n.directory ())
+ n.dir.canonicalize ();
+ else
+ n.value = convert<path> (move (n)).canonicalize ().string ();
+ }
+ return ns;
+ };
+
+ // normalize
+ //
+ f["normalize"] = [](path p, optional<value> a)
+ {
+ p.normalize (a && convert<bool> (move (*a)));
+ return p;
+ };
+
+ f["normalize"] = [](dir_path p, optional<value> a)
+ {
+ p.normalize (a && convert<bool> (move (*a)));
+ return p;
+ };
+
+ f["normalize"] = [](paths v, optional<value> a)
+ {
+ bool act (a && convert<bool> (move (*a)));
+
+ for (auto& p: v)
+ p.normalize (act);
+
+ return v;
+ };
+ f["normalize"] = [](dir_paths v, optional<value> a)
+ {
+ bool act (a && convert<bool> (move (*a)));
+
+ for (auto& p: v)
+ p.normalize (act);
+ return v;
+ };
+
+ f[".normalize"] = [](names ns, optional<value> a)
+ {
+ bool act (a && convert<bool> (move (*a)));
+
+ // For each path decide based on the presence of a trailing slash
+ // whether it is a directory. Return as untyped list of (potentially
+ // mixed) paths.
+ //
+ for (name& n: ns)
+ {
+ if (n.directory ())
+ n.dir.normalize (act);
+ else
+ n.value = convert<path> (move (n)).normalize (act).string ();
+ }
+ return ns;
+ };
+
+ // directory
+ //
+ f["directory"] = &path::directory;
+
+ f["directory"] = [](paths v)
+ {
+ dir_paths r;
+ for (const path& p: v)
+ r.push_back (p.directory ());
+ return r;
+ };
+
+ f["directory"] = [](dir_paths v)
+ {
+ for (dir_path& p: v)
+ p = p.directory ();
+ return v;
+ };
+
+ f[".directory"] = [](names ns)
+ {
+ // For each path decide based on the presence of a trailing slash
+ // whether it is a directory. Return as list of directory names.
+ //
+ for (name& n: ns)
+ {
+ if (n.directory ())
+ n.dir = n.dir.directory ();
+ else
+ n = convert<path> (move (n)).directory ();
+ }
+ return ns;
+ };
+
+ // base
+ //
+ f["base"] = &path::base;
+
+ f["base"] = [](paths v)
+ {
+ for (path& p: v)
+ p = p.base ();
+ return v;
+ };
+
+ f["base"] = [](dir_paths v)
+ {
+ for (dir_path& p: v)
+ p = p.base ();
+ return v;
+ };
+
+ f[".base"] = [](names ns)
+ {
+ // For each path decide based on the presence of a trailing slash
+ // whether it is a directory. Return as untyped list of (potentially
+ // mixed) paths.
+ //
+ for (name& n: ns)
+ {
+ if (n.directory ())
+ n.dir = n.dir.base ();
+ else
+ n.value = convert<path> (move (n)).base ().string ();
+ }
+ return ns;
+ };
+
+ // leaf
+ //
+ f["leaf"] = &path::leaf;
+
+ f["leaf"] = [](path p, dir_path d)
+ {
+ return leaf (p, move (d));
+ };
+
+ f["leaf"] = [](paths v, optional<dir_path> d)
+ {
+ for (path& p: v)
+ p = leaf (p, d);
+ return v;
+ };
+
+ f["leaf"] = [](dir_paths v, optional<dir_path> d)
+ {
+ for (dir_path& p: v)
+ p = leaf (p, d);
+ return v;
+ };
+
+ f[".leaf"] = [](names ns, optional<dir_path> d)
+ {
+ // For each path decide based on the presence of a trailing slash
+ // whether it is a directory. Return as untyped list of (potentially
+ // mixed) paths.
+ //
+ for (name& n: ns)
+ {
+ if (n.directory ())
+ n.dir = leaf (n.dir, d);
+ else
+ n.value = leaf (convert<path> (move (n)), d).string ();
+ }
+ return ns;
+ };
+
+ // extension
+ //
+ f["extension"] = &extension;
+
+ f[".extension"] = [](names ns)
+ {
+ return extension (convert<path> (move (ns)));
+ };
+
+ // Path-specific overloads from builtins.
+ //
+ function_family b ("builtin", &path_thunk);
+
+ b[".concat"] = &concat_path_string;
+ b[".concat"] = &concat_dir_path_string;
+
+ b[".concat"] = [](path l, names ur)
+ {
+ return concat_path_string (move (l), convert<string> (move (ur)));
+ };
+
+ b[".concat"] = [](dir_path l, names ur)
+ {
+ return concat_dir_path_string (move (l), convert<string> (move (ur)));
+ };
+ }
+}
diff --git a/libbuild2/functions-process-path.cxx b/libbuild2/functions-process-path.cxx
new file mode 100644
index 0000000..65e426b
--- /dev/null
+++ b/libbuild2/functions-process-path.cxx
@@ -0,0 +1,25 @@
+// file : libbuild2/functions-process-path.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <libbuild2/function.hxx>
+#include <libbuild2/variable.hxx>
+
+using namespace std;
+
+namespace build2
+{
+ void
+ process_path_functions ()
+ {
+ function_family f ("process_path");
+
+ // As discussed in value_traits<process_path>, we always have recall.
+ //
+ f["recall"] = &process_path::recall;
+ f["effect"] = [](process_path p)
+ {
+ return move (p.effect.empty () ? p.recall : p.effect);
+ };
+ }
+}
diff --git a/libbuild2/functions-process.cxx b/libbuild2/functions-process.cxx
new file mode 100644
index 0000000..83188d3
--- /dev/null
+++ b/libbuild2/functions-process.cxx
@@ -0,0 +1,253 @@
+// file : libbuild2/functions-process.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <libbutl/regex.mxx>
+
+#include <libbuild2/function.hxx>
+#include <libbuild2/variable.hxx>
+
+using namespace std;
+using namespace butl;
+
+namespace build2
+{
+ // Ideas for potential further improvements:
+ //
+ // - Use scope to query environment.
+ // - Mode to ignore error/suppress diagnostics and return NULL?
+ // - Similar regex flags to regex.* functions (icase, etc)?
+
+ // Process arguments.
+ //
+ static pair<process_path, strings>
+ process_args (names&& args, const char* fn)
+ {
+ if (args.empty () || args[0].empty ())
+ fail << "executable name expected in process." << fn << "()";
+
+ process_path pp;
+ try
+ {
+ size_t erase;
+
+ // This can be a process_path (pair) or just a path.
+ //
+ if (args[0].pair)
+ {
+ pp = convert<process_path> (move (args[0]), move (args[1]));
+ erase = 2;
+ }
+ else
+ {
+ pp = run_search (convert<path> (move (args[0])));
+ erase = 1;
+ }
+
+ args.erase (args.begin (), args.begin () + erase);
+ }
+ catch (const invalid_argument& e)
+ {
+ fail << "invalid process." << fn << "() executable path: " << e.what ();
+ }
+
+ strings sargs;
+ try
+ {
+ sargs = convert<strings> (move (args));
+ }
+ catch (const invalid_argument& e)
+ {
+ fail << "invalid process." << fn << "() argument: " << e.what ();
+ }
+
+ return pair<process_path, strings> (move (pp), move (sargs));
+ }
+
+ static process
+ start (const scope*,
+ const process_path& pp,
+ const strings& args,
+ cstrings& cargs)
+ {
+ cargs.reserve (args.size () + 2);
+ cargs.push_back (pp.recall_string ());
+ transform (args.begin (),
+ args.end (),
+ back_inserter (cargs),
+ [] (const string& s) {return s.c_str ();});
+ cargs.push_back (nullptr);
+
+ return run_start (3 /* verbosity */,
+ pp,
+ cargs.data (),
+ 0 /* stdin */,
+ -1 /* stdout */);
+ }
+
+ static void
+ finish (cstrings& args, process& pr, bool io)
+ {
+ run_finish (args, pr);
+
+ if (io)
+ fail << "error reading " << args[0] << " output";
+ }
+
+ static value
+ run (const scope* s, const process_path& pp, const strings& args)
+ {
+ cstrings cargs;
+ process pr (start (s, pp, args, cargs));
+
+ string v;
+ bool io (false);
+ try
+ {
+ ifdstream is (move (pr.in_ofd));
+
+ // Note that getline() will fail if there is no output.
+ //
+ if (is.peek () != ifdstream::traits_type::eof ())
+ getline (is, v, '\0');
+
+ is.close (); // Detect errors.
+ }
+ catch (const io_error&)
+ {
+ // Presumably the child process failed and issued diagnostics so let
+ // finish() try to deal with that first.
+ //
+ io = true;
+ }
+
+ finish (cargs, pr, io);
+
+ names r;
+ r.push_back (to_name (move (trim (v))));
+ return value (move (r));
+ }
+
+ regex
+ parse_regex (const string&, regex::flag_type); // functions-regex.cxx
+
+ static value
+ run_regex (const scope* s,
+ const process_path& pp,
+ const strings& args,
+ const string& pat,
+ const optional<string>& fmt)
+ {
+ regex re (parse_regex (pat, regex::ECMAScript));
+
+ cstrings cargs;
+ process pr (start (s, pp, args, cargs));
+
+ names r;
+ bool io (false);
+ try
+ {
+ ifdstream is (move (pr.in_ofd), ifdstream::badbit);
+
+ for (string l; !eof (getline (is, l)); )
+ {
+ if (fmt)
+ {
+ pair<string, bool> p (regex_replace_match (l, re, *fmt));
+
+ if (p.second)
+ r.push_back (to_name (move (p.first)));
+ }
+ else
+ {
+ if (regex_match (l, re))
+ r.push_back (to_name (move (l)));
+ }
+ }
+
+ is.close (); // Detect errors.
+ }
+ catch (const io_error&)
+ {
+ // Presumably the child process failed and issued diagnostics so let
+ // finish() try to deal with that first.
+ //
+ io = true;
+ }
+
+ finish (cargs, pr, io);
+
+ return value (move (r));
+ }
+
+ static inline value
+ run_regex (const scope* s,
+ names&& args,
+ const string& pat,
+ const optional<string>& fmt)
+ {
+ pair<process_path, strings> pa (process_args (move (args), "run_regex"));
+ return run_regex (s, pa.first, pa.second, pat, fmt);
+ }
+
+ void
+ process_functions ()
+ {
+ function_family f ("process");
+
+ // $process.run(<prog>[ <args>...])
+ //
+ // Return trimmed stdout.
+ //
+ f[".run"] = [](const scope* s, names args)
+ {
+ pair<process_path, strings> pa (process_args (move (args), "run"));
+ return run (s, pa.first, pa.second);
+ };
+
+ f["run"] = [](const scope* s, process_path pp)
+ {
+ return run (s, pp, strings ());
+ };
+
+ // $process.run_regex(<prog>[ <args>...], <pat> [, <fmt>])
+ //
+ // Return stdout lines matched and optionally processed with regex.
+ //
+ // Each line of stdout (including the customary trailing blank) is matched
+ // (as a whole) against <pat> and, if successful, returned, optionally
+ // processed with <fmt>, as an element of a list.
+ //
+ f[".run_regex"] = [](const scope* s, names a, string p, optional<string> f)
+ {
+ return run_regex (s, move (a), p, f);
+ };
+
+ f[".run_regex"] = [] (const scope* s, names a, names p, optional<names> f)
+ {
+ return run_regex (s,
+ move (a),
+ convert<string> (move (p)),
+ f ? convert<string> (move (*f)) : nullopt_string);
+ };
+
+ f["run_regex"] = [](const scope* s,
+ process_path pp,
+ string p,
+ optional<string> f)
+ {
+ return run_regex (s, pp, strings (), p, f);
+ };
+
+ f["run_regex"] = [](const scope* s,
+ process_path pp,
+ names p,
+ optional<names> f)
+ {
+ return run_regex (s,
+ pp, strings (),
+ convert<string> (move (p)),
+ f ? convert<string> (move (*f)) : nullopt_string);
+ };
+ }
+}
diff --git a/libbuild2/functions-project-name.cxx b/libbuild2/functions-project-name.cxx
new file mode 100644
index 0000000..163e865
--- /dev/null
+++ b/libbuild2/functions-project-name.cxx
@@ -0,0 +1,63 @@
+// file : libbuild2/functions-project-name.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <libbuild2/function.hxx>
+#include <libbuild2/variable.hxx>
+
+using namespace std;
+
+namespace build2
+{
+ void
+ project_name_functions ()
+ {
+ function_family f ("project_name");
+
+ f["string"] = [](project_name p) {return move (p).string ();};
+
+ f["base"] = [](project_name p, optional<string> ext)
+ {
+ return ext ? p.base (ext->c_str ()) : p.base ();
+ };
+
+ f["base"] = [](project_name p, names ext)
+ {
+ return p.base (convert<string> (move (ext)).c_str ());
+ };
+
+ f["extension"] = &project_name::extension;
+ f["variable"] = &project_name::variable;
+
+ // Project name-specific overloads from builtins.
+ //
+ function_family b ("builtin");
+
+ b[".concat"] = [](project_name n, string s)
+ {
+ string r (move (n).string ());
+ r += s;
+ return r;
+ };
+
+ b[".concat"] = [](string s, project_name n)
+ {
+ s += n.string ();
+ return s;
+ };
+
+ b[".concat"] = [](project_name n, names ns)
+ {
+ string r (move (n).string ());
+ r += convert<string> (move (ns));
+ return r;
+ };
+
+ b[".concat"] = [](names ns, project_name n)
+ {
+ string r (convert<string> (move (ns)));
+ r += n.string ();
+ return r;
+ };
+ }
+}
diff --git a/libbuild2/functions-regex.cxx b/libbuild2/functions-regex.cxx
new file mode 100644
index 0000000..2c478fe
--- /dev/null
+++ b/libbuild2/functions-regex.cxx
@@ -0,0 +1,542 @@
+// file : libbuild2/functions-regex.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <sstream>
+
+#include <libbutl/regex.mxx>
+
+#include <libbuild2/function.hxx>
+#include <libbuild2/variable.hxx>
+
+using namespace std;
+using namespace butl;
+
+namespace build2
+{
+ // Convert value of an arbitrary type to string.
+ //
+ static inline string
+ to_string (value&& v)
+ {
+ // Optimize for the string value type.
+ //
+ if (v.type != &value_traits<string>::value_type)
+ untypify (v);
+
+ return convert<string> (move (v));
+ }
+
+ // Parse a regular expression. Throw invalid_argument if it is not valid.
+ //
+ // Note: also used in functions-process.cxx (thus not static).
+ //
+ regex
+ parse_regex (const string& s, regex::flag_type f)
+ {
+ try
+ {
+ return regex (s, f);
+ }
+ catch (const regex_error& e)
+ {
+ // Print regex_error description if meaningful (no space).
+ //
+ ostringstream os;
+ os << "invalid regex '" << s << "'" << e;
+ throw invalid_argument (os.str ());
+ }
+ }
+
+ // Match value of an arbitrary type against the regular expression. See
+ // match() overloads (below) for details.
+ //
+ static value
+ match (value&& v, const string& re, optional<names>&& flags)
+ {
+ // Parse flags.
+ //
+ regex::flag_type rf (regex::ECMAScript);
+ bool subs (false);
+
+ if (flags)
+ {
+ for (auto& f: *flags)
+ {
+ string s (convert<string> (move (f)));
+
+ if (s == "icase")
+ rf |= regex::icase;
+ else if (s == "return_subs")
+ subs = true;
+ else
+ throw invalid_argument ("invalid flag '" + s + "'");
+ }
+ }
+
+ // Parse regex.
+ //
+ regex rge (parse_regex (re, rf));
+
+ // Match.
+ //
+ string s (to_string (move (v)));
+
+ if (!subs)
+ return value (regex_match (s, rge)); // Return boolean value.
+
+ names r;
+ match_results<string::const_iterator> m;
+
+ if (regex_match (s, m, rge))
+ {
+ assert (!m.empty ());
+
+ for (size_t i (1); i != m.size (); ++i)
+ {
+ if (m[i].matched)
+ r.emplace_back (m.str (i));
+ }
+ }
+
+ return value (move (r));
+ }
+
+ // Determine if there is a match between the regular expression and some
+ // part of a value of an arbitrary type. See search() overloads (below)
+ // for details.
+ //
+ static value
+ search (value&& v, const string& re, optional<names>&& flags)
+ {
+ // Parse flags.
+ //
+ regex::flag_type rf (regex::ECMAScript);
+ bool match (false);
+ bool subs (false);
+
+ if (flags)
+ {
+ for (auto& f: *flags)
+ {
+ string s (convert<string> (move (f)));
+
+ if (s == "icase")
+ rf |= regex::icase;
+ else if (s == "return_match")
+ match = true;
+ else if (s == "return_subs")
+ subs = true;
+ else
+ throw invalid_argument ("invalid flag '" + s + "'");
+ }
+ }
+
+ // Parse regex.
+ //
+ regex rge (parse_regex (re, rf));
+
+ // Search.
+ //
+ string s (to_string (move (v)));
+
+ if (!match && !subs)
+ return value (regex_search (s, rge)); // Return boolean value.
+
+ names r;
+ match_results<string::const_iterator> m;
+
+ if (regex_search (s, m, rge))
+ {
+ assert (!m.empty ());
+
+ if (match)
+ {
+ assert (m[0].matched);
+ r.emplace_back (m.str (0));
+ }
+
+ if (subs)
+ {
+ for (size_t i (1); i != m.size (); ++i)
+ {
+ if (m[i].matched)
+ r.emplace_back (m.str (i));
+ }
+ }
+ }
+
+ return value (move (r));
+ }
+
+ static pair<regex::flag_type, regex_constants::match_flag_type>
+ parse_replacement_flags (optional<names>&& flags, bool first_only = true)
+ {
+ regex::flag_type rf (regex::ECMAScript);
+ regex_constants::match_flag_type mf (regex_constants::match_default);
+
+ if (flags)
+ {
+ for (auto& f: *flags)
+ {
+ string s (convert<string> (move (f)));
+
+ if (s == "icase")
+ rf |= regex::icase;
+ else if (first_only && s == "format_first_only")
+ mf |= regex_constants::format_first_only;
+ else if (s == "format_no_copy")
+ mf |= regex_constants::format_no_copy;
+ else
+ throw invalid_argument ("invalid flag '" + s + "'");
+ }
+ }
+
+ return make_pair (rf, mf);
+ }
+
+ // Replace matched parts in a value of an arbitrary type, using the format
+ // string. See replace() overloads (below) for details.
+ //
+ static names
+ replace (value&& v,
+ const string& re,
+ const string& fmt,
+ optional<names>&& flags)
+ {
+ auto fl (parse_replacement_flags (move (flags)));
+ regex rge (parse_regex (re, fl.first));
+
+ names r;
+
+ try
+ {
+ r.emplace_back (regex_replace_search (to_string (move (v)),
+ rge,
+ fmt,
+ fl.second).first);
+ }
+ catch (const regex_error& e)
+ {
+ fail << "unable to replace" << e;
+ }
+
+ return r;
+ }
+
+ // Split a value of an arbitrary type into a list of unmatched value parts
+ // and replacements of the matched parts. See split() overloads (below) for
+ // details.
+ //
+ static names
+ split (value&& v,
+ const string& re,
+ const string& fmt,
+ optional<names>&& flags)
+ {
+ auto fl (parse_replacement_flags (move (flags), false));
+ regex rge (parse_regex (re, fl.first));
+
+ names r;
+
+ try
+ {
+ regex_replace_search (to_string (move (v)), rge, fmt,
+ [&r] (string::const_iterator b,
+ string::const_iterator e)
+ {
+ if (b != e)
+ r.emplace_back (string (b, e));
+ },
+ fl.second);
+ }
+ catch (const regex_error& e)
+ {
+ fail << "unable to split" << e;
+ }
+
+ return r;
+ }
+
+ // Replace matched parts of list elements using the format string. See
+ // apply() overloads (below) for details.
+ //
+ static names
+ apply (names&& s,
+ const string& re,
+ const string& fmt,
+ optional<names>&& flags)
+ {
+ auto fl (parse_replacement_flags (move (flags)));
+ regex rge (parse_regex (re, fl.first));
+
+ names r;
+
+ try
+ {
+ for (auto& v: s)
+ {
+ string s (regex_replace_search (convert<string> (move (v)),
+ rge,
+ fmt,
+ fl.second).first);
+
+ if (!s.empty ())
+ r.emplace_back (move (s));
+ }
+ }
+ catch (const regex_error& e)
+ {
+ fail << "unable to apply" << e;
+ }
+
+ return r;
+ }
+
+ // Replace matched parts of list elements using the format string and
+ // concatenate the transformed elements. See merge() overloads (below) for
+ // details.
+ //
+ static names
+ merge (names&& s,
+ const string& re,
+ const string& fmt,
+ optional<string>&& delim,
+ optional<names>&& flags)
+ {
+ auto fl (parse_replacement_flags (move (flags)));
+ regex rge (parse_regex (re, fl.first));
+
+ string rs;
+
+ try
+ {
+ for (auto& v: s)
+ {
+ string s (regex_replace_search (convert<string> (move (v)),
+ rge,
+ fmt,
+ fl.second).first);
+
+ if (!s.empty ())
+ {
+ if (!rs.empty () && delim)
+ rs.append (*delim);
+
+ rs.append (s);
+ }
+
+ }
+ }
+ catch (const regex_error& e)
+ {
+ fail << "unable to merge" << e;
+ }
+
+ names r;
+ r.emplace_back (move (rs));
+ return r;
+ }
+
+ void
+ regex_functions ()
+ {
+ function_family f ("regex");
+
+ // $regex.match(<val>, <pat> [, <flags>])
+ //
+ // Match a value of an arbitrary type against the regular expression.
+ // Convert the value to string prior to matching. Return the boolean value
+ // unless return_subs flag is specified (see below), in which case return
+ // names (empty if no match).
+ //
+ // The following flags are supported:
+ //
+ // icase - match ignoring case
+ //
+ // return_subs - return names (rather than boolean), that contain
+ // sub-strings that match the marked sub-expressions
+ //
+ f[".match"] = [](value s, string re, optional<names> flags)
+ {
+ return match (move (s), re, move (flags));
+ };
+
+ f[".match"] = [](value s, names re, optional<names> flags)
+ {
+ return match (move (s), convert<string> (move (re)), move (flags));
+ };
+
+ // $regex.search(<val>, <pat> [, <flags>])
+ //
+ // Determine if there is a match between the regular expression and some
+ // part of a value of an arbitrary type. Convert the value to string prior
+ // to searching. Return the boolean value unless return_match or
+ // return_subs flag is specified (see below) in which case return names
+ // (empty if no match).
+ //
+ // The following flags are supported:
+ //
+ // icase - match ignoring case
+ //
+ // return_match - return names (rather than boolean), that contain a
+ // sub-string that matches the whole regular expression
+ //
+ // return_subs - return names (rather than boolean), that contain
+ // sub-strings that match the marked sub-expressions
+ //
+ // If both return_match and return_subs flags are specified then the
+ // sub-string that matches the whole regular expression comes first.
+ //
+ f[".search"] = [](value s, string re, optional<names> flags)
+ {
+ return search (move (s), re, move (flags));
+ };
+
+ f[".search"] = [](value s, names re, optional<names> flags)
+ {
+ return search (move (s), convert<string> (move (re)), move (flags));
+ };
+
+ // $regex.replace(<val>, <pat>, <fmt> [, <flags>])
+ //
+ // Replace matched parts in a value of an arbitrary type, using the format
+ // string. Convert the value to string prior to matching. The result value
+ // is always untyped, regardless of the argument type.
+ //
+ // Substitution escape sequences are extended with a subset of Perl
+ // sequences (see libbutl/regex.mxx for details).
+ //
+ // The following flags are supported:
+ //
+ // icase - match ignoring case
+ //
+ // format_first_only - only replace the first match
+ //
+ // format_no_copy - do not copy unmatched value parts into the result
+ //
+ // If both format_first_only and format_no_copy flags are specified then
+ // the result will only contain the replacement of the first match.
+ //
+ f[".replace"] = [](value s, string re, string fmt, optional<names> flags)
+ {
+ return replace (move (s), re, fmt, move (flags));
+ };
+
+ f[".replace"] = [](value s, names re, names fmt, optional<names> flags)
+ {
+ return replace (move (s),
+ convert<string> (move (re)),
+ convert<string> (move (fmt)),
+ move (flags));
+ };
+
+ // $regex.split(<val>, <pat>, <fmt> [, <flags>])
+ //
+ // Split a value of an arbitrary type into a list of unmatched value parts
+ // and replacements of the matched parts, omitting empty ones. Convert the
+ // value to string prior to matching.
+ //
+ // Substitution escape sequences are extended with a subset of Perl
+ // sequences (see libbutl/regex.mxx for details).
+ //
+ // The following flags are supported:
+ //
+ // icase - match ignoring case
+ //
+ // format_no_copy - do not copy unmatched value parts into the result
+ //
+ f[".split"] = [](value s, string re, string fmt, optional<names> flags)
+ {
+ return split (move (s), re, fmt, move (flags));
+ };
+
+ f[".split"] = [](value s, names re, names fmt, optional<names> flags)
+ {
+ return split (move (s),
+ convert<string> (move (re)),
+ convert<string> (move (fmt)),
+ move (flags));
+ };
+
+ // $regex.merge(<vals>, <pat>, <fmt> [, <delim> [, <flags>]])
+ //
+ // Replace matched parts in a list of elements using the regex format
+ // string. Convert the elements to string prior to matching. The result
+ // value is untyped and contains concatenation of transformed non-empty
+ // elements optionally separated with a delimiter.
+ //
+ // Substitution escape sequences are extended with a subset of Perl
+ // sequences (see libbutl/regex.mxx for details).
+ //
+ // The following flags are supported:
+ //
+ // icase - match ignoring case
+ //
+ // format_first_only - only replace the first match
+ //
+ // format_no_copy - do not copy unmatched value parts into the result
+ //
+ // If both format_first_only and format_no_copy flags are specified then
+ // the result will be a concatenation of only the first match
+ // replacements.
+ //
+ f[".merge"] = [](names s,
+ string re,
+ string fmt,
+ optional<string> delim,
+ optional<names> flags)
+ {
+ return merge (move (s), re, fmt, move (delim), move (flags));
+ };
+
+ f[".merge"] = [](names s,
+ names re,
+ names fmt,
+ optional<names> delim,
+ optional<names> flags)
+ {
+ return merge (move (s),
+ convert<string> (move (re)),
+ convert<string> (move (fmt)),
+ delim
+ ? convert<string> (move (*delim))
+ : optional<string> (),
+ move (flags));
+ };
+
+ // $regex.apply(<vals>, <pat>, <fmt> [, <flags>])
+ //
+ // Replace matched parts of each element in a list using the regex format
+ // string. Convert the elements to string prior to matching. Return a list
+ // of transformed elements, omitting the empty ones.
+ //
+ // Substitution escape sequences are extended with a subset of Perl
+ // sequences (see libbutl/regex.mxx for details).
+ //
+ // The following flags are supported:
+ //
+ // icase - match ignoring case
+ //
+ // format_first_only - only replace the first match
+ //
+ // format_no_copy - do not copy unmatched value parts into the result
+ //
+ // If both format_first_only and format_no_copy flags are specified then
+ // the result elements will only contain the replacement of the first
+ // match.
+ //
+ f[".apply"] = [](names s, string re, string fmt, optional<names> flags)
+ {
+ return apply (move (s), re, fmt, move (flags));
+ };
+
+ f[".apply"] = [](names s, names re, names fmt, optional<names> flags)
+ {
+ return apply (move (s),
+ convert<string> (move (re)),
+ convert<string> (move (fmt)),
+ move (flags));
+ };
+ }
+}
diff --git a/libbuild2/functions-string.cxx b/libbuild2/functions-string.cxx
new file mode 100644
index 0000000..22860cb
--- /dev/null
+++ b/libbuild2/functions-string.cxx
@@ -0,0 +1,43 @@
+// file : libbuild2/functions-string.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <libbuild2/function.hxx>
+#include <libbuild2/variable.hxx>
+
+using namespace std;
+
+namespace build2
+{
+ void
+ string_functions ()
+ {
+ function_family f ("string");
+
+ f["string"] = [](string s) {return s;};
+
+ // @@ Shouldn't it concatenate elements into the single string?
+ // @@ Doesn't seem to be used so far. Can consider removing.
+ //
+ // f["string"] = [](strings v) {return v;};
+
+ // String-specific overloads from builtins.
+ //
+ function_family b ("builtin");
+
+ b[".concat"] = [](string l, string r) {l += r; return l;};
+
+ b[".concat"] = [](string l, names ur)
+ {
+ l += convert<string> (move (ur));
+ return l;
+ };
+
+ b[".concat"] = [](names ul, string r)
+ {
+ string l (convert<string> (move (ul)));
+ l += r;
+ return l;
+ };
+ }
+}
diff --git a/libbuild2/functions-target-triplet.cxx b/libbuild2/functions-target-triplet.cxx
new file mode 100644
index 0000000..4394c5a
--- /dev/null
+++ b/libbuild2/functions-target-triplet.cxx
@@ -0,0 +1,36 @@
+// file : libbuild2/functions-target-triplet.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <libbuild2/function.hxx>
+#include <libbuild2/variable.hxx>
+
+using namespace std;
+
+namespace build2
+{
+ void
+ target_triplet_functions ()
+ {
+ function_family f ("target_triplet");
+
+ f["string"] = [](target_triplet t) {return t.string ();};
+
+ // Target triplet-specific overloads from builtins.
+ //
+ function_family b ("builtin");
+
+ b[".concat"] = [](target_triplet l, string sr) {return l.string () + sr;};
+ b[".concat"] = [](string sl, target_triplet r) {return sl + r.string ();};
+
+ b[".concat"] = [](target_triplet l, names ur)
+ {
+ return l.string () + convert<string> (move (ur));
+ };
+
+ b[".concat"] = [](names ul, target_triplet r)
+ {
+ return convert<string> (move (ul)) + r.string ();
+ };
+ }
+}
diff --git a/libbuild2/lexer+buildspec.test.testscript b/libbuild2/lexer+buildspec.test.testscript
new file mode 100644
index 0000000..a80b2d5
--- /dev/null
+++ b/libbuild2/lexer+buildspec.test.testscript
@@ -0,0 +1,16 @@
+# file : libbuild2/lexer+buildspec.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+test.arguments = buildspec
+
+: punctuation
+:
+$* <:'x,x(x)' >>EOO
+'x'
+,
+'x'
+ (
+'x'
+)
+EOO
diff --git a/libbuild2/lexer+comment.test.testscript b/libbuild2/lexer+comment.test.testscript
new file mode 100644
index 0000000..6ad1202
--- /dev/null
+++ b/libbuild2/lexer+comment.test.testscript
@@ -0,0 +1,139 @@
+# file : libbuild2/lexer+comment.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+: single-line
+:
+{
+ : only
+ :
+ $* <<EOI >>:EOO
+ # comment
+ EOI
+ EOO
+
+ : first
+ :
+ $* <<EOI >>EOO
+ # comment
+ foo
+ EOI
+ 'foo'
+ <newline>
+ EOO
+
+ : last
+ :
+ $* <<EOI >>EOO
+ foo
+ # comment
+ EOI
+ 'foo'
+ <newline>
+ EOO
+
+ : few
+ :
+ $* <<EOI >>EOO
+ foo
+ # comment
+ # comment
+ EOI
+ 'foo'
+ <newline>
+ EOO
+
+ : cont
+ :
+ $* <<EOI >>EOO
+ foo
+ # comment\\
+ bar
+ EOI
+ 'foo'
+ <newline>
+ 'bar'
+ <newline>
+ EOO
+
+ : same
+ :
+ $* <<EOI >>EOO
+ foo # comment
+ bar # comment
+ EOI
+ 'foo'
+ <newline>
+ 'bar'
+ <newline>
+ EOO
+}
+
+: multi-line
+:
+{
+ : only
+ :
+ $* <<EOI >>:EOO
+ #\
+ comment
+ comment
+ #\
+ EOI
+ EOO
+
+ : empty
+ :
+ $* <<EOI >>:EOO
+ #\
+ #\
+ EOI
+ EOO
+
+ : start-same
+ :
+ $* <<EOI >>EOO
+ foo #\
+ comment
+ comment
+ #\
+ EOI
+ 'foo'
+ <newline>
+ EOO
+
+ : end-same
+ :
+ $* <<EOI >>EOO
+ #\
+ comment
+ comment
+ foo #\
+ bar
+ EOI
+ 'bar'
+ <newline>
+ EOO
+
+ : end-not
+ :
+ $* <<EOI >>EOO
+ #\
+ comment
+ #\ not an end
+ foo #\
+ bar
+ EOI
+ 'bar'
+ <newline>
+ EOO
+
+ : unterm
+ :
+ $* <<EOI 2>>EOE != 0
+ #\
+ comment
+ EOI
+ stdin:3:1: error: unterminated multi-line comment
+ EOE
+}
diff --git a/libbuild2/lexer+eval.test.testscript b/libbuild2/lexer+eval.test.testscript
new file mode 100644
index 0000000..86f804a
--- /dev/null
+++ b/libbuild2/lexer+eval.test.testscript
@@ -0,0 +1,76 @@
+# file : libbuild2/lexer+eval.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+test.arguments = eval
+
+: punctuation
+:
+$* <:'x:x{x}x[x]x$x?x,x(x)' >>EOO
+'x'
+:
+'x'
+{
+'x'
+}
+'x'
+[
+'x'
+]
+'x'
+$
+'x'
+?
+'x'
+,
+'x'
+(
+'x'
+)
+EOO
+
+: logical
+:
+$* <:'x|x||x&x&&x!x!!x)' >>EOO
+'x|x'
+||
+'x&x'
+&&
+'x'
+!
+'x'
+!
+!
+'x'
+)
+EOO
+
+: comparison
+:
+$* <:'x=x==x!=x<x<=x>x>=)' >>EOO
+'x=x'
+==
+'x'
+!=
+'x'
+<
+'x'
+<=
+'x'
+>
+'x'
+>=
+)
+EOO
+
+: newline
+:
+$* <'x' >- 2>>EOE != 0
+stdin:1:2: error: newline in evaluation context
+EOE
+
+: eof
+:
+$* <:'' 2>>EOE != 0
+stdin:1:1: error: unterminated evaluation context
+EOE
diff --git a/libbuild2/lexer+quoting.test.testscript b/libbuild2/lexer+quoting.test.testscript
new file mode 100644
index 0000000..043737f
--- /dev/null
+++ b/libbuild2/lexer+quoting.test.testscript
@@ -0,0 +1,108 @@
+# file : libbuild2/lexer+quoting.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+test.options += -q
+
+: unquoted
+:
+$* <'foo' >>EOO
+'foo'
+<newline>
+EOO
+
+: comp
+:
+{
+ : single
+ :
+ $* <":'foo':" >>EOO
+ :
+ 'foo' [S/C]
+ :
+ <newline>
+ EOO
+
+ : double
+ :
+ $* <':"foo":' >>EOO
+ :
+ 'foo' [D/C]
+ :
+ <newline>
+ EOO
+
+ : single-empty
+ :
+ $* <"''" >>EOO
+ '' [S/C]
+ <newline>
+ EOO
+
+ : double-empty
+ :
+ $* <'""' >>EOO
+ '' [D/C]
+ <newline>
+ EOO
+}
+
+: part
+{
+ : quoted
+ {
+ : start
+ : Token start already quoted
+ :
+ $* <'"$foo"' >>EOO
+ '' [D/P]
+ $ [D/C]
+ 'foo' [D/P]
+ <newline>
+ EOO
+
+ : end
+ : Token end still quoted
+ :
+ $* <'"foo$"' >>EOO
+ 'foo' [D/P]
+ $ [D/C]
+ '' [D/P]
+ <newline>
+ EOO
+ }
+
+ : unquoted
+ {
+ : start
+ : Token starts with unquoted character
+ :
+ $* <'f"oo"' >>EOO
+ 'foo' [D/P]
+ <newline>
+ EOO
+
+ : end
+ : Token continous with unquoted character
+ :
+ $* <'"fo"o' >>EOO
+ 'foo' [D/P]
+ <newline>
+ EOO
+
+ : escape
+ : Token continous with unquoted escaped character
+ :
+ $* <'"fo"\"' >>EOO
+ 'fo"' [D/P]
+ <newline>
+ EOO
+ }
+}
+
+: mixed
+:
+$* <"\"fo\"'o'" >>EOO
+'foo' [M/P]
+<newline>
+EOO
diff --git a/libbuild2/lexer.cxx b/libbuild2/lexer.cxx
new file mode 100644
index 0000000..fd13c31
--- /dev/null
+++ b/libbuild2/lexer.cxx
@@ -0,0 +1,720 @@
+// file : libbuild2/lexer.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <libbuild2/lexer.hxx>
+
+#include <cstring> // strchr()
+
+using namespace std;
+
+namespace build2
+{
+ using type = token_type;
+
+ pair<char, bool> lexer::
+ peek_char ()
+ {
+ sep_ = skip_spaces ();
+ xchar c (peek ());
+ return make_pair (eos (c) ? '\0' : char (c), sep_);
+ }
+
+ void lexer::
+ mode (lexer_mode m, char ps, optional<const char*> esc)
+ {
+ const char* s1 (nullptr);
+ const char* s2 (nullptr);
+ bool s (true);
+ bool n (true);
+ bool q (true);
+
+ if (!esc)
+ {
+ assert (!state_.empty ());
+ esc = state_.top ().escapes;
+ }
+
+ switch (m)
+ {
+ case lexer_mode::normal:
+ {
+ s1 = ":<>=+ $(){}[]#\t\n";
+ s2 = " = ";
+ break;
+ }
+ case lexer_mode::value:
+ {
+ s1 = " $(){}[]#\t\n";
+ s2 = " ";
+ break;
+ }
+ case lexer_mode::attribute:
+ {
+ s1 = " $(]#\t\n";
+ s2 = " ";
+ break;
+ }
+ case lexer_mode::eval:
+ {
+ s1 = ":<>=!&|?, $(){}[]#\t\n";
+ s2 = " = &| ";
+ break;
+ }
+ case lexer_mode::buildspec:
+ {
+ // Like the value mode with these differences:
+ //
+ // 1. Returns '(' as a separated token provided the state stack depth
+ // is less than or equal to 3 (initial state plus two buildspec)
+ // (see parse_buildspec() for details).
+ //
+ // 2. Recognizes comma.
+ //
+ // 3. Treat newline as an ordinary space.
+ //
+ s1 = " $(){}[],\t\n";
+ s2 = " ";
+ n = false;
+ break;
+ }
+ case lexer_mode::single_quoted:
+ case lexer_mode::double_quoted:
+ s = false;
+ // Fall through.
+ case lexer_mode::variable:
+ {
+ // These are handled in an ad hoc way in word().
+ assert (ps == '\0');
+ break;
+ }
+ default: assert (false); // Unhandled custom mode.
+ }
+
+ state_.push (state {m, ps, s, n, q, *esc, s1, s2});
+ }
+
+ token lexer::
+ next ()
+ {
+ const state& st (state_.top ());
+ lexer_mode m (st.mode);
+
+ // For some modes we have dedicated imlementations of next().
+ //
+ switch (m)
+ {
+ case lexer_mode::normal:
+ case lexer_mode::value:
+ case lexer_mode::attribute:
+ case lexer_mode::variable:
+ case lexer_mode::buildspec: break;
+ case lexer_mode::eval: return next_eval ();
+ case lexer_mode::double_quoted: return next_quoted ();
+ default: assert (false); // Unhandled custom mode.
+ }
+
+ bool sep (skip_spaces ());
+
+ xchar c (get ());
+ uint64_t ln (c.line), cn (c.column);
+
+ auto make_token = [&sep, ln, cn] (type t, string v = string ())
+ {
+ return token (t, move (v),
+ sep, quote_type::unquoted, false,
+ ln, cn, token_printer);
+ };
+
+ if (eos (c))
+ return make_token (type::eos);
+
+ // Handle pair separator.
+ //
+ if (c == st.sep_pair)
+ return make_token (type::pair_separator, string (1, c));
+
+ switch (c)
+ {
+ // NOTE: remember to update mode(), next_eval() if adding new special
+ // characters.
+ //
+ case '\n':
+ {
+ // Expire value mode at the end of the line.
+ //
+ if (m == lexer_mode::value)
+ state_.pop ();
+
+ sep = true; // Treat newline as always separated.
+ return make_token (type::newline);
+ }
+ case '{': return make_token (type::lcbrace);
+ case '}': return make_token (type::rcbrace);
+ case '[': return make_token (type::lsbrace);
+ case ']':
+ {
+ // Expire attribute mode after closing ']'.
+ //
+ if (m == lexer_mode::attribute)
+ state_.pop ();
+
+ return make_token (type::rsbrace);
+ }
+ case '$': return make_token (type::dollar);
+ case ')': return make_token (type::rparen);
+ case '(':
+ {
+ // Left paren is always separated in the buildspec mode.
+ //
+ if (m == lexer_mode::buildspec && state_.size () <= 3)
+ sep = true;
+
+ return make_token (type::lparen);
+ }
+ }
+
+ // The following characters are special in the normal and variable modes.
+ //
+ if (m == lexer_mode::normal || m == lexer_mode::variable)
+ {
+ switch (c)
+ {
+ // NOTE: remember to update mode(), next_eval() if adding new special
+ // characters.
+ //
+ case ':': return make_token (type::colon);
+ case '=':
+ {
+ if (peek () == '+')
+ {
+ get ();
+ return make_token (type::prepend);
+ }
+ else
+ return make_token (type::assign);
+ }
+ case '+':
+ {
+ if (peek () == '=')
+ {
+ get ();
+ return make_token (type::append);
+ }
+ }
+ }
+ }
+
+ // The following characters are special in the normal mode.
+ //
+ if (m == lexer_mode::normal)
+ {
+ // NOTE: remember to update mode() if adding new special characters.
+ //
+ switch (c)
+ {
+ case '<': return make_token (type::labrace);
+ case '>': return make_token (type::rabrace);
+ }
+ }
+
+ // The following characters are special in the buildspec mode.
+ //
+ if (m == lexer_mode::buildspec)
+ {
+ // NOTE: remember to update mode() if adding new special characters.
+ //
+ switch (c)
+ {
+ case ',': return make_token (type::comma);
+ }
+ }
+
+ // Otherwise it is a word.
+ //
+ unget (c);
+ return word (st, sep);
+ }
+
+ token lexer::
+ next_eval ()
+ {
+ bool sep (skip_spaces ());
+ xchar c (get ());
+
+ if (eos (c))
+ fail (c) << "unterminated evaluation context";
+
+ const state& st (state_.top ());
+
+ uint64_t ln (c.line), cn (c.column);
+
+ auto make_token = [sep, ln, cn] (type t, string v = string ())
+ {
+ return token (t, move (v),
+ sep, quote_type::unquoted, false,
+ ln, cn, token_printer);
+ };
+
+ // This mode is quite a bit like the value mode when it comes to special
+ // characters, except that we have some of our own.
+ //
+
+ // Handle pair separator.
+ //
+ if (c == st.sep_pair)
+ return make_token (type::pair_separator, string (1, c));
+
+ // Note: we don't treat [ and ] as special here. Maybe can use them for
+ // something later.
+ //
+ switch (c)
+ {
+ // NOTE: remember to update mode() if adding new special characters.
+ //
+ case '\n': fail (c) << "newline in evaluation context" << endf;
+ case ':': return make_token (type::colon);
+ case '{': return make_token (type::lcbrace);
+ case '}': return make_token (type::rcbrace);
+ case '[': return make_token (type::lsbrace);
+ case ']': return make_token (type::rsbrace);
+ case '$': return make_token (type::dollar);
+ case '?': return make_token (type::question);
+ case ',': return make_token (type::comma);
+ case '(': return make_token (type::lparen);
+ case ')':
+ {
+ state_.pop (); // Expire eval mode.
+ return make_token (type::rparen);
+ }
+ // Potentially two-character tokens.
+ //
+ case '=':
+ case '!':
+ case '<':
+ case '>':
+ case '|':
+ case '&':
+ {
+ xchar p (peek ());
+
+ type r (type::eos);
+ switch (c)
+ {
+ case '|': if (p == '|') r = type::log_or; break;
+ case '&': if (p == '&') r = type::log_and; break;
+
+ case '<': r = (p == '=' ? type::less_equal : type::less); break;
+ case '>': r = (p == '=' ? type::greater_equal : type::greater); break;
+
+ case '=': if (p == '=') r = type::equal; break;
+
+ case '!': r = (p == '=' ? type::not_equal : type::log_not); break;
+ }
+
+ if (r == type::eos)
+ break;
+
+ switch (r)
+ {
+ case type::less:
+ case type::greater:
+ case type::log_not: break;
+ default: get ();
+ }
+
+ return make_token (r);
+ }
+ }
+
+ // Otherwise it is a word.
+ //
+ unget (c);
+ return word (st, 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);
+
+ auto make_token = [ln, cn] (type t)
+ {
+ return token (t, false, quote_type::double_, ln, cn, token_printer);
+ };
+
+ switch (c)
+ {
+ case '$': return make_token (type::dollar);
+ case '(': return make_token (type::lparen);
+ }
+
+ // Otherwise it is a word.
+ //
+ unget (c);
+ return word (state_.top (), false);
+ }
+
+ token lexer::
+ word (state st, bool sep)
+ {
+ lexer_mode m (st.mode);
+
+ xchar c (peek ());
+ assert (!eos (c));
+
+ uint64_t ln (c.line), cn (c.column);
+
+ string lexeme;
+ quote_type qtype (m == lexer_mode::double_quoted
+ ? quote_type::double_
+ : quote_type::unquoted);
+
+ // If we are already in the quoted mode then we didn't start with the
+ // quote character.
+ //
+ bool qcomp (false);
+
+ auto append = [&lexeme, &m, &qcomp] (char c)
+ {
+ lexeme += c;
+
+ // An unquoted character after a quoted fragment.
+ //
+ if (qcomp && m != lexer_mode::double_quoted)
+ qcomp = false;
+ };
+
+ for (; !eos (c); c = peek ())
+ {
+ // First handle escape sequences.
+ //
+ if (c == '\\')
+ {
+ // In the variable mode we treat the beginning of the escape sequence
+ // as a separator (think \"$foo\").
+ //
+ if (m == lexer_mode::variable)
+ break;
+
+ get ();
+ xchar p (peek ());
+
+ const char* esc (st.escapes);
+
+ if (esc == nullptr ||
+ (*esc != '\0' && !eos (p) && strchr (esc, p) != nullptr))
+ {
+ get ();
+
+ if (eos (p))
+ fail (p) << "unterminated escape sequence";
+
+ if (p != '\n') // Ignore if line continuation.
+ append (p);
+
+ continue;
+ }
+ else
+ unget (c); // Treat as a normal character.
+ }
+
+ bool done (false);
+
+ // Next take care of the double-quoted mode. This one is tricky since
+ // we push/pop modes while accumulating the same lexeme for example:
+ //
+ // foo" bar "baz
+ //
+ if (m == lexer_mode::double_quoted)
+ {
+ switch (c)
+ {
+ // Only these two characters are special in the double-quoted mode.
+ //
+ case '$':
+ case '(':
+ {
+ done = true;
+ break;
+ }
+ // End quote.
+ //
+ case '\"':
+ {
+ get ();
+ state_.pop ();
+
+ st = state_.top ();
+ m = st.mode;
+ continue;
+ }
+ }
+ }
+ // We also handle the variable mode in an ad hoc way.
+ //
+ else if (m == lexer_mode::variable)
+ {
+ if (c != '_' && !(lexeme.empty () ? alpha (c) : alnum (c)))
+ {
+ if (c != '.')
+ done = true;
+ else
+ {
+ // Normally '.' is part of the variable (namespace separator)
+ // unless it is trailing (think $major.$minor).
+ //
+ get ();
+ xchar p (peek ());
+ done = eos (p) || !(alpha (p) || p == '_');
+ unget (c);
+ }
+ }
+ }
+ else
+ {
+ // First check if it's a pair separator.
+ //
+ if (c == st.sep_pair)
+ done = true;
+ else
+ {
+ // Then see if this character or character sequence is a separator.
+ //
+ for (const char* p (strchr (st.sep_first, c));
+ p != nullptr;
+ p = done ? nullptr : strchr (p + 1, c))
+ {
+ char s (st.sep_second[p - st.sep_first]);
+
+ // See if it has a second.
+ //
+ if (s != ' ')
+ {
+ get ();
+ done = (peek () == s);
+ unget (c);
+ }
+ else
+ done = true;
+ }
+ }
+
+ // Handle single and double quotes if enabled for this mode and unless
+ // they were considered separators.
+ //
+ if (st.quotes && !done)
+ {
+ switch (c)
+ {
+ case '\'':
+ {
+ // Enter the single-quoted mode in case the derived lexer needs
+ // to notice this.
+ //
+ mode (lexer_mode::single_quoted);
+
+ switch (qtype)
+ {
+ case quote_type::unquoted:
+ qtype = quote_type::single;
+ qcomp = lexeme.empty ();
+ break;
+ case quote_type::single:
+ qcomp = false; // Non-contiguous.
+ break;
+ case quote_type::double_:
+ qtype = quote_type::mixed;
+ // Fall through.
+ case quote_type::mixed:
+ qcomp = false;
+ break;
+ }
+
+ get ();
+ for (c = get (); !eos (c) && c != '\''; c = get ())
+ lexeme += c;
+
+ if (eos (c))
+ fail (c) << "unterminated single-quoted sequence";
+
+ state_.pop ();
+ continue;
+ }
+ case '\"':
+ {
+ get ();
+
+ mode (lexer_mode::double_quoted);
+ st = state_.top ();
+ m = st.mode;
+
+ switch (qtype)
+ {
+ case quote_type::unquoted:
+ qtype = quote_type::double_;
+ qcomp = lexeme.empty ();
+ break;
+ case quote_type::double_:
+ qcomp = false; // Non-contiguous.
+ break;
+ case quote_type::single:
+ qtype = quote_type::mixed;
+ // Fall through.
+ case quote_type::mixed:
+ qcomp = false;
+ break;
+ }
+
+ continue;
+ }
+ }
+ }
+ }
+
+ if (done)
+ break;
+
+ get ();
+ append (c);
+ }
+
+ if (m == lexer_mode::double_quoted)
+ {
+ if (eos (c))
+ fail (c) << "unterminated double-quoted sequence";
+
+ // If we are still in the quoted mode then we didn't end with the quote
+ // character.
+ //
+ if (qcomp)
+ qcomp = false;
+ }
+
+ // Expire variable mode at the end of the word.
+ //
+ if (m == lexer_mode::variable)
+ state_.pop ();
+
+ return token (move (lexeme), sep, qtype, qcomp, ln, cn);
+ }
+
+ bool lexer::
+ skip_spaces ()
+ {
+ bool r (sep_);
+ sep_ = false;
+
+ const state& s (state_.top ());
+
+ // In some special modes we don't skip spaces.
+ //
+ if (!s.sep_space)
+ return r;
+
+ xchar c (peek ());
+ bool start (c.column == 1);
+
+ for (; !eos (c); c = peek ())
+ {
+ switch (c)
+ {
+ case ' ':
+ case '\t':
+ {
+ r = true;
+ break;
+ }
+ case '\n':
+ {
+ // In some modes we treat newlines as ordinary spaces.
+ //
+ if (!s.sep_newline)
+ {
+ r = true;
+ break;
+ }
+
+ // Skip empty lines.
+ //
+ if (start)
+ {
+ r = false;
+ break;
+ }
+
+ return r;
+ }
+ case '#':
+ {
+ r = true;
+ get ();
+
+ // See if this is a multi-line comment in the form:
+ //
+ /*
+ #\
+ ...
+ #\
+ */
+ auto ml = [&c, this] () -> bool
+ {
+ if ((c = peek ()) == '\\')
+ {
+ get ();
+ if ((c = peek ()) == '\n')
+ return true;
+ }
+
+ return false;
+ };
+
+ if (ml ())
+ {
+ // Scan until we see the closing one.
+ //
+ for (; !eos (c); c = peek ())
+ {
+ get ();
+ if (c == '#' && ml ())
+ break;
+ }
+
+ if (eos (c))
+ fail (c) << "unterminated multi-line comment";
+ }
+ else
+ {
+ // Read until newline or eos.
+ //
+ for (; !eos (c) && c != '\n'; c = peek ())
+ get ();
+ }
+
+ continue;
+ }
+ case '\\':
+ {
+ get ();
+
+ if (peek () == '\n')
+ break; // Ignore.
+
+ unget (c);
+ }
+ // Fall through.
+ default:
+ return r; // Not a space.
+ }
+
+ get ();
+ }
+
+ return r;
+ }
+}
diff --git a/libbuild2/lexer.hxx b/libbuild2/lexer.hxx
new file mode 100644
index 0000000..f987071
--- /dev/null
+++ b/libbuild2/lexer.hxx
@@ -0,0 +1,207 @@
+// file : libbuild2/lexer.hxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef LIBBUILD2_LEXER_HXX
+#define LIBBUILD2_LEXER_HXX
+
+#include <stack>
+
+#include <libbutl/char-scanner.mxx>
+
+#include <libbuild2/types.hxx>
+#include <libbuild2/utility.hxx>
+
+#include <libbuild2/token.hxx>
+#include <libbuild2/diagnostics.hxx>
+
+#include <libbuild2/export.hxx>
+
+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
+ // attribute mode is like value except it doesn't treat '{' and '}' as
+ // special (so we cannot have name groups in attributes). The eval mode is
+ // used in the evaluation context. Quoted modes are internal and should not
+ // be set explicitly.
+ //
+ // Note that the normal, value, and eval modes split words separated by the
+ // pair character (to disable pairs one can pass '\0' as a pair character).
+ //
+ // The alternnative modes must be set manually. The value mode automatically
+ // expires after the end of the line. The attribute mode expires after the
+ // closing ']'. The variable mode expires after the word token. And the eval
+ // mode expires after the closing ')'.
+ //
+ // Note that normally it is only safe to switch mode when the current token
+ // is not quoted (or, more generally, when you are not in the double-quoted
+ // mode) unless the mode treats the double-quote as a separator (e.g.,
+ // variable name mode). Failed that your mode (which now will be the top of
+ // the mode stack) will prevent proper recognition of the closing quote.
+ //
+
+ // Extendable/inheritable enum-like class.
+ //
+ struct lexer_mode: lexer_mode_base
+ {
+ using base_type = lexer_mode_base;
+
+ enum
+ {
+ normal = base_type::value_next,
+ variable,
+ value,
+ attribute,
+ eval,
+ single_quoted,
+ double_quoted,
+ buildspec,
+
+ value_next
+ };
+
+ lexer_mode () = default;
+ lexer_mode (value_type v): base_type (v) {}
+ lexer_mode (base_type v): base_type (v) {}
+ };
+
+ class LIBBUILD2_SYMEXPORT lexer: public butl::char_scanner
+ {
+ public:
+ // If escape is not NULL then only escape sequences with characters from
+ // this string are considered "effective escapes" with all others passed
+ // through as is. Note that the escape string is not copied.
+ //
+ lexer (istream& is,
+ const path& name,
+ uint64_t line = 1, // Start line in the stream.
+ const char* escapes = nullptr)
+ : lexer (is, name, line, escapes, true /* set_mode */) {}
+
+ const path&
+ name () const {return name_;}
+
+ // Note: sets mode for the next token. The second argument can be used to
+ // specifythe pair separator character (if the mode supports pairs). If
+ // escapes not specified, then inherit the current mode's (thought a mode
+ // can also override it).
+ //
+ virtual void
+ mode (lexer_mode,
+ char pair_separator = '\0',
+ optional<const char*> escapes = nullopt);
+
+ // Expire the current mode early.
+ //
+ void
+ expire_mode () {state_.pop ();}
+
+ lexer_mode
+ mode () const {return state_.top ().mode;}
+
+ char
+ pair_separator () const {return state_.top ().sep_pair;}
+
+ // Scanner. Note that it is ok to call next() again after getting eos.
+ //
+ // If you extend the lexer and add a custom lexer mode, then you must
+ // override next() and handle the custom mode there.
+ //
+ virtual 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 ();
+
+ protected:
+ struct state
+ {
+ lexer_mode mode;
+
+ char sep_pair;
+ bool sep_space; // Are whitespaces separators (see skip_spaces())?
+ bool sep_newline; // Is newline special (see skip_spaces())?
+ bool quotes; // Recognize quoted fragments.
+
+ const char* escapes; // Effective escape sequences to recognize.
+
+ // Word separator characters. For two-character sequence put the first
+ // one in sep_first and the second one in the corresponding position of
+ // sep_second. If it's a single-character sequence, then put space in
+ // sep_second. If there are multiple sequences that start with the same
+ // character, then repeat the first character in sep_first.
+ //
+ const char* sep_first;
+ const char* sep_second;
+ };
+
+ token
+ next_eval ();
+
+ token
+ next_quoted ();
+
+ // Lex a word assuming current is the top state (which may already have
+ // been "expired" from the top).
+ //
+ virtual token
+ word (state current, 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 ();
+
+ // Diagnostics.
+ //
+ protected:
+ fail_mark fail;
+
+ // Lexer state.
+ //
+ protected:
+ lexer (istream& is,
+ const path& name,
+ uint64_t line,
+ const char* escapes,
+ bool set_mode)
+ : char_scanner (is, true /* crlf */, line),
+ fail ("error", &name_),
+ name_ (name),
+ sep_ (false)
+ {
+ if (set_mode)
+ mode (lexer_mode::normal, '@', escapes);
+ }
+
+ const path name_;
+ std::stack<state> state_;
+
+ bool sep_; // True if we skipped spaces in peek().
+ };
+}
+
+// Diagnostics plumbing.
+//
+namespace butl // ADL
+{
+ inline build2::location
+ get_location (const butl::char_scanner::xchar& c, const void* data)
+ {
+ using namespace build2;
+
+ assert (data != nullptr); // E.g., must be &lexer::name_.
+ return location (static_cast<const path*> (data), c.line, c.column);
+ }
+}
+
+#endif // LIBBUILD2_LEXER_HXX
diff --git a/libbuild2/lexer.test.cxx b/libbuild2/lexer.test.cxx
new file mode 100644
index 0000000..84520d1
--- /dev/null
+++ b/libbuild2/lexer.test.cxx
@@ -0,0 +1,98 @@
+// file : libbuild2/lexer.test.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <cassert>
+#include <iostream>
+
+#include <libbuild2/types.hxx>
+#include <libbuild2/utility.hxx>
+
+#include <libbuild2/token.hxx>
+#include <libbuild2/lexer.hxx>
+
+using namespace std;
+
+namespace build2
+{
+ // Usage: argv[0] [-q] [<lexer-mode>]
+ //
+ int
+ main (int argc, char* argv[])
+ {
+ bool quote (false);
+ lexer_mode m (lexer_mode::normal);
+
+ for (int i (1); i != argc; ++i)
+ {
+ string a (argv[i]);
+
+ if (a == "-q")
+ quote = true;
+ else
+ {
+ if (a == "normal") m = lexer_mode::normal;
+ else if (a == "variable") m = lexer_mode::variable;
+ else if (a == "value") m = lexer_mode::value;
+ else if (a == "attribute") m = lexer_mode::attribute;
+ else if (a == "eval") m = lexer_mode::eval;
+ else if (a == "buildspec") m = lexer_mode::buildspec;
+ else assert (false);
+ break;
+ }
+ }
+
+ try
+ {
+ cin.exceptions (istream::failbit | istream::badbit);
+
+ // Most alternative modes auto-expire so we need something underneath.
+ //
+ lexer l (cin, path ("stdin"));
+
+ if (m != lexer_mode::normal)
+ l.mode (m);
+
+ // No use printing eos since we will either get it or loop forever.
+ //
+ for (token t (l.next ()); t.type != token_type::eos; t = l.next ())
+ {
+ if (t.separated && t.type != token_type::newline)
+ cout << ' ';
+
+ // Print each token on a separate line without quoting operators.
+ //
+ t.printer (cout, t, false);
+
+ if (quote)
+ {
+ char q ('\0');
+ switch (t.qtype)
+ {
+ case quote_type::single: q = 'S'; break;
+ case quote_type::double_: q = 'D'; break;
+ case quote_type::mixed: q = 'M'; break;
+ case quote_type::unquoted: break;
+ }
+
+ if (q != '\0')
+ cout << " [" << q << (t.qcomp ? "/C" : "/P") << ']';
+ }
+
+ cout << endl;
+ }
+ }
+ catch (const failed&)
+ {
+ return 1;
+ }
+
+ return 0;
+ }
+}
+
+int
+main (int argc, char* argv[])
+{
+ return build2::main (argc, argv);
+}
diff --git a/libbuild2/module.cxx b/libbuild2/module.cxx
new file mode 100644
index 0000000..50530f2
--- /dev/null
+++ b/libbuild2/module.cxx
@@ -0,0 +1,147 @@
+// file : libbuild2/module.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <libbuild2/module.hxx>
+
+#include <libbuild2/scope.hxx>
+#include <libbuild2/variable.hxx>
+#include <libbuild2/diagnostics.hxx>
+
+using namespace std;
+
+namespace build2
+{
+ available_module_map builtin_modules;
+
+ void
+ boot_module (scope& rs, const string& name, const location& loc)
+ {
+ // First see if this modules has already been loaded for this project.
+ //
+ loaded_module_map& lm (rs.root_extra->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, false, mf.init, nullptr, loc}).first;
+ i->second.first = mf.boot (rs, loc, i->second.module);
+
+ rs.assign (var_pool.rw (rs).insert (name + ".booted")) = true;
+ }
+
+ bool
+ load_module (scope& rs,
+ scope& bs,
+ const string& name,
+ const location& loc,
+ bool opt,
+ const variable_map& hints)
+ {
+ // First see if this modules has already been loaded for this project.
+ //
+ loaded_module_map& lm (rs.root_extra->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, 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.
+ }
+ }
+
+ // Note: pattern-typed in context.cxx:reset() as project-visibility
+ // variables of type bool.
+ //
+ auto& vp (var_pool.rw (rs));
+ value& lv (bs.assign (vp.insert (name + ".loaded")));
+ value& cv (bs.assign (vp.insert (name + ".configured")));
+
+ bool l; // Loaded.
+ bool c; // Configured.
+
+ // Suppress duplicate init() calls for the same module in the same scope.
+ //
+ if (!lv.null)
+ {
+ assert (!cv.null);
+
+ l = cast<bool> (lv);
+ c = cast<bool> (cv);
+
+ if (!opt)
+ {
+ if (!l)
+ fail (loc) << "unknown module " << name;
+
+ // We don't have original diagnostics. We could call init() again so
+ // that it can issue it. But that means optional modules must be
+ // prepared to be called again if configuring failed. Let's keep it
+ // simple for now.
+ //
+ if (!c)
+ fail (loc) << "module " << name << " failed to configure";
+ }
+ }
+ else
+ {
+ l = i != lm.end ();
+ c = l && i->second.init (rs, bs, loc, i->second.module, f, opt, hints);
+
+ lv = l;
+ cv = c;
+ }
+
+ return l && c;
+ }
+}
diff --git a/libbuild2/module.hxx b/libbuild2/module.hxx
new file mode 100644
index 0000000..5fbed9c
--- /dev/null
+++ b/libbuild2/module.hxx
@@ -0,0 +1,120 @@
+// file : libbuild2/module.hxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef LIBBUILD2_MODULE_HXX
+#define LIBBUILD2_MODULE_HXX
+
+#include <map>
+
+#include <libbuild2/types.hxx>
+#include <libbuild2/utility.hxx>
+
+#include <libbuild2/variable.hxx>
+#include <libbuild2/diagnostics.hxx>
+
+#include <libbuild2/export.hxx>
+
+namespace build2
+{
+ class scope;
+ class location;
+
+ class module_base
+ {
+ public:
+ virtual
+ ~module_base () = default;
+ };
+
+ // Return true if the module should be initialized first (the order of
+ // initialization within each group is unspecified).
+ //
+ using module_boot_function =
+ bool (scope& root,
+ const location&,
+ unique_ptr<module_base>&);
+
+ // 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.
+ //
+ using module_init_function =
+ bool (scope& root,
+ scope& base,
+ const location&,
+ unique_ptr<module_base>&,
+ bool first, // First time for this project.
+ bool optional, // Loaded with using? (optional module).
+ const variable_map& hints); // Configuration hints (see below).
+
+ struct module_functions
+ {
+ module_boot_function* boot;
+ module_init_function* init;
+ };
+
+ // The register() function will be written in C++ and will be called from
+ // C++ but we need to suppress name mangling to be able to use dlsym() and
+ // equivalent.
+ //
+ extern "C"
+ using module_register_function = module_functions ();
+
+ // Loaded modules state.
+ //
+ struct module_state
+ {
+ bool boot; // True if the module boot'ed but not yet init'ed.
+ bool first; // True if the boot'ed module must be init'ed first.
+ module_init_function* init;
+ unique_ptr<module_base> module;
+ const location loc; // Boot location.
+ };
+
+ struct loaded_module_map: std::map<string, module_state>
+ {
+ template <typename T>
+ T*
+ lookup (const string& name) const
+ {
+ auto i (find (name));
+ return i != end ()
+ ? static_cast<T*> (i->second.module.get ())
+ : nullptr;
+ }
+ };
+
+ // Load and boot the specified module.
+ //
+ LIBBUILD2_SYMEXPORT void
+ boot_module (scope& root, const string& name, 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).
+ //
+ // The config_hints variable map can be used to pass configuration hints
+ // from one module to another. For example, the cxx modude may pass the
+ // target platform (which was extracted from the C++ compiler) to the bin
+ // module (which may not always be able to extract the same information from
+ // its tools).
+ //
+ LIBBUILD2_SYMEXPORT bool
+ load_module (scope& root,
+ scope& base,
+ const string& name,
+ const location&,
+ bool optional = false,
+ const variable_map& config_hints = variable_map ());
+
+ // Builtin modules.
+ //
+ using available_module_map = std::map<string, module_functions>;
+ LIBBUILD2_SYMEXPORT extern available_module_map builtin_modules;
+}
+
+#endif // LIBBUILD2_MODULE_HXX
diff --git a/libbuild2/name.cxx b/libbuild2/name.cxx
new file mode 100644
index 0000000..4aac32f
--- /dev/null
+++ b/libbuild2/name.cxx
@@ -0,0 +1,187 @@
+// file : libbuild2/name.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <libbuild2/types.hxx> // Note: not <libbuild2/name.hxx>
+
+#include <string.h> // strchr()
+
+#include <libbuild2/diagnostics.hxx>
+
+namespace build2
+{
+ const name empty_name;
+ const names empty_names;
+
+ string
+ to_string (const name& n)
+ {
+ string r;
+
+ // Note: similar to to_stream() below.
+ //
+ if (n.empty ())
+ return r;
+
+ if (n.proj)
+ {
+ r += n.proj->string ();
+ r += '%';
+ }
+
+ // If the value is empty, then we want to put the last component of the
+ // directory inside {}, e.g., dir{bar/}, not bar/dir{}.
+ //
+ bool v (!n.value.empty ());
+ bool t (!n.type.empty ());
+
+ const dir_path& pd (v ? n.dir :
+ t ? n.dir.directory () :
+ dir_path ());
+
+ if (!pd.empty ())
+ r += pd.representation ();
+
+ if (t)
+ {
+ r += n.type;
+ r += '{';
+ }
+
+ if (v)
+ r += n.value;
+ else
+ r += (pd.empty () ? n.dir : n.dir.leaf ()).representation ();
+
+ if (t)
+ r += '}';
+
+ return r;
+ }
+
+ ostream&
+ to_stream (ostream& os, const name& n, bool quote, char pair)
+ {
+ auto write_string = [quote, pair, &os](const string& v)
+ {
+ char sc[] = {
+ '{', '}', '[', ']', '$', '(', ')', // Token endings.
+ ' ', '\t', '\n', '#', // Spaces.
+ '\\', '"', // Escaping and quoting.
+ '%', // Project name separator.
+ '*', '?', // Wildcard characters.
+ pair, // Pair separator, if any.
+ '\0'};
+
+ if (quote && v.find ('\'') != string::npos)
+ {
+ // Quote the string with the double quotes rather than with the single
+ // one. Escape some of the special characters.
+ //
+ os << '"';
+
+ for (auto c: v)
+ {
+ if (strchr ("\\$(\"", c) != nullptr) // Special inside double quotes.
+ os << '\\';
+
+ os << c;
+ }
+
+ os << '"';
+ }
+ else if (quote && v.find_first_of (sc) != string::npos)
+ os << "'" << v << "'";
+ else
+ os << v;
+ };
+
+ uint16_t dv (stream_verb (os).path); // Directory verbosity.
+
+ auto write_dir = [dv, quote, &os, &write_string] (const dir_path& d)
+ {
+ const string& s (dv < 1
+ ? diag_relative (d)
+ : d.representation ());
+ if (quote)
+ write_string (s);
+ else
+ os << s;
+ };
+
+ // Note: similar to to_string() below.
+ //
+
+ // If quoted then print empty name as '' rather than {}.
+ //
+ if (quote && n.empty ())
+ return os << "''";
+
+ if (n.proj)
+ {
+ write_string (n.proj->string ());
+ os << '%';
+ }
+
+ // If the value is empty, then we want to print the last component of the
+ // directory inside {}, e.g., dir{bar/}, not bar/dir{}. We also want to
+ // print {} for an empty name (unless quoted, which is handled above).
+ //
+ bool d (!n.dir.empty ());
+ bool v (!n.value.empty ());
+ bool t (!n.type.empty ());
+
+ // Note: relative() may return empty.
+ //
+ const dir_path& rd (dv < 1 ? relative (n.dir) : n.dir); // Relative.
+ const dir_path& pd (v ? rd :
+ t ? rd.directory () :
+ dir_path ());
+
+ if (!pd.empty ())
+ write_dir (pd);
+
+ if (t || (!d && !v))
+ {
+ if (t)
+ write_string (n.type);
+
+ os << '{';
+ }
+
+ if (v)
+ write_string (n.value);
+ else if (d)
+ {
+ if (rd.empty ())
+ write_string (dir_path (".").representation ());
+ else if (!pd.empty ())
+ write_string (rd.leaf ().representation ());
+ else
+ write_dir (rd);
+ }
+
+ if (t || (!d && !v))
+ os << '}';
+
+ return os;
+ }
+
+ ostream&
+ to_stream (ostream& os, const names_view& ns, bool quote, char pair)
+ {
+ for (auto i (ns.begin ()), e (ns.end ()); i != e; )
+ {
+ const name& n (*i);
+ ++i;
+ to_stream (os, n, quote, pair);
+
+ if (n.pair)
+ os << n.pair;
+ else if (i != e)
+ os << ' ';
+ }
+
+ return os;
+ }
+}
diff --git a/libbuild2/name.hxx b/libbuild2/name.hxx
new file mode 100644
index 0000000..1ce073a
--- /dev/null
+++ b/libbuild2/name.hxx
@@ -0,0 +1,172 @@
+// file : libbuild2/name.hxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+// Note: include <libbuild2/types.hxx> instead of this file directly.
+//
+
+#ifndef LIBBUILD2_NAME_HXX
+#define LIBBUILD2_NAME_HXX
+
+// We cannot include <libbuild2/utility.hxx> since it includes
+// <libbuild2/types.hxx>.
+//
+#include <utility> // move()
+
+#include <libbuild2/export.hxx>
+
+namespace build2
+{
+ using std::move;
+
+ // 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 qualified with a project. If the project name is
+ // empty, then it means the name is in a project other than our own (e.g.,
+ // it is installed).
+ //
+ // A type or project can only be specified if either directory or value are
+ // not empty.
+ //
+ // If pair is not '\0', then this name and the next in the list form a
+ // pair. Can be used as a bool flag.
+ //
+ struct name
+ {
+ optional<project_name> proj;
+ dir_path dir;
+ string type;
+ string value;
+ char pair = '\0';
+
+ name () {} // = default; Clang needs this to initialize const object.
+ name (string v): value (move (v)) {}
+ name (dir_path d): dir (move (d)) {}
+ name (string t, string v): type (move (t)), value (move (v)) {}
+ name (dir_path d, string v): dir (move (d)), value (move (v)) {}
+
+ name (dir_path d, string t, string v)
+ : dir (move (d)), type (move (t)), value (move (v)) {}
+
+ name (optional<project_name> p, dir_path d, string t, string v)
+ : proj (move (p)), dir (move (d)), type (move (t)), value (move (v)) {}
+
+ bool
+ qualified () const {return proj.has_value ();}
+
+ bool
+ unqualified () const {return !qualified ();}
+
+ bool
+ typed () const {return !type.empty ();}
+
+ bool
+ untyped () const {return type.empty ();}
+
+ // Note: if dir and value are empty then there should be no proj or type.
+ //
+ 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 (bool ignore_qual = false) const
+ {
+ return (ignore_qual || unqualified ()) && untyped () && dir.empty ();
+ }
+
+ bool
+ directory (bool ignore_qual = false) const
+ {
+ return (ignore_qual || unqualified ()) &&
+ untyped () && !dir.empty () && value.empty ();
+ }
+
+ int
+ compare (const name&) const;
+ };
+
+ LIBBUILD2_SYMEXPORT extern const name empty_name;
+
+ inline bool
+ operator== (const name& x, const name& y) {return x.compare (y) == 0;}
+
+ inline bool
+ operator!= (const name& x, const name& y) {return !(x == y);}
+
+ inline bool
+ operator< (const name& x, const name& y) {return x.compare (y) < 0;}
+
+ // Return string representation of a name.
+ //
+ LIBBUILD2_SYMEXPORT string
+ to_string (const name&);
+
+ // Store a string in a name in a reversible way. If the string ends with a
+ // trailing directory separator then it is stored as a directory, otherwise
+ // as a simple name.
+ //
+ name
+ to_name (string);
+
+ // Serialize the name to the stream. If requested, the name components
+ // containing special characters are quoted. The special characters are:
+ //
+ // {}[]$() \t\n#\"'%
+ //
+ // If the pair argument is not '\0', then it is added to the above special
+ // characters set. If the quote character is present in the component then
+ // it is double quoted rather than single quoted. In this case the following
+ // characters are escaped:
+ //
+ // \$("
+ //
+ // Note that in the quoted mode empty unqualified name is printed as '',
+ // not {}.
+ //
+ LIBBUILD2_SYMEXPORT ostream&
+ to_stream (ostream&, const name&, bool quote, char pair = '\0');
+
+ inline ostream&
+ operator<< (ostream& os, const name& n) {return to_stream (os, n, false);}
+
+ // Vector of names.
+ //
+ // Quite often it will contain just one element so we use small_vector<1>.
+ // Note also that it must be a separate type rather than an alias for
+ // vector<name> in order to distinguish between untyped variable values
+ // (names) and typed ones (vector<name>).
+ //
+ using names = small_vector<name, 1>;
+ using names_view = vector_view<const name>;
+
+ LIBBUILD2_SYMEXPORT extern const names empty_names;
+
+ // The same semantics as to_stream(name).
+ //
+ LIBBUILD2_SYMEXPORT ostream&
+ to_stream (ostream&, const names_view&, bool quote, char pair = '\0');
+
+ inline ostream&
+ operator<< (ostream& os, const names_view& ns) {
+ return to_stream (os, ns, false);}
+
+ inline ostream&
+ operator<< (ostream& os, const names& ns) {return os << names_view (ns);}
+
+ // Pair of names.
+ //
+ using name_pair = pair<name, name>;
+}
+
+#include <libbuild2/name.ixx>
+
+#endif // LIBBUILD2_NAME_HXX
diff --git a/libbuild2/name.ixx b/libbuild2/name.ixx
new file mode 100644
index 0000000..188126e
--- /dev/null
+++ b/libbuild2/name.ixx
@@ -0,0 +1,40 @@
+// file : libbuild2/name.ixx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+namespace build2
+{
+ inline int name::
+ compare (const name& x) const
+ {
+ int r (proj < x.proj ? -1 : (proj > x.proj ? 1 : 0));
+
+ if (r == 0)
+ r = dir.compare (x.dir);
+
+ if (r == 0)
+ r = type.compare (x.type);
+
+ if (r == 0)
+ r = value.compare (x.value);
+
+ if (r == 0)
+ r = pair < x.pair ? -1 : (pair > x.pair ? 1 : 0);
+
+ return r;
+ }
+
+ inline name
+ to_name (string s)
+ {
+ if (!s.empty () && path::traits_type::is_separator (s.back ()))
+ {
+ dir_path d (move (s), dir_path::exact);
+
+ if (!d.empty ())
+ return name (move (d));
+ }
+
+ return name (move (s));
+ }
+}
diff --git a/libbuild2/name.test.cxx b/libbuild2/name.test.cxx
new file mode 100644
index 0000000..09fb841
--- /dev/null
+++ b/libbuild2/name.test.cxx
@@ -0,0 +1,96 @@
+// file : libbuild2/name.test.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <sstream>
+
+#include <cassert>
+#include <iostream>
+
+#include <libbuild2/types.hxx> // Includes name.
+#include <libbuild2/utility.hxx>
+
+#include <libbuild2/diagnostics.hxx>
+
+using namespace std;
+
+namespace build2
+{
+ int
+ main (int, char*[])
+ {
+ using dir = dir_path;
+
+ // Test string representation.
+ //
+ {
+ auto ts = [] (const name& n) {return to_string (n);};
+
+ assert (ts (name ()) == "");
+
+ assert (ts (name ("foo")) == "foo");
+
+ assert (ts (name (dir ("bar/"))) == "bar/");
+ assert (ts (name (dir ("bar/baz/"))) == "bar/baz/");
+
+ assert (ts (name (dir ("bar/"), "dir", "")) == "dir{bar/}");
+ assert (ts (name (dir ("bar/baz/"), "dir", "")) == "bar/dir{baz/}");
+
+ assert (ts (name (dir ("bar/"), "foo")) == "bar/foo");
+
+ assert (ts (name (dir ("bar/"), "dir", "foo")) == "bar/dir{foo}");
+ assert (ts (name (dir ("bar/baz/"), "dir", "foo")) == "bar/baz/dir{foo}");
+ }
+
+ // Test stream representation.
+ //
+ {
+ auto ts = [] (const name& n, bool quote = true)
+ {
+ ostringstream os;
+ stream_verb (os, stream_verbosity (0, 1));
+ to_stream (os, n, quote);
+ return os.str ();
+ };
+
+ assert (ts (name ()) == "''");
+ assert (ts (name (), false) == "{}");
+
+ assert (ts (name ("foo")) == "foo");
+
+ assert (ts (name (dir ("bar/"))) == "bar/");
+ assert (ts (name (dir ("bar/baz/"))) == "bar/baz/");
+
+ assert (ts (name (dir ("bar/"), "dir", "")) == "dir{bar/}");
+ assert (ts (name (dir ("bar/baz/"), "dir", "")) == "bar/dir{baz/}");
+
+ assert (ts (name (dir ("bar/"), "foo")) == "bar/foo");
+
+ assert (ts (name (dir ("bar/"), "dir", "foo")) == "bar/dir{foo}");
+ assert (ts (name (dir ("bar/baz/"), "dir", "foo")) == "bar/baz/dir{foo}");
+
+ // Quoting.
+ //
+ assert (ts (name (dir ("bar baz/"), "dir", "foo fox")) == "'bar baz/'dir{'foo fox'}");
+
+ // Relative logic.
+ //
+#ifndef _WIN32
+ dir rb ("/bar/");
+ relative_base = &rb;
+
+ assert (ts (name (dir ("/bar/"), "dir", "")) == "dir{./}");
+ assert (ts (name (dir ("/bar/"), "", "foo")) == "foo");
+ assert (ts (name (dir ("/bar/baz/"), "dir", "")) == "dir{baz/}");
+#endif
+ }
+
+ return 0;
+ }
+}
+
+int
+main (int argc, char* argv[])
+{
+ return build2::main (argc, argv);
+}
diff --git a/libbuild2/operation.cxx b/libbuild2/operation.cxx
new file mode 100644
index 0000000..9d84cc2
--- /dev/null
+++ b/libbuild2/operation.cxx
@@ -0,0 +1,617 @@
+// file : libbuild2/operation.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <libbuild2/operation.hxx>
+
+#include <iostream> // cout
+
+#include <libbuild2/file.hxx>
+#include <libbuild2/scope.hxx>
+#include <libbuild2/target.hxx>
+#include <libbuild2/algorithm.hxx>
+#include <libbuild2/diagnostics.hxx>
+
+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;
+ }
+
+ // noop
+ //
+ const meta_operation_info mo_noop {
+ noop_id,
+ "noop",
+ "", // Presumably we will never need these since we are not going
+ "", // to do anything.
+ "",
+ "",
+ true, // bootstrap_outer
+ nullptr, // meta-operation pre
+ nullptr, // operation pre
+ &load,
+ nullptr, // search
+ nullptr, // match
+ nullptr, // execute
+ nullptr, // operation post
+ nullptr, // meta-operation post
+ nullptr // include
+ };
+
+ // perform
+ //
+ void
+ load (const values&,
+ scope& root,
+ const path& bf,
+ const dir_path& out_base,
+ const dir_path& src_base,
+ const location&)
+ {
+ // Load project's root.build.
+ //
+ load_root (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.rw (root).insert (out_base));
+ scope& base (setup_base (i, out_base, src_base));
+
+ // Load the buildfile unless it is implied.
+ //
+ if (!bf.empty ())
+ source_once (root, base, bf, root);
+ }
+
+ void
+ search (const values&,
+ const scope&,
+ const scope& bs,
+ const path& bf,
+ const target_key& tk,
+ const location& l,
+ action_targets& ts)
+ {
+ tracer trace ("search");
+
+ phase_lock pl (run_phase::match);
+
+ const target* t (targets.find (tk, trace));
+
+ // Only do the implied buildfile if we haven't loaded one. Failed that we
+ // may try go this route even though we've concluded the implied buildfile
+ // is implausible and have loaded an outer buildfile (see main() for
+ // details).
+ //
+ if (t == nullptr && tk.is_a<dir> () && bf.empty ())
+ t = dir::search_implied (bs, tk, trace);
+
+ if (t == nullptr)
+ {
+ diag_record dr (fail (l));
+
+ dr << "unknown target " << tk;
+
+ if (!bf.empty ())
+ dr << " in " << bf;
+ }
+
+ ts.push_back (t);
+ }
+
+ void
+ match (const values&, action a, action_targets& ts, uint16_t diag, bool prog)
+ {
+ tracer trace ("match");
+
+ {
+ phase_lock l (run_phase::match);
+
+ // Setup progress reporting if requested.
+ //
+ string what; // Note: must outlive monitor_guard.
+ scheduler::monitor_guard mg;
+
+ if (prog && show_progress (2 /* max_verb */))
+ {
+ size_t incr (stderr_term ? 1 : 10); // Scale depending on output type.
+
+ what = " targets to " + diag_do (a);
+
+ mg = sched.monitor (
+ target_count,
+ incr,
+ [incr, &what] (size_t c) -> size_t
+ {
+ diag_progress_lock pl;
+ diag_progress = ' ';
+ diag_progress += to_string (c);
+ diag_progress += what;
+ return c + incr;
+ });
+ }
+
+ // Start asynchronous matching of prerequisites keeping track of how
+ // many we have started. Wait with unlocked phase to allow phase
+ // switching.
+ //
+ size_t i (0), n (ts.size ());
+ {
+ atomic_count task_count (0);
+ wait_guard wg (task_count, true);
+
+ for (; i != n; ++i)
+ {
+ const target& t (ts[i].as_target ());
+ l5 ([&]{trace << diag_doing (a, t);});
+
+ target_state s (match_async (a, t, 0, task_count, false));
+
+ // Bail out if the target has failed and we weren't instructed to
+ // keep going.
+ //
+ if (s == target_state::failed && !keep_going)
+ {
+ ++i;
+ break;
+ }
+ }
+
+ wg.wait ();
+ }
+
+ // Clear the progress if present.
+ //
+ if (mg)
+ {
+ diag_progress_lock pl;
+ diag_progress.clear ();
+ }
+
+ // We are now running serially. Re-examine targets that we have matched.
+ //
+ bool fail (false);
+ for (size_t j (0); j != n; ++j)
+ {
+ action_target& at (ts[j]);
+ const target& t (at.as_target ());
+
+ target_state s (j < i
+ ? match (a, t, false)
+ : target_state::postponed);
+ switch (s)
+ {
+ case target_state::postponed:
+ {
+ // We bailed before matching it (leave state in action_target as
+ // unknown).
+ //
+ if (verb != 0 && diag >= 1)
+ info << "not " << diag_did (a, t);
+
+ break;
+ }
+ case target_state::unknown:
+ case target_state::unchanged:
+ {
+ break; // Matched successfully.
+ }
+ case target_state::failed:
+ {
+ // Things didn't go well for this target.
+ //
+ if (verb != 0 && diag >= 1)
+ info << "failed to " << diag_do (a, t);
+
+ at.state = s;
+ fail = true;
+ break;
+ }
+ default:
+ assert (false);
+ }
+ }
+
+ if (fail)
+ throw failed ();
+ }
+
+ // Phase restored to load.
+ //
+ assert (phase == run_phase::load);
+ }
+
+ void
+ execute (const values&, action a, action_targets& ts,
+ uint16_t diag, bool prog)
+ {
+ tracer trace ("execute");
+
+ // Reverse the order of targets if the execution mode is 'last'.
+ //
+ if (current_mode == execution_mode::last)
+ reverse (ts.begin (), ts.end ());
+
+ // Tune the scheduler.
+ //
+ switch (current_inner_oif->concurrency)
+ {
+ case 0: sched.tune (1); break; // Run serially.
+ case 1: break; // Run as is.
+ default: assert (false); // Not yet supported.
+ }
+
+ phase_lock pl (run_phase::execute); // Never switched.
+
+ // Set the dry-run flag.
+ //
+ dry_run = dry_run_option;
+
+ // Setup progress reporting if requested.
+ //
+ string what; // Note: must outlive monitor_guard.
+ scheduler::monitor_guard mg;
+
+ if (prog && show_progress (1 /* max_verb */))
+ {
+ size_t init (target_count.load (memory_order_relaxed));
+ size_t incr (init > 100 ? init / 100 : 1); // 1%.
+
+ if (init != incr)
+ {
+ what = "% of targets " + diag_did (a);
+
+ mg = sched.monitor (
+ target_count,
+ init - incr,
+ [init, incr, &what] (size_t c) -> size_t
+ {
+ size_t p ((init - c) * 100 / init);
+ size_t s (skip_count.load (memory_order_relaxed));
+
+ diag_progress_lock pl;
+ diag_progress = ' ';
+ diag_progress += to_string (p);
+ diag_progress += what;
+
+ if (s != 0)
+ {
+ diag_progress += " (";
+ diag_progress += to_string (s);
+ diag_progress += " skipped)";
+ }
+
+ return c - incr;
+ });
+ }
+ }
+
+ // Similar logic to execute_members(): first start asynchronous execution
+ // of all the top-level targets.
+ //
+ {
+ atomic_count task_count (0);
+ wait_guard wg (task_count);
+
+ for (const action_target& at: ts)
+ {
+ const target& t (at.as_target ());
+
+ l5 ([&]{trace << diag_doing (a, t);});
+
+ target_state s (execute_async (a, t, 0, task_count, false));
+
+ // Bail out if the target has failed and we weren't instructed to keep
+ // going.
+ //
+ if (s == target_state::failed && !keep_going)
+ break;
+ }
+
+ wg.wait ();
+ }
+
+ // We are now running serially.
+ //
+
+ sched.tune (0); // Restore original scheduler settings.
+
+ // Clear the dry-run flag.
+ //
+ dry_run = false;
+
+ // Clear the progress if present.
+ //
+ if (mg)
+ {
+ diag_progress_lock pl;
+ diag_progress.clear ();
+ }
+
+ // Print skip count if not zero. Note that we print it regardless of the
+ // diag level since this is essentially a "summary" of all the commands
+ // that we did not (and, in fact, used to originally) print.
+ //
+ if (verb != 0)
+ {
+ if (size_t s = skip_count.load (memory_order_relaxed))
+ {
+ text << "skipped " << diag_doing (a) << ' ' << s << " targets";
+ }
+ }
+
+ // Re-examine all the targets and print diagnostics.
+ //
+ bool fail (false);
+ for (action_target& at: ts)
+ {
+ const target& t (at.as_target ());
+
+ switch ((at.state = t.executed_state (a, false)))
+ {
+ case target_state::unknown:
+ {
+ // We bailed before executing it (leave state in action_target as
+ // unknown).
+ //
+ if (verb != 0 && diag >= 1)
+ info << "not " << diag_did (a, t);
+
+ break;
+ }
+ case target_state::unchanged:
+ {
+ // Nothing had to be done.
+ //
+ if (verb != 0 && diag >= 2)
+ info << diag_done (a, t);
+
+ break;
+ }
+ case target_state::changed:
+ {
+ // Something has been done.
+ //
+ break;
+ }
+ case target_state::failed:
+ {
+ // Things didn't go well for this target.
+ //
+ if (verb != 0 && diag >= 1)
+ info << "failed to " << diag_do (a, t);
+
+ fail = true;
+ break;
+ }
+ default:
+ assert (false);
+ }
+ }
+
+ if (fail)
+ throw failed ();
+
+ // We should have executed every target that we matched, provided we
+ // haven't failed (in which case we could have bailed out early).
+ //
+ assert (target_count.load (memory_order_relaxed) == 0);
+ assert (dependency_count.load (memory_order_relaxed) == 0);
+ }
+
+ const meta_operation_info mo_perform {
+ perform_id,
+ "perform",
+ "",
+ "",
+ "",
+ "",
+ true, // bootstrap_outer
+ nullptr, // meta-operation pre
+ nullptr, // operation pre
+ &load,
+ &search,
+ &match,
+ &execute,
+ nullptr, // operation post
+ nullptr, // meta-operation post
+ nullptr // include
+ };
+
+ // info
+ //
+ static operation_id
+ info_operation_pre (const values&, operation_id o)
+ {
+ if (o != default_id)
+ fail << "explicit operation specified for meta-operation info";
+
+ return o;
+ }
+
+ void
+ info_load (const values&,
+ scope& rs,
+ const path&,
+ const dir_path& out_base,
+ const dir_path& src_base,
+ const location& l)
+ {
+ // For info we don't want to go any further than bootstrap so that it can
+ // be used in pretty much any situation (unresolved imports, etc). We do
+ // need to setup root as base though.
+
+ if (rs.out_path () != out_base || rs.src_path () != src_base)
+ fail (l) << "meta-operation info target must be project root directory";
+
+ setup_base (scopes.rw (rs).insert (out_base), out_base, src_base);
+ }
+
+ void
+ info_search (const values&,
+ const scope& rs,
+ const scope&,
+ const path&,
+ const target_key& tk,
+ const location& l,
+ action_targets& ts)
+ {
+ // Collect all the projects we need to print information about.
+
+ // We've already verified the target is in the project root. Now verify
+ // it is dir{}.
+ //
+ if (!tk.type->is_a<dir> ())
+ fail (l) << "meta-operation info target must be project root directory";
+
+ ts.push_back (&rs);
+ }
+
+ static void
+ info_execute (const values&, action, action_targets& ts, uint16_t, bool)
+ {
+ for (size_t i (0); i != ts.size (); ++i)
+ {
+ // Separate projects with blank lines.
+ //
+ if (i != 0)
+ cout << endl;
+
+ const scope& rs (*static_cast<const scope*> (ts[i].target));
+
+ // Print [meta_]operation names. Due to the way our aliasing works, we
+ // have to go through the [meta_]operation_table.
+ //
+ auto print_ops = [] (const auto& ov, const auto& ot)
+ {
+ // This is a sparse vector with NULL holes. id 0 is invalid while 1 is
+ // the noop meta-operation and the default operation; we omit printing
+ // both.
+ //
+ for (uint8_t id (2); id < ov.size (); ++id)
+ {
+ if (ov[id] != nullptr)
+ cout << ' ' << ot[id];
+ }
+ };
+
+ // This could be a simple project that doesn't set project name.
+ //
+ cout
+ << "project: " << cast_empty<project_name> (rs[var_project]) << endl
+ << "version: " << cast_empty<string> (rs[var_version]) << endl
+ << "summary: " << cast_empty<string> (rs[var_project_summary]) << endl
+ << "url: " << cast_empty<string> (rs[var_project_url]) << endl
+ << "src_root: " << cast<dir_path> (rs[var_src_root]) << endl
+ << "out_root: " << cast<dir_path> (rs[var_out_root]) << endl
+ << "amalgamation: " << cast_empty<dir_path> (rs[var_amalgamation]) << endl
+ << "subprojects: " << cast_empty<subprojects> (rs[var_subprojects]) << endl
+ << "operations:"; print_ops (rs.root_extra->operations, operation_table); cout << endl
+ << "meta-operations:"; print_ops (rs.root_extra->meta_operations, meta_operation_table); cout << endl;
+ }
+ }
+
+ const meta_operation_info mo_info {
+ info_id,
+ "info",
+ "",
+ "",
+ "",
+ "",
+ false, // bootstrap_outer
+ nullptr, // meta-operation pre
+ &info_operation_pre,
+ &info_load,
+ &info_search,
+ nullptr, // match
+ &info_execute,
+ nullptr, // operation post
+ nullptr, // meta-operation post
+ nullptr // include
+ };
+
+ // operations
+ //
+ const operation_info op_default {
+ default_id,
+ 0,
+ "<default>",
+ "",
+ "",
+ "",
+ "",
+ execution_mode::first,
+ 1,
+ nullptr,
+ nullptr
+ };
+
+#ifndef _MSC_VER
+ constexpr
+#else
+ // VC doesn't "see" this can be const-initialized so we have to hack around
+ // to ensure correct initialization order.
+ //
+ #pragma warning(disable: 4073)
+ #pragma init_seg(lib)
+ const
+#endif
+ operation_info op_update {
+ update_id,
+ 0,
+ "update",
+ "update",
+ "updating",
+ "updated",
+ "is up to date",
+ execution_mode::first,
+ 1,
+ nullptr,
+ nullptr
+ };
+
+ const operation_info op_clean {
+ clean_id,
+ 0,
+ "clean",
+ "clean",
+ "cleaning",
+ "cleaned",
+ "is clean",
+ execution_mode::last,
+ 1,
+ nullptr,
+ nullptr
+ };
+
+ // Tables.
+ //
+ string_table<meta_operation_id, meta_operation_data> meta_operation_table;
+ string_table<operation_id> operation_table;
+}
diff --git a/libbuild2/operation.hxx b/libbuild2/operation.hxx
new file mode 100644
index 0000000..86f93c6
--- /dev/null
+++ b/libbuild2/operation.hxx
@@ -0,0 +1,361 @@
+// file : libbuild2/operation.hxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef LIBBUILD2_OPERATION_HXX
+#define LIBBUILD2_OPERATION_HXX
+
+#include <libbutl/string-table.mxx>
+
+#include <libbuild2/types.hxx>
+#include <libbuild2/utility.hxx>
+
+#include <libbuild2/action.hxx>
+#include <libbuild2/variable.hxx>
+#include <libbuild2/prerequisite.hxx>
+#include <libbuild2/target-state.hxx>
+
+#include <libbuild2/export.hxx>
+
+namespace build2
+{
+ class location;
+ class scope;
+ class target_key;
+ class target;
+ struct prerequisite_member;
+
+ struct opspec;
+
+ // Meta-operation info.
+ //
+
+ // Normally a list of resolved and matched targets to execute. But can be
+ // something else, depending on the meta-operation.
+ //
+ // The state is used to print structured result state. If it is not unknown,
+ // then this is assumed to be a target.
+ //
+ struct action_target
+ {
+ using target_type = build2::target;
+
+ const void* target = nullptr;
+ target_state state = target_state::unknown;
+
+ action_target () = default;
+ action_target (const void* t): target (t) {}
+
+ const target_type&
+ as_target () const {return *static_cast<const target_type*> (target);}
+ };
+
+ class action_targets: public vector<action_target>
+ {
+ public:
+ using vector<action_target>::vector;
+
+ void
+ reset () {for (auto& x: *this) x.state = target_state::unknown;}
+ };
+
+ struct meta_operation_info
+ {
+ const meta_operation_id id;
+ const string name;
+
+ // Name derivatives for diagnostics. If empty, then the meta-
+ // operation need not be mentioned.
+ //
+ const string name_do; // E.g., [to] 'configure'.
+ const string name_doing; // E.g., [while] 'configuring'.
+ const string name_did; // E.g., 'configured'.
+ const string name_done; // E.g., 'is configured'.
+
+ // Whether to bootstrap outer projects. If load() below calls load_root(),
+ // then this must be true. Note that this happens before
+ // meta_operation_pre() is called.
+ //
+ const bool bootstrap_outer;
+
+ // The first argument in all the callback is the meta-operation
+ // parameters.
+ //
+ // If the meta-operation expects parameters, then it should have a
+ // non-NULL meta_operation_pre(). Failed that, any parameters will be
+ // diagnosed as unexpected.
+
+ // Start of meta-operation and operation batches.
+ //
+ // 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) (const values&, const location&);
+ operation_id (*operation_pre) (const values&, operation_id);
+
+ // Meta-operation-specific logic to load the buildfile, search and match
+ // the targets, and execute the action on the targets.
+ //
+ void (*load) (const values&,
+ scope& root,
+ const path& buildfile,
+ const dir_path& out_base,
+ const dir_path& src_base,
+ const location&);
+
+ void (*search) (const values&,
+ const scope& root,
+ const scope& base,
+ const path& buildfile,
+ const target_key&,
+ const location&,
+ action_targets&);
+
+ // Diagnostics levels:
+ //
+ // 0 - none (for structured result).
+ // 1 - failures only (for pre-operations).
+ // 2 - all (for normal operations).
+ //
+ // The false progress argument can be used to suppress progress. If it is
+ // true, then whether the progress is shown is meta operation-specific (in
+ // other words, you can suppress it but not force it).
+ //
+ void (*match) (const values&, action, action_targets&,
+ uint16_t diag, bool progress);
+
+ void (*execute) (const values&, action, action_targets&,
+ uint16_t diag, bool progress);
+
+ // End of operation and meta-operation batches.
+ //
+ void (*operation_post) (const values&, operation_id);
+ void (*meta_operation_post) (const values&);
+
+ // Optional prerequisite inclusion/exclusion override callback. See
+ // include() for details.
+ //
+ include_type (*include) (action,
+ const target&,
+ const prerequisite_member&,
+ include_type);
+ };
+
+ // 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.
+ //
+ LIBBUILD2_SYMEXPORT void
+ load (const values&,
+ scope&,
+ const path&,
+ const dir_path&,
+ const dir_path&,
+ 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.
+ //
+ LIBBUILD2_SYMEXPORT void
+ search (const values&,
+ const scope&,
+ const scope&,
+ const path&,
+ const target_key&,
+ const location&,
+ action_targets&);
+
+ LIBBUILD2_SYMEXPORT void
+ match (const values&, action, action_targets&,
+ uint16_t diag, bool prog);
+
+ // Execute the action on the list of targets. This is the default
+ // implementation that does just that while issuing appropriate
+ // diagnostics (unless quiet).
+ //
+ LIBBUILD2_SYMEXPORT void
+ execute (const values&, action, const action_targets&,
+ uint16_t diag, bool prog);
+
+ LIBBUILD2_SYMEXPORT extern const meta_operation_info mo_noop;
+ LIBBUILD2_SYMEXPORT extern const meta_operation_info mo_perform;
+ LIBBUILD2_SYMEXPORT extern const meta_operation_info mo_info;
+
+ // Operation info.
+ //
+ // NOTE: keep POD-like to ensure can be constant-initialized in order to
+ // sidestep static initialization order (relied upon in operation
+ // aliasing).
+ //
+ struct operation_info
+ {
+ // If outer_id is not 0, then use that as the outer part of the
+ // action.
+ //
+ const operation_id id;
+ const operation_id outer_id;
+ const char* 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 char* name_do; // E.g., [to] 'update'.
+ const char* name_doing; // E.g., [while] 'updating'.
+ const char* name_did; // E.g., [not] 'updated'.
+ const char* name_done; // E.g., 'is up to date'.
+
+ const execution_mode mode;
+
+ // This is the operation's concurrency multiplier. 0 means run serially,
+ // 1 means run at hardware concurrency (unless overridden by the user).
+ //
+ const size_t concurrency;
+
+ // The first argument in all the callback is the operation parameters.
+ //
+ // If the operation expects parameters, then it should have a non-NULL
+ // pre(). Failed that, any parameters will be diagnosed as unexpected.
+
+ // 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) (const values&, meta_operation_id, const location&);
+ operation_id (*post) (const values&, meta_operation_id);
+ };
+
+ // Built-in operations.
+ //
+ LIBBUILD2_SYMEXPORT extern const operation_info op_default;
+ LIBBUILD2_SYMEXPORT extern const operation_info op_update;
+ LIBBUILD2_SYMEXPORT extern const operation_info op_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.
+ //
+ // A built-in/pre-defined meta-operation can also provide a pre-processor
+ // callback that will be called for operation-specs before any project
+ // discovery/bootstrap is performed.
+ //
+ struct meta_operation_data
+ {
+ // The processor may modify the parameters, opspec, and change the
+ // meta-operation by returning a different name.
+ //
+ // If lifted is true then the operation name in opspec is bogus (has
+ // been lifted) and the default/empty name should be assumed instead.
+ //
+ using process_func = const string& (const variable_overrides&,
+ values&,
+ vector_view<opspec>&,
+ bool lifted,
+ const location&);
+
+ meta_operation_data () = default;
+ meta_operation_data (const char* n, process_func p = nullptr)
+ : name (n), process (p) {}
+
+ string name;
+ process_func* process;
+ };
+
+ inline ostream&
+ operator<< (ostream& os, const meta_operation_data& d)
+ {
+ return os << d.name;
+ }
+
+ LIBBUILD2_SYMEXPORT extern butl::string_table<meta_operation_id,
+ meta_operation_data>
+ meta_operation_table;
+
+ LIBBUILD2_SYMEXPORT 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 = 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>;
+}
+
+namespace butl
+{
+ template <>
+ struct string_table_traits<build2::meta_operation_data>
+ {
+ static const std::string&
+ key (const build2::meta_operation_data& d) {return d.name;}
+ };
+}
+
+#endif // LIBBUILD2_OPERATION_HXX
diff --git a/libbuild2/parser.cxx b/libbuild2/parser.cxx
new file mode 100644
index 0000000..4e8ad23
--- /dev/null
+++ b/libbuild2/parser.cxx
@@ -0,0 +1,5526 @@
+// file : libbuild2/parser.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <libbuild2/parser.hxx>
+
+#include <sstream>
+#include <iostream> // cout
+
+#include <libbutl/filesystem.mxx> // path_search(), path_match()
+
+#include <libbuild2/dump.hxx>
+#include <libbuild2/file.hxx>
+#include <libbuild2/scope.hxx>
+#include <libbuild2/module.hxx>
+#include <libbuild2/target.hxx>
+#include <libbuild2/context.hxx>
+#include <libbuild2/function.hxx>
+#include <libbuild2/variable.hxx>
+#include <libbuild2/filesystem.hxx>
+#include <libbuild2/diagnostics.hxx>
+#include <libbuild2/prerequisite.hxx>
+
+using namespace std;
+
+namespace build2
+{
+ using type = token_type;
+
+ class parser::enter_scope
+ {
+ public:
+ enter_scope (): p_ (nullptr), r_ (nullptr), s_ (nullptr), b_ (nullptr) {}
+
+ enter_scope (parser& p, dir_path&& d)
+ : p_ (&p), r_ (p.root_), s_ (p.scope_), b_ (p.pbase_)
+ {
+ // Try hard not to call normalize(). Most of the time we will go just
+ // one level deeper.
+ //
+ bool n (true);
+
+ if (d.relative ())
+ {
+ // Relative scopes are opened relative to out, not src.
+ //
+ if (d.simple () && !d.current () && !d.parent ())
+ {
+ d = dir_path (p.scope_->out_path ()) /= d.string ();
+ n = false;
+ }
+ else
+ d = p.scope_->out_path () / d;
+ }
+
+ if (n)
+ d.normalize ();
+
+ p.switch_scope (d);
+ }
+
+ ~enter_scope ()
+ {
+ if (p_ != nullptr)
+ {
+ p_->scope_ = s_;
+ p_->root_ = r_;
+ p_->pbase_ = b_;
+ }
+ }
+
+ explicit operator bool () const {return p_ != nullptr;}
+
+ // Note: move-assignable to empty only.
+ //
+ enter_scope (enter_scope&& x) {*this = move (x);}
+ enter_scope& operator= (enter_scope&& x)
+ {
+ if (this != &x)
+ {
+ p_ = x.p_;
+ r_ = x.r_;
+ s_ = x.s_;
+ b_ = x.b_;
+ x.p_ = nullptr;
+ }
+ return *this;
+ }
+
+ enter_scope (const enter_scope&) = delete;
+ enter_scope& operator= (const enter_scope&) = delete;
+
+ private:
+ parser* p_;
+ scope* r_;
+ scope* s_;
+ const dir_path* b_; // Pattern base.
+ };
+
+ class parser::enter_target
+ {
+ public:
+ enter_target (): p_ (nullptr), t_ (nullptr) {}
+
+ enter_target (parser& p, target& t)
+ : p_ (&p), t_ (p.target_)
+ {
+ p.target_ = &t;
+ }
+
+ enter_target (parser& p,
+ name&& n, // If n.pair, then o is out dir.
+ name&& o,
+ bool implied,
+ const location& loc,
+ tracer& tr)
+ : p_ (&p), t_ (p.target_)
+ {
+ p.target_ = &insert_target (p, move (n), move (o), implied, loc, tr);
+ }
+
+ // Find or insert.
+ //
+ static target&
+ insert_target (parser& p,
+ name&& n, // If n.pair, then o is out dir.
+ name&& o,
+ bool implied,
+ const location& loc,
+ tracer& tr)
+ {
+ auto r (process_target (p, n, o, loc));
+ return targets.insert (*r.first, // target type
+ move (n.dir),
+ move (o.dir),
+ move (n.value),
+ move (r.second), // extension
+ implied,
+ tr).first;
+ }
+
+ // Only find.
+ //
+ static const target*
+ find_target (parser& p,
+ name& n, // If n.pair, then o is out dir.
+ name& o,
+ const location& loc,
+ tracer& tr)
+ {
+ auto r (process_target (p, n, o, loc));
+ return targets.find (*r.first, // target type
+ n.dir,
+ o.dir,
+ n.value,
+ r.second, // extension
+ tr);
+ }
+
+ static pair<const target_type*, optional<string>>
+ process_target (parser& p,
+ name& n, // If n.pair, then o is out dir.
+ name& o,
+ const location& loc)
+ {
+ auto r (p.scope_->find_target_type (n, loc));
+
+ if (r.first == nullptr)
+ p.fail (loc) << "unknown target type " << n.type;
+
+ bool src (n.pair); // If out-qualified, then it is from src.
+ if (src)
+ {
+ assert (n.pair == '@');
+
+ if (!o.directory ())
+ p.fail (loc) << "expected directory after '@'";
+ }
+
+ dir_path& d (n.dir);
+
+ const dir_path& sd (p.scope_->src_path ());
+ const dir_path& od (p.scope_->out_path ());
+
+ if (d.empty ())
+ d = src ? sd : od; // Already dormalized.
+ else
+ {
+ if (d.relative ())
+ d = (src ? sd : od) / d;
+
+ d.normalize ();
+ }
+
+ dir_path out;
+ if (src && sd != od) // If in-source build, then out must be empty.
+ {
+ out = o.dir.relative () ? od / o.dir : move (o.dir);
+ out.normalize ();
+ }
+ o.dir = move (out); // Result.
+
+ return r;
+ }
+
+ ~enter_target ()
+ {
+ if (p_ != nullptr)
+ p_->target_ = t_;
+ }
+
+ // Note: move-assignable to empty only.
+ //
+ enter_target (enter_target&& x) {*this = move (x);}
+ enter_target& operator= (enter_target&& x) {
+ p_ = x.p_; t_ = x.t_; x.p_ = nullptr; return *this;}
+
+ enter_target (const enter_target&) = delete;
+ enter_target& operator= (const enter_target&) = delete;
+
+ private:
+ parser* p_;
+ target* t_;
+ };
+
+ class parser::enter_prerequisite
+ {
+ public:
+ enter_prerequisite (): p_ (nullptr), r_ (nullptr) {}
+
+ enter_prerequisite (parser& p, prerequisite& r)
+ : p_ (&p), r_ (p.prerequisite_)
+ {
+ assert (p.target_ != nullptr);
+ p.prerequisite_ = &r;
+ }
+
+ ~enter_prerequisite ()
+ {
+ if (p_ != nullptr)
+ p_->prerequisite_ = r_;
+ }
+
+ // Note: move-assignable to empty only.
+ //
+ enter_prerequisite (enter_prerequisite&& x) {*this = move (x);}
+ enter_prerequisite& operator= (enter_prerequisite&& x) {
+ p_ = x.p_; r_ = x.r_; x.p_ = nullptr; return *this;}
+
+ enter_prerequisite (const enter_prerequisite&) = delete;
+ enter_prerequisite& operator= (const enter_prerequisite&) = delete;
+
+ private:
+ parser* p_;
+ prerequisite* r_;
+ };
+
+ void parser::
+ parse_buildfile (istream& is, const path& p, scope& root, scope& base)
+ {
+ path_ = &p;
+
+ lexer l (is, *path_);
+ lexer_ = &l;
+ root_ = &root;
+ scope_ = &base;
+ pbase_ = scope_->src_path_;
+ target_ = nullptr;
+ prerequisite_ = nullptr;
+ default_target_ = nullptr;
+
+ enter_buildfile (p); // Needs scope_.
+
+ token t;
+ type tt;
+ next (t, tt);
+
+ parse_clause (t, tt);
+
+ if (tt != type::eos)
+ fail (t) << "unexpected " << t;
+
+ process_default_target (t);
+ }
+
+ token parser::
+ parse_variable (lexer& l, scope& s, const variable& var, type kind)
+ {
+ path_ = &l.name ();
+ lexer_ = &l;
+ scope_ = &s;
+ pbase_ = scope_->src_path_; // Normally NULL.
+ target_ = nullptr;
+ prerequisite_ = nullptr;
+
+ token t;
+ type tt;
+ parse_variable (t, tt, var, kind);
+ return t;
+ }
+
+ pair<value, token> parser::
+ parse_variable_value (lexer& l,
+ scope& s,
+ const dir_path* b,
+ const variable& var)
+ {
+ path_ = &l.name ();
+ lexer_ = &l;
+ scope_ = &s;
+ pbase_ = b;
+ target_ = nullptr;
+ prerequisite_ = nullptr;
+
+ token t;
+ type tt;
+ value rhs (parse_variable_value (t, tt));
+
+ value lhs;
+ apply_value_attributes (&var, lhs, move (rhs), type::assign);
+
+ return make_pair (move (lhs), move (t));
+ }
+
+ // Test if a string is a wildcard pattern.
+ //
+ static inline bool
+ pattern (const string& s)
+ {
+ return s.find_first_of ("*?") != string::npos;
+ };
+
+ bool parser::
+ parse_clause (token& t, type& tt, bool one)
+ {
+ tracer trace ("parser::parse_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.
+ //
+ bool parsed (false);
+
+ while (tt != type::eos && !(one && parsed))
+ {
+ // Extract attributes if any.
+ //
+ assert (attributes_.empty ());
+ auto at (attributes_push (t, tt));
+
+ // We should always start with one or more names, potentially
+ // <>-grouped.
+ //
+ if (!(start_names (tt) || tt == type::labrace))
+ {
+ // Something else. Let our caller handle that.
+ //
+ if (at.first)
+ fail (at.second) << "attributes before " << t;
+ else
+ attributes_pop ();
+
+ break;
+ }
+
+ // Now we will either parse something or fail.
+ //
+ if (!parsed)
+ parsed = true;
+
+ // See if this is one of the directives.
+ //
+ if (tt == type::word && keyword (t))
+ {
+ const string& n (t.value);
+ void (parser::*f) (token&, type&) = nullptr;
+
+ // @@ Is this the only place where some of these are valid? Probably
+ // also in the var namespace?
+ //
+ if (n == "assert" ||
+ n == "assert!")
+ {
+ f = &parser::parse_assert;
+ }
+ else if (n == "print") // Unlike text goes to stdout.
+ {
+ f = &parser::parse_print;
+ }
+ else if (n == "fail" ||
+ n == "warn" ||
+ n == "info" ||
+ n == "text")
+ {
+ f = &parser::parse_diag;
+ }
+ else if (n == "dump")
+ {
+ f = &parser::parse_dump;
+ }
+ else if (n == "source")
+ {
+ f = &parser::parse_source;
+ }
+ else if (n == "include")
+ {
+ f = &parser::parse_include;
+ }
+ else if (n == "run")
+ {
+ f = &parser::parse_run;
+ }
+ else if (n == "import")
+ {
+ f = &parser::parse_import;
+ }
+ else if (n == "export")
+ {
+ f = &parser::parse_export;
+ }
+ else if (n == "using" ||
+ n == "using?")
+ {
+ f = &parser::parse_using;
+ }
+ else if (n == "define")
+ {
+ f = &parser::parse_define;
+ }
+ else if (n == "if" ||
+ n == "if!")
+ {
+ f = &parser::parse_if_else;
+ }
+ else if (n == "else" ||
+ n == "elif" ||
+ n == "elif!")
+ {
+ // Valid ones are handled in if_else().
+ //
+ fail (t) << n << " without if";
+ }
+ else if (n == "for")
+ {
+ f = &parser::parse_for;
+ }
+
+ if (f != nullptr)
+ {
+ if (at.first)
+ fail (at.second) << "attributes before " << n;
+ else
+ attributes_pop ();
+
+ (this->*f) (t, tt);
+ continue;
+ }
+ }
+
+ location nloc (get_location (t));
+ names ns;
+
+ if (tt != type::labrace)
+ {
+ ns = parse_names (t, tt, pattern_mode::ignore);
+
+ // Allow things like function calls that don't result in anything.
+ //
+ if (tt == type::newline && ns.empty ())
+ {
+ if (at.first)
+ fail (at.second) << "standalone attributes";
+ else
+ attributes_pop ();
+
+ next (t, tt);
+ continue;
+ }
+ }
+
+ // Handle ad hoc target group specification (<...>).
+ //
+ // We keep an "optional" (empty) vector of names parallel to ns.
+ //
+ adhoc_names ans;
+ if (tt == type::labrace)
+ {
+ while (tt == type::labrace)
+ {
+ // Parse target names inside < >.
+ //
+ next (t, tt);
+
+ auto at (attributes_push (t, tt));
+
+ if (at.first)
+ fail (at.second) << "attributes before ad hoc target";
+ else
+ attributes_pop ();
+
+ // Allow empty case (<>).
+ //
+ if (tt != type::rabrace)
+ {
+ location aloc (get_location (t));
+
+ // The first name (or a pair) is the primary target which we need
+ // to keep in ns. The rest, if any, are ad hoc members that we
+ // should move to ans.
+ //
+ size_t m (ns.size ());
+ parse_names (t, tt, ns, pattern_mode::ignore);
+ size_t n (ns.size ());
+
+ // Another empty case (<$empty>).
+ //
+ if (m != n)
+ {
+ m = n - m - (ns[m].pair ? 2 : 1); // Number of names to move.
+
+ // Allow degenerate case with just the primary target.
+ //
+ if (m != 0)
+ {
+ n -= m; // Number of names in ns we should end up with.
+
+ ans.resize (n); // Catch up with the names vector.
+ adhoc_names_loc& a (ans.back ());
+
+ a.loc = move (aloc);
+ a.ns.insert (a.ns.end (),
+ make_move_iterator (ns.begin () + n),
+ make_move_iterator (ns.end ()));
+ ns.resize (n);
+ }
+ }
+ }
+
+ if (tt != type::rabrace)
+ fail (t) << "expected '>' instead of " << t;
+
+ // Parse the next chunk of target names after >, if any.
+ //
+ next (t, tt);
+ if (start_names (tt))
+ parse_names (t, tt, ns, pattern_mode::ignore);
+ }
+
+ if (!ans.empty ())
+ ans.resize (ns.size ()); // Catch up with the final chunk.
+
+ if (tt != type::colon)
+ fail (t) << "expected ':' instead of " << t;
+
+ if (ns.empty ())
+ fail (t) << "expected target before ':'";
+ }
+
+ // If we have a colon, then this is target-related.
+ //
+ if (tt == type::colon)
+ {
+ // While '{}:' means empty name, '{$x}:' where x is empty list
+ // means empty list.
+ //
+ if (ns.empty ())
+ fail (t) << "expected target before ':'";
+
+ if (at.first)
+ fail (at.second) << "attributes before target";
+ else
+ attributes_pop ();
+
+ // Call the specified parsing function (either variable or block) for
+ // each target. We handle multiple targets by replaying the tokens
+ // since the value/block may contain variable expansions that would be
+ // sensitive to the target context in which they are evaluated. The
+ // function signature is:
+ //
+ // void (token& t, type& tt, const target_type* type, string pat)
+ //
+ auto for_each = [this, &trace,
+ &t, &tt,
+ &ns, &nloc, &ans] (auto&& f)
+ {
+ // Note: watch out for an out-qualified single target (two names).
+ //
+ replay_guard rg (*this,
+ ns.size () > 2 || (ns.size () == 2 && !ns[0].pair));
+
+ for (size_t i (0), e (ns.size ()); i != e; )
+ {
+ name& n (ns[i]);
+
+ if (n.qualified ())
+ fail (nloc) << "project name in target " << n;
+
+ // Figure out if this is a target or a target type/pattern (yeah,
+ // it can be a mixture).
+ //
+ if (pattern (n.value))
+ {
+ if (n.pair)
+ fail (nloc) << "out-qualified target type/pattern";
+
+ if (!ans.empty () && !ans[i].ns.empty ())
+ fail (ans[i].loc) << "ad hoc member in target type/pattern";
+
+ // If we have the directory, then it is the scope.
+ //
+ enter_scope sg;
+ if (!n.dir.empty ())
+ sg = enter_scope (*this, move (n.dir));
+
+ // Resolve target type. If none is specified or if it is '*',
+ // use the root of the hierarchy. So these are all equivalent:
+ //
+ // *: foo = bar
+ // {*}: foo = bar
+ // *{*}: foo = bar
+ //
+ const target_type* ti (
+ n.untyped () || n.type == "*"
+ ? &target::static_type
+ : scope_->find_target_type (n.type));
+
+ if (ti == nullptr)
+ fail (nloc) << "unknown target type " << n.type;
+
+ f (t, tt, ti, move (n.value));
+ }
+ else
+ {
+ name o (n.pair ? move (ns[++i]) : name ());
+ enter_target tg (*this,
+ move (n),
+ move (o),
+ true /* implied */,
+ nloc,
+ trace);
+
+ // Enter ad hoc members.
+ //
+ if (!ans.empty ())
+ {
+ // Note: index after the pair increment.
+ //
+ enter_adhoc_members (move (ans[i]), true /* implied */);
+ }
+
+ f (t, tt, nullptr, string ());
+ }
+
+ if (++i != e)
+ rg.play (); // Replay.
+ }
+ };
+
+ if (next (t, tt) == type::newline)
+ {
+ // See if this is a target block.
+ //
+ // Note that we cannot just let parse_dependency() handle this case
+ // because we can have (a mixture of) target type/patterns.
+ //
+ if (next (t, tt) == type::lcbrace && peek () == type::newline)
+ {
+ next (t, tt); // Newline.
+
+ // Parse the block for each target.
+ //
+ for_each ([this] (token& t, type& tt,
+ const target_type* type, string pat)
+ {
+ next (t, tt); // First token inside the block.
+
+ parse_variable_block (t, tt, type, move (pat));
+
+ if (tt != type::rcbrace)
+ fail (t) << "expected '}' instead of " << t;
+ });
+
+ next (t, tt); // Presumably newline after '}'.
+ next_after_newline (t, tt, '}'); // Should be on its own line.
+ }
+ else
+ {
+ // If not followed by a block, then it's a target without any
+ // prerequisites. We, however, cannot just fall through to the
+ // parse_dependency() call because we have already seen the next
+ // token.
+ //
+ // Note also that we treat this as an explicit dependency
+ // declaration (i.e., not implied).
+ //
+ enter_targets (move (ns), nloc, move (ans), 0);
+ }
+
+ continue;
+ }
+
+ // Target-specific variable assignment or dependency declaration,
+ // including a dependency chain and/or prerequisite-specific variable
+ // assignment.
+ //
+ auto at (attributes_push (t, tt));
+
+ if (!start_names (tt))
+ fail (t) << "unexpected " << t;
+
+ // @@ PAT: currently we pattern-expand target-specific vars.
+ //
+ const location ploc (get_location (t));
+ names pns (parse_names (t, tt, pattern_mode::expand));
+
+ // Target-specific variable assignment.
+ //
+ if (tt == type::assign || tt == type::prepend || tt == type::append)
+ {
+ type akind (tt);
+ const location aloc (get_location (t));
+
+ const variable& var (parse_variable_name (move (pns), ploc));
+ apply_variable_attributes (var);
+
+ if (var.visibility > variable_visibility::target)
+ {
+ fail (nloc) << "variable " << var << " has " << var.visibility
+ << " visibility but is assigned on a target";
+ }
+
+ // Parse the assignment for each target.
+ //
+ for_each ([this, &var, akind, &aloc] (token& t, type& tt,
+ const target_type* type,
+ string pat)
+ {
+ if (type == nullptr)
+ parse_variable (t, tt, var, akind);
+ else
+ parse_type_pattern_variable (t, tt,
+ *type, move (pat),
+ var, akind, aloc);
+ });
+
+ next_after_newline (t, tt);
+ }
+ // Dependency declaration potentially followed by a chain and/or a
+ // prerequisite-specific variable assignment/block.
+ //
+ else
+ {
+ if (at.first)
+ fail (at.second) << "attributes before prerequisites";
+ else
+ attributes_pop ();
+
+ bool r (parse_dependency (t, tt,
+ move (ns), nloc,
+ move (ans),
+ move (pns), ploc));
+ assert (r); // Block must have been claimed.
+ }
+
+ continue;
+ }
+
+ // Variable assignment.
+ //
+ // This can take any of the following forms:
+ //
+ // x = y
+ // foo/ x = y (ns will have two elements)
+ // foo/ [attrs] x = y (tt will be '[')
+ //
+ // In the future we may also want to support:
+ //
+ // foo/ bar/ x = y
+ //
+ if (tt == type::assign || tt == type::prepend || tt == type::append ||
+ tt == type::lsbrace)
+ {
+ // Detect and handle the directory scope. If things look off, then we
+ // let parse_variable_name() complain.
+ //
+ dir_path d;
+
+ if ((ns.size () == 2 && ns[0].directory ()) ||
+ (ns.size () == 1 && ns[0].directory () && tt == type::lsbrace))
+ {
+ if (at.first)
+ fail (at.second) << "attributes before scope directory";
+
+ if (tt == type::lsbrace)
+ {
+ attributes_pop ();
+ attributes_push (t, tt);
+
+ d = move (ns[0].dir);
+ nloc = get_location (t);
+ ns = parse_names (t, tt, pattern_mode::ignore);
+
+ // It got to be a variable assignment.
+ //
+ if (tt != type::assign &&
+ tt != type::prepend &&
+ tt != type::append)
+ fail (t) << "expected variable assignment instead of " << t;
+ }
+ else
+ {
+ d = move (ns[0].dir);
+ ns.erase (ns.begin ());
+ }
+ }
+
+ // Make sure not a pattern (see also the target case above and scope
+ // below).
+ //
+ if (pattern (d.string ()))
+ fail (nloc) << "pattern in directory " << d.representation ();
+
+ if (tt != type::lsbrace)
+ {
+ const variable& var (parse_variable_name (move (ns), nloc));
+ apply_variable_attributes (var);
+
+ if (var.visibility >= variable_visibility::target)
+ {
+ diag_record dr (fail (nloc));
+
+ dr << "variable " << var << " has " << var.visibility
+ << " visibility but is assigned on a scope";
+
+ if (var.visibility == variable_visibility::target)
+ dr << info << "consider changing it to '*: " << var << "'";
+ }
+
+ {
+ enter_scope sg (d.empty ()
+ ? enter_scope ()
+ : enter_scope (*this, move (d)));
+ parse_variable (t, tt, var, tt);
+ }
+
+ next_after_newline (t, tt);
+ continue;
+ }
+
+ // Not "our" attribute, see if anyone else likes it.
+ }
+
+ // See if this is a directory scope.
+ //
+ // Note: must be last since we are going to get the next token.
+ //
+ if (ns.size () == 1 && ns[0].directory () && tt == type::newline)
+ {
+ token ot (t);
+
+ if (next (t, tt) == type::lcbrace && peek () == type::newline)
+ {
+ dir_path&& d (move (ns[0].dir));
+
+ // Make sure not a pattern (see also the target and directory cases
+ // above).
+ //
+ if (pattern (d.string ()))
+ fail (nloc) << "pattern in directory " << d.representation ();
+
+ next (t, tt); // Newline.
+ next (t, tt); // First token inside the block.
+
+ if (at.first)
+ fail (at.second) << "attributes before scope directory";
+ else
+ attributes_pop ();
+
+ // Can contain anything that a top level can.
+ //
+ {
+ enter_scope sg (*this, move (d));
+ parse_clause (t, tt);
+ }
+
+ if (tt != type::rcbrace)
+ fail (t) << "expected '}' instead of " << t;
+
+ next (t, tt); // Presumably newline after '}'.
+ next_after_newline (t, tt, '}'); // Should be on its own line.
+ continue;
+ }
+
+ t = ot;
+ // Fall through to fail.
+ }
+
+ fail (t) << "unexpected " << t << " after " << ns;
+ }
+
+ return parsed;
+ }
+
+ void parser::
+ parse_variable_block (token& t, type& tt,
+ const target_type* type, string pat)
+ {
+ // Parse a target or prerequisite-specific variable block. If type is not
+ // NULL, then this is a target type/pattern-specific block.
+ //
+ // enter: first token of first line in the block
+ // leave: rcbrace
+ //
+ // This is a more restricted variant of parse_clause() that only allows
+ // variable assignments.
+ //
+ tracer trace ("parser::parse_variable_block", &path_);
+
+ while (tt != type::rcbrace && tt != type::eos)
+ {
+ attributes_push (t, tt);
+
+ location nloc (get_location (t));
+ names ns (parse_names (t, tt,
+ pattern_mode::ignore,
+ false /* chunk */,
+ "variable name"));
+
+ if (tt != type::assign &&
+ tt != type::prepend &&
+ tt != type::append)
+ fail (t) << "expected variable assignment instead of " << t;
+
+ const variable& var (parse_variable_name (move (ns), nloc));
+ apply_variable_attributes (var);
+
+ if (prerequisite_ != nullptr &&
+ var.visibility > variable_visibility::target)
+ {
+ fail (t) << "variable " << var << " has " << var.visibility
+ << " visibility but is assigned on a target";
+ }
+
+ if (type == nullptr)
+ parse_variable (t, tt, var, tt);
+ else
+ parse_type_pattern_variable (t, tt,
+ *type, pat, // Note: can't move.
+ var, tt, get_location (t));
+
+ if (tt != type::newline)
+ fail (t) << "expected newline instead of " << t;
+
+ next (t, tt);
+ }
+ }
+
+ void parser::
+ enter_adhoc_members (adhoc_names_loc&& ans, bool implied)
+ {
+ tracer trace ("parser::enter_adhoc_members", &path_);
+
+ names& ns (ans.ns);
+ const location& loc (ans.loc);
+
+ for (size_t i (0); i != ns.size (); ++i)
+ {
+ name&& n (move (ns[i]));
+ name&& o (n.pair ? move (ns[++i]) : name ());
+
+ if (n.qualified ())
+ fail (loc) << "project name in target " << n;
+
+ // We derive the path unless the target name ends with the '...' escape
+ // which here we treat as the "let the rule derive the path" indicator
+ // (see target::split_name() for details). This will only be useful for
+ // referring to ad hoc members that are managed by the group's matching
+ // rule. Note also that omitting '...' for such a member could be used
+ // to override the file name, provided the rule checks if the path has
+ // already been derived before doing it itself.
+ //
+ bool escaped;
+ {
+ const string& v (n.value);
+ size_t p (v.size ());
+
+ escaped = (p > 3 &&
+ v[--p] == '.' && v[--p] == '.' && v[--p] == '.' &&
+ v[--p] != '.');
+ }
+
+ target& at (
+ enter_target::insert_target (*this,
+ move (n), move (o),
+ implied,
+ loc, trace));
+
+ if (target_ == &at)
+ fail (loc) << "ad hoc group member " << at << " is primary target";
+
+ // Add as an ad hoc member at the end of the chain skipping duplicates.
+ //
+ {
+ const_ptr<target>* mp (&target_->member);
+ for (; *mp != nullptr; mp = &(*mp)->member)
+ {
+ if (*mp == &at)
+ {
+ mp = nullptr;
+ break;
+ }
+ }
+
+ if (mp != nullptr)
+ {
+ *mp = &at;
+ at.group = target_;
+ }
+ }
+
+ if (!escaped)
+ {
+ if (file* ft = at.is_a<file> ())
+ ft->derive_path ();
+ }
+ }
+ }
+
+ small_vector<reference_wrapper<target>, 1> parser::
+ enter_targets (names&& tns, const location& tloc, // Target names.
+ adhoc_names&& ans, // Ad hoc target names.
+ size_t prereq_size)
+ {
+ // Enter all the targets (normally we will have just one) and their ad hoc
+ // groups.
+ //
+ tracer trace ("parser::enter_targets", &path_);
+
+ small_vector<reference_wrapper<target>, 1> tgs;
+
+ for (size_t i (0); i != tns.size (); ++i)
+ {
+ name&& n (move (tns[i]));
+ name&& o (n.pair ? move (tns[++i]) : name ());
+
+ if (n.qualified ())
+ fail (tloc) << "project name in target " << n;
+
+ // Make sure none of our targets are patterns (maybe we will allow
+ // quoting later).
+ //
+ if (pattern (n.value))
+ fail (tloc) << "pattern in target " << n;
+
+ enter_target tg (*this,
+ move (n), move (o),
+ false /* implied */,
+ tloc, trace);
+
+ // Enter ad hoc members.
+ //
+ if (!ans.empty ())
+ {
+ // Note: index after the pair increment.
+ //
+ enter_adhoc_members (move (ans[i]), false /* implied */);
+ }
+
+ if (default_target_ == nullptr)
+ default_target_ = target_;
+
+ target_->prerequisites_state_.store (2, memory_order_relaxed);
+ target_->prerequisites_.reserve (prereq_size);
+ tgs.push_back (*target_);
+ }
+
+ return tgs;
+ }
+
+ bool parser::
+ parse_dependency (token& t, token_type& tt,
+ names&& tns, const location& tloc, // Target names.
+ adhoc_names&& ans, // Ad hoc target names.
+ names&& pns, const location& ploc, // Prereq names.
+ bool chain)
+ {
+ // Parse a dependency chain and/or a target/prerequisite-specific variable
+ // assignment/block. Return true if the following block (if any) has been
+ // "claimed" (the block "belongs" to targets/prerequisites before the last
+ // colon).
+ //
+ // enter: colon (anything else is not handled)
+ // leave: - first token on the next line if returning true
+ // - newline (presumably, must be verified) if returning false
+ //
+ // Note that top-level call (with chain == false) is expected to always
+ // return true.
+ //
+ // This dual-return "complication" is necessary to handle non-block cases
+ // like this:
+ //
+ // foo: bar
+ // {hxx ixx}: install = true
+ //
+ tracer trace ("parser::parse_dependency", &path_);
+
+ // First enter all the targets.
+ //
+ small_vector<reference_wrapper<target>, 1> tgs (
+ enter_targets (move (tns), tloc, move (ans), pns.size ()));
+
+ // Now enter each prerequisite into each target.
+ //
+ for (name& pn: pns)
+ {
+ // We cannot reuse the names if we (potentially) may need to pass them
+ // as targets in case of a chain (see below).
+ //
+ name n (tt != type::colon ? move (pn) : pn);
+
+ auto rp (scope_->find_target_type (n, ploc));
+ const target_type* tt (rp.first);
+ optional<string>& e (rp.second);
+
+ if (tt == nullptr)
+ fail (ploc) << "unknown target type " << n.type;
+
+ // Current dir collapses to an empty one.
+ //
+ if (!n.dir.empty ())
+ n.dir.normalize (false, true);
+
+ // @@ OUT: for now we assume the prerequisite's out is undetermined. The
+ // only way to specify an src prerequisite will be with the explicit
+ // @-syntax.
+ //
+ // Perhaps use @file{foo} as a way to specify it is in the out tree,
+ // e.g., to suppress any src searches? The issue is what to use for such
+ // a special indicator. Also, one can easily and natually suppress any
+ // searches by specifying the absolute path.
+ //
+ prerequisite p (move (n.proj),
+ *tt,
+ move (n.dir),
+ dir_path (),
+ move (n.value),
+ move (e),
+ *scope_);
+
+ for (auto i (tgs.begin ()), e (tgs.end ()); i != e; )
+ {
+ // Move last prerequisite (which will normally be the only one).
+ //
+ target& t (*i);
+ t.prerequisites_.push_back (++i == e
+ ? move (p)
+ : prerequisite (p, memory_order_relaxed));
+ }
+ }
+
+ // Call the specified parsing function (either variable or block) for each
+ // target in tgs (for_each_t) or for the last pns.size() prerequisites of
+ // each target (for_each_p).
+ //
+ // We handle multiple targets and/or prerequisites by replaying the tokens
+ // (see the target-specific case for details). The function signature is:
+ //
+ // void (token& t, type& tt)
+ //
+ auto for_each_t = [this, &t, &tt, &tgs] (auto&& f)
+ {
+ replay_guard rg (*this, tgs.size () > 1);
+
+ for (auto ti (tgs.begin ()), te (tgs.end ()); ti != te; )
+ {
+ target& tg (*ti);
+ enter_target tgg (*this, tg);
+
+ f (t, tt);
+
+ if (++ti != te)
+ rg.play (); // Replay.
+ }
+ };
+
+ auto for_each_p = [this, &t, &tt, &tgs, &pns] (auto&& f)
+ {
+ replay_guard rg (*this, tgs.size () > 1 || pns.size () > 1);
+
+ for (auto ti (tgs.begin ()), te (tgs.end ()); ti != te; )
+ {
+ target& tg (*ti);
+ enter_target tgg (*this, tg);
+
+ for (size_t pn (tg.prerequisites_.size ()), pi (pn - pns.size ());
+ pi != pn; )
+ {
+ enter_prerequisite pg (*this, tg.prerequisites_[pi]);
+
+ f (t, tt);
+
+ if (++pi != pn)
+ rg.play (); // Replay.
+ }
+
+ if (++ti != te)
+ rg.play (); // Replay.
+ }
+ };
+
+ // Do we have a dependency chain and/or prerequisite-specific variable
+ // assignment? If not, check for the target-specific variable block unless
+ // this is a chained call (in which case the block, if any, "belongs" to
+ // prerequisites).
+ //
+ if (tt != type::colon)
+ {
+ if (chain)
+ return false;
+
+ next_after_newline (t, tt); // Must be a newline then.
+
+ if (tt == type::lcbrace && peek () == type::newline)
+ {
+ next (t, tt); // Newline.
+
+ // Parse the block for each target.
+ //
+ for_each_t ([this] (token& t, token_type& tt)
+ {
+ next (t, tt); // First token inside the block.
+
+ parse_variable_block (t, tt, nullptr, string ());
+
+ if (tt != type::rcbrace)
+ fail (t) << "expected '}' instead of " << t;
+ });
+
+ next (t, tt); // Presumably newline after '}'.
+ next_after_newline (t, tt, '}'); // Should be on its own line.
+ }
+
+ return true; // Claimed or isn't any.
+ }
+
+ // What should we do if there are no prerequisites (for example, because
+ // of an empty wildcard result)? We can fail or we can ignore. In most
+ // cases, however, this is probably an error (for example, forgetting to
+ // checkout a git submodule) so let's not confuse the user and fail (one
+ // can always handle the optional prerequisites case with a variable and
+ // an if).
+ //
+ if (pns.empty ())
+ fail (ploc) << "no prerequisites in dependency chain or prerequisite-"
+ << "specific variable assignment";
+
+ next (t, tt);
+ auto at (attributes_push (t, tt));
+
+ // @@ PAT: currently we pattern-expand prerequisite-specific vars.
+ //
+ const location loc (get_location (t));
+ names ns (tt != type::newline && tt != type::eos
+ ? parse_names (t, tt, pattern_mode::expand)
+ : names ());
+
+ // Prerequisite-specific variable assignment.
+ //
+ if (tt == type::assign || tt == type::prepend || tt == type::append)
+ {
+ type at (tt);
+
+ const variable& var (parse_variable_name (move (ns), loc));
+ apply_variable_attributes (var);
+
+ // Parse the assignment for each prerequisites of each target.
+ //
+ for_each_p ([this, &var, at] (token& t, token_type& tt)
+ {
+ parse_variable (t, tt, var, at);
+ });
+
+ // Pretend that we have claimed the block to cause an error if there is
+ // one. Failed that, the following would result in a valid (target-
+ // specific) block:
+ //
+ // foo: bar: x = y
+ // {
+ // ...
+ // }
+ //
+ next_after_newline (t, tt);
+ return true;
+ }
+ //
+ // Dependency chain.
+ //
+ else
+ {
+ if (at.first)
+ fail (at.second) << "attributes before prerequisites";
+ else
+ attributes_pop ();
+
+ // Note that we could have "pre-resolved" these prerequisites to actual
+ // targets or, at least, made their directories absolute. We don't do it
+ // for ease of documentation: with the current semantics we can just say
+ // that the dependency chain is equivalent to specifying each dependency
+ // separately.
+ //
+ // Also note that supporting ad hoc target group specification in chains
+ // will be complicated. For example, what if prerequisites that have ad
+ // hoc targets don't end up being chained? Do we just silently drop
+ // them? Also, these are prerequsites first that happened to be reused
+ // as target names so perhaps it is the right thing not to support,
+ // conceptually.
+ //
+ if (parse_dependency (t, tt,
+ names (pns), ploc, // Note: can't move.
+ {} /* ad hoc target name */,
+ move (ns), loc,
+ true /* chain */))
+ return true;
+
+ // Claim the block (if any) for these prerequisites if it hasn't been
+ // claimed by the inner ones.
+ //
+ next_after_newline (t, tt); // Must be a newline.
+
+ if (tt == type::lcbrace && peek () == type::newline)
+ {
+ next (t, tt); // Newline.
+
+ // Parse the block for each prerequisites of each target.
+ //
+ for_each_p ([this] (token& t, token_type& tt)
+ {
+ next (t, tt); // First token inside the block.
+
+ parse_variable_block (t, tt, nullptr, string ());
+
+ if (tt != type::rcbrace)
+ fail (t) << "expected '}' instead of " << t;
+ });
+
+ next (t, tt); // Presumably newline after '}'.
+ next_after_newline (t, tt, '}'); // Should be on its own line.
+ }
+
+ return true; // Claimed or isn't any.
+ }
+ }
+
+ void parser::
+ source (istream& is,
+ const path& p,
+ const location& loc,
+ bool enter,
+ bool deft)
+ {
+ tracer trace ("parser::source", &path_);
+
+ l5 ([&]{trace (loc) << "entering " << p;});
+
+ if (enter)
+ enter_buildfile (p);
+
+ const path* op (path_);
+ path_ = &p;
+
+ lexer l (is, *path_);
+ lexer* ol (lexer_);
+ lexer_ = &l;
+
+ target* odt;
+ if (deft)
+ {
+ odt = default_target_;
+ default_target_ = nullptr;
+ }
+
+ token t;
+ type tt;
+ next (t, tt);
+ parse_clause (t, tt);
+
+ if (tt != type::eos)
+ fail (t) << "unexpected " << t;
+
+ if (deft)
+ {
+ process_default_target (t);
+ default_target_ = odt;
+ }
+
+ lexer_ = ol;
+ path_ = op;
+
+ l5 ([&]{trace (loc) << "leaving " << p;});
+ }
+
+ void parser::
+ parse_source (token& t, type& tt)
+ {
+ // The rest should be a list of buildfiles. Parse them as names in the
+ // value mode to get variable expansion and directory prefixes.
+ //
+ mode (lexer_mode::value, '@');
+ next (t, tt);
+ const location l (get_location (t));
+ names ns (tt != type::newline && tt != type::eos
+ ? parse_names (t, tt,
+ pattern_mode::expand,
+ false,
+ "path",
+ nullptr)
+ : names ());
+
+ for (name& n: ns)
+ {
+ if (n.pair || n.qualified () || n.typed () || 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 (scope_->src_path_ != nullptr && p.relative ())
+ p = scope_->src_path () / p;
+
+ p.normalize ();
+
+ try
+ {
+ ifdstream ifs (p);
+ source (ifs,
+ p,
+ get_location (t),
+ true /* enter */,
+ false /* default_target */);
+ }
+ catch (const io_error& e)
+ {
+ fail (l) << "unable to read buildfile " << p << ": " << e;
+ }
+ }
+
+ next_after_newline (t, tt);
+ }
+
+ void parser::
+ parse_include (token& t, type& tt)
+ {
+ tracer trace ("parser::parse_include", &path_);
+
+ if (root_->src_path_ == nullptr)
+ fail (t) << "inclusion during bootstrap";
+
+ // The rest should be a list of buildfiles. Parse them as names in the
+ // value mode to get variable expansion and directory prefixes.
+ //
+ mode (lexer_mode::value, '@');
+ next (t, tt);
+ const location l (get_location (t));
+ names ns (tt != type::newline && tt != type::eos
+ ? parse_names (t, tt,
+ pattern_mode::expand,
+ false,
+ "path",
+ nullptr)
+ : names ());
+
+ for (name& n: ns)
+ {
+ if (n.pair || n.qualified () || n.typed () || 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));
+
+ bool a;
+ if (n.value.empty ())
+ a = true;
+ else
+ {
+ a = path::traits_type::is_separator (n.value.back ());
+ p /= path (move (n.value));
+ }
+
+ if (a)
+ {
+ // This shouldn't happen but let's make sure.
+ //
+ if (root_->root_extra == nullptr)
+ fail (l) << "buildfile naming scheme is not yet known";
+
+ p /= root_->root_extra->buildfile_file;
+ }
+
+ l6 ([&]{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_);
+ const dir_path* opb (pbase_);
+ switch_scope (out_base);
+
+ if (root_ == nullptr)
+ fail (l) << "out of project include from " << 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 ();
+
+ l6 ([&]{trace (l) << "absolute path " << p;});
+
+ if (!root_->buildfiles.insert (p).second) // Note: may be "new" root.
+ {
+ l5 ([&]{trace (l) << "skipping already included " << p;});
+ pbase_ = opb;
+ scope_ = ocs;
+ root_ = ors;
+ continue;
+ }
+
+ try
+ {
+ ifdstream ifs (p);
+ source (ifs,
+ p,
+ get_location (t),
+ true /* enter */,
+ true /* default_target */);
+ }
+ catch (const io_error& e)
+ {
+ fail (l) << "unable to read buildfile " << p << ": " << e;
+ }
+
+ pbase_ = opb;
+ scope_ = ocs;
+ root_ = ors;
+ }
+
+ next_after_newline (t, tt);
+ }
+
+ void parser::
+ parse_run (token& t, type& tt)
+ {
+ // run <name> [<arg>...]
+ //
+
+ // Parse the command line as names in the value mode to get variable
+ // expansion, etc.
+ //
+ mode (lexer_mode::value);
+ next (t, tt);
+ const location l (get_location (t));
+
+ strings args;
+ try
+ {
+ args = convert<strings> (tt != type::newline && tt != type::eos
+ ? parse_names (t, tt,
+ pattern_mode::ignore,
+ false,
+ "argument",
+ nullptr)
+ : names ());
+ }
+ catch (const invalid_argument& e)
+ {
+ fail (l) << "invalid run argument: " << e.what ();
+ }
+
+ if (args.empty () || args[0].empty ())
+ fail (l) << "expected executable name after run";
+
+ cstrings cargs;
+ cargs.reserve (args.size () + 1);
+ transform (args.begin (),
+ args.end (),
+ back_inserter (cargs),
+ [] (const string& s) {return s.c_str ();});
+ cargs.push_back (nullptr);
+
+ process pr (run_start (3 /* verbosity */,
+ cargs,
+ 0 /* stdin */,
+ -1 /* stdout */,
+ true /* error */,
+ empty_dir_path /* cwd */,
+ l));
+ bool bad (false);
+ try
+ {
+ // While a failing process could write garbage to stdout, for simplicity
+ // let's assume it is well behaved.
+ //
+ ifdstream is (move (pr.in_ofd), fdstream_mode::skip);
+
+ // If there is an error in the output, our diagnostics will look like
+ // this:
+ //
+ // <stdout>:2:3 error: unterminated single quote
+ // buildfile:3:4 info: while parsing foo output
+ //
+ {
+ auto df = make_diag_frame (
+ [&args, &l](const diag_record& dr)
+ {
+ dr << info (l) << "while parsing " << args[0] << " output";
+ });
+
+ source (is,
+ path ("<stdout>"),
+ l,
+ false /* enter */,
+ false /* default_target */);
+ }
+
+ is.close (); // Detect errors.
+ }
+ catch (const io_error&)
+ {
+ // Presumably the child process failed and issued diagnostics so let
+ // run_finish() try to deal with that first.
+ //
+ bad = true;
+ }
+
+ run_finish (cargs, pr, l);
+
+ if (bad)
+ fail (l) << "error reading " << args[0] << " output";
+
+ next_after_newline (t, tt);
+ }
+
+ void parser::
+ parse_import (token& t, type& tt)
+ {
+ tracer trace ("parser::parse_import", &path_);
+
+ if (root_->src_path_ == nullptr)
+ fail (t) << "import during bootstrap";
+
+ // General import format:
+ //
+ // import [<var>=](<project>|<project>/<target>])+
+ //
+ type atype; // Assignment type.
+ value* val (nullptr);
+ const build2::variable* var (nullptr);
+
+ // We are now in the normal lexing mode and here is the problem: we need
+ // to switch to the value mode so that we don't treat certain characters
+ // as separators (e.g., + in 'libstdc++'). But at the same time we need
+ // to detect if we have the <var>= part. So what we are going to do is
+ // switch to the value mode, get the first token, and then re-parse it
+ // manually looking for =/=+/+=.
+ //
+ mode (lexer_mode::value, '@');
+ next (t, tt);
+
+ // Get variable attributes, if any (note that here we will go into a
+ // nested value mode with a different pair character).
+ //
+ auto at (attributes_push (t, tt));
+
+ const location vloc (get_location (t));
+
+ if (tt == type::word)
+ {
+ // Split the token into the variable name and value at position (p) of
+ // '=', taking into account leading/trailing '+'. The variable name is
+ // returned while the token is set to value. If the resulting token
+ // value is empty, get the next token. Also set assignment type (at).
+ //
+ auto split = [&atype, &t, &tt, this] (size_t p) -> string
+ {
+ string& v (t.value);
+ size_t e;
+
+ if (p != 0 && v[p - 1] == '+') // +=
+ {
+ e = p--;
+ atype = type::append;
+ }
+ else if (p + 1 != v.size () && v[p + 1] == '+') // =+
+ {
+ e = p + 1;
+ atype = type::prepend;
+ }
+ else // =
+ {
+ e = p;
+ atype = type::assign;
+ }
+
+ string nv (v, e + 1); // value
+ v.resize (p); // var name
+ v.swap (nv);
+
+ if (v.empty ())
+ next (t, tt);
+
+ return nv;
+ };
+
+ // Is this the 'foo=...' case?
+ //
+ size_t p (t.value.find ('='));
+ auto& vp (var_pool.rw (*scope_));
+
+ if (p != string::npos)
+ var = &vp.insert (split (p), true /* overridable */);
+ //
+ // This could still be the 'foo =...' case.
+ //
+ else if (peek () == type::word)
+ {
+ const string& v (peeked ().value);
+ size_t n (v.size ());
+
+ // We should start with =/+=/=+.
+ //
+ if (n > 0 &&
+ (v[p = 0] == '=' ||
+ (n > 1 && v[0] == '+' && v[p = 1] == '=')))
+ {
+ var = &vp.insert (move (t.value), true /* overridable */);
+ next (t, tt); // Get the peeked token.
+ split (p); // Returned name should be empty.
+ }
+ }
+ }
+
+ if (var != nullptr)
+ {
+ apply_variable_attributes (*var);
+
+ if (var->visibility >= variable_visibility::target)
+ {
+ fail (vloc) << "variable " << *var << " has " << var->visibility
+ << " visibility but is assigned in import";
+ }
+
+ val = atype == type::assign
+ ? &scope_->assign (*var)
+ : &scope_->append (*var);
+ }
+ else
+ {
+ if (at.first)
+ fail (at.second) << "attributes without variable";
+ else
+ attributes_pop ();
+ }
+
+ // The rest should be a list of projects and/or targets. Parse them as
+ // names to get variable expansion and directory prefixes. Note: doesn't
+ // make sense to expand patterns (what's the base directory?)
+ //
+ const location l (get_location (t));
+ names ns (tt != type::newline && tt != type::eos
+ ? parse_names (t, tt, pattern_mode::ignore)
+ : names ());
+
+ for (name& n: ns)
+ {
+ if (n.pair)
+ fail (l) << "unexpected pair in import";
+
+ // build2::import() will check the name, if required.
+ //
+ names r (build2::import (*scope_, move (n), l));
+
+ if (val != nullptr)
+ {
+ if (atype == type::assign)
+ {
+ val->assign (move (r), var);
+ atype = type::append; // Append subsequent values.
+ }
+ else if (atype == type::prepend)
+ {
+ // Note: multiple values will be prepended in reverse.
+ //
+ val->prepend (move (r), var);
+ }
+ else
+ val->append (move (r), var);
+ }
+ }
+
+ next_after_newline (t, tt);
+ }
+
+ void parser::
+ parse_export (token& t, type& tt)
+ {
+ tracer trace ("parser::parse_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 a variable value to get expansion,
+ // attributes, etc. build2::import() will check the names, if required.
+ //
+ location l (get_location (t));
+ value rhs (parse_variable_value (t, tt));
+
+ // While it may seem like supporting attributes is a good idea here,
+ // there is actually little benefit in being able to type them or to
+ // return NULL.
+ //
+ // export_value_ = value (); // Reset to untyped NULL value.
+ // value_attributes (nullptr,
+ // export_value_,
+ // move (rhs),
+ // type::assign);
+ if (attributes& a = attributes_top ())
+ fail (a.loc) << "attributes in export";
+ else
+ attributes_pop ();
+
+ if (!rhs)
+ fail (l) << "null value in export";
+
+ if (rhs.type != nullptr)
+ untypify (rhs);
+
+ export_value_ = move (rhs).as<names> ();
+
+ if (export_value_.empty ())
+ fail (l) << "empty value in export";
+
+ next_after_newline (t, tt);
+ }
+
+ void parser::
+ parse_using (token& t, type& tt)
+ {
+ tracer trace ("parser::parse_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 in the
+ // value mode to get variable expansion, etc.
+ //
+ mode (lexer_mode::value, '@');
+ next (t, tt);
+ const location l (get_location (t));
+ names ns (tt != type::newline && tt != type::eos
+ ? parse_names (t, tt,
+ pattern_mode::ignore,
+ false,
+ "module",
+ nullptr)
+ : names ());
+
+ for (auto i (ns.begin ()); i != ns.end (); ++i)
+ {
+ string n;
+ standard_version v;
+
+ if (!i->simple ())
+ fail (l) << "expected module name instead of " << *i;
+
+ n = move (i->value);
+
+ if (i->pair)
+ try
+ {
+ if (i->pair != '@')
+ fail (l) << "unexpected pair style in using directive";
+
+ ++i;
+ if (!i->simple ())
+ fail (l) << "expected module version instead of " << *i;
+
+ v = standard_version (i->value, standard_version::allow_earliest);
+ }
+ catch (const invalid_argument& e)
+ {
+ fail (l) << "invalid module version '" << i->value << "': " << e;
+ }
+
+ // Handle the special 'build' module.
+ //
+ if (n == "build")
+ {
+ standard_version_constraint c (move (v), false, nullopt, true); // >=
+
+ if (!v.empty ())
+ check_build_version (c, l);
+ }
+ else
+ {
+ assert (v.empty ()); // Module versioning not yet implemented.
+
+ if (boot_)
+ boot_module (*root_, n, l);
+ else
+ load_module (*root_, *scope_, n, l, optional);
+ }
+ }
+
+ next_after_newline (t, tt);
+ }
+
+ void parser::
+ parse_define (token& t, type& tt)
+ {
+ // define <derived>: <base>
+ //
+ // See tests/define.
+ //
+ if (next (t, tt) != type::word)
+ fail (t) << "expected name instead of " << t << " in target type "
+ << "definition";
+
+ string dn (move (t.value));
+ const location dnl (get_location (t));
+
+ if (next (t, tt) != type::colon)
+ fail (t) << "expected ':' instead of " << t << " in target type "
+ << "definition";
+
+ next (t, tt);
+
+ if (tt == type::word)
+ {
+ // Target.
+ //
+ const string& bn (t.value);
+ const target_type* bt (scope_->find_target_type (bn));
+
+ if (bt == nullptr)
+ fail (t) << "unknown target type " << bn;
+
+ if (!scope_->derive_target_type (move (dn), *bt).second)
+ fail (dnl) << "target type " << dn << " already define in this scope";
+
+ next (t, tt); // Get newline.
+ }
+ else
+ fail (t) << "expected name instead of " << t << " in target type "
+ << "definition";
+
+ next_after_newline (t, tt);
+ }
+
+ void parser::
+ parse_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. Note
+ // that we also expand patterns (could be used in nested contexts,
+ // etc; e.g., "if pattern expansion is empty" condition).
+ //
+ const location l (get_location (t));
+
+ try
+ {
+ // Should evaluate to 'true' or 'false'.
+ //
+ bool e (
+ convert<bool> (
+ parse_value (t, tt,
+ pattern_mode::expand,
+ "expression",
+ nullptr)));
+
+ take = (k.back () == '!' ? !e : e);
+ }
+ catch (const invalid_argument& e) { fail (l) << e; }
+ }
+ }
+ else
+ take = !taken;
+
+ if (tt != type::newline)
+ fail (t) << "expected newline instead of " << t << " after " << k
+ << (k != "else" ? "-expression" : "");
+
+ // This can be a block or a single line. The block part is a bit
+ // tricky, consider:
+ //
+ // else
+ // {hxx cxx}{options}: install = false
+ //
+ // So we treat it as a block if it's followed immediately by newline.
+ //
+ if (next (t, tt) == type::lcbrace && peek () == type::newline)
+ {
+ next (t, tt); // Get newline.
+ next (t, tt);
+
+ if (take)
+ {
+ parse_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); // Presumably newline after '}'.
+ next_after_newline (t, tt, '}'); // Should be on its own line.
+ }
+ else
+ {
+ if (take)
+ {
+ if (!parse_clause (t, tt, true))
+ fail (t) << "expected " << k << "-line instead of " << t;
+
+ taken = true;
+ }
+ else
+ {
+ skip_line (t, tt);
+
+ if (tt == type::newline)
+ next (t, tt);
+ }
+ }
+
+ // See if we have another el* keyword.
+ //
+ if (k != "else" && tt == type::word && keyword (t))
+ {
+ const string& n (t.value);
+
+ if (n == "else" || n == "elif" || n == "elif!")
+ continue;
+ }
+
+ break;
+ }
+ }
+
+ void parser::
+ parse_for (token& t, type& tt)
+ {
+ // for <varname>: <value>
+ // <line>
+ //
+ // for <varname>: <value>
+ // {
+ // <block>
+ // }
+ //
+
+ // First take care of the variable name. There is no reason not to
+ // support variable attributes.
+ //
+ next (t, tt);
+ attributes_push (t, tt);
+
+ // @@ PAT: currently we pattern-expand for var.
+ //
+ const location vloc (get_location (t));
+ names vns (parse_names (t, tt, pattern_mode::expand));
+
+ if (tt != type::colon)
+ fail (t) << "expected ':' instead of " << t << " after variable name";
+
+ const variable& var (parse_variable_name (move (vns), vloc));
+ apply_variable_attributes (var);
+
+ if (var.visibility >= variable_visibility::target)
+ {
+ fail (vloc) << "variable " << var << " has " << var.visibility
+ << " visibility but is assigned in for-loop";
+ }
+
+ // Now the value (list of names) to iterate over. Parse it as a variable
+ // value to get expansion, attributes, etc.
+ //
+ value val;
+ apply_value_attributes (
+ nullptr, val, parse_variable_value (t, tt), type::assign);
+
+ // If this value is a vector, then save its element type so that we
+ // can typify each element below.
+ //
+ const value_type* etype (nullptr);
+
+ if (val && val.type != nullptr)
+ {
+ etype = val.type->element_type;
+ untypify (val);
+ }
+
+ if (tt != type::newline)
+ fail (t) << "expected newline instead of " << t << " after for";
+
+ // Finally the body. The initial thought was to use the token replay
+ // facility but on closer inspection this didn't turn out to be a good
+ // idea (no support for nested replays, etc). So instead we are going to
+ // do a full-blown re-lex. Specifically, we will first skip the line/block
+ // just as we do for non-taken if/else branches while saving the character
+ // sequence that comprises the body. Then we re-lex/parse it on each
+ // iteration.
+ //
+ string body;
+ uint64_t line (lexer_->line); // Line of the first character to be saved.
+ lexer::save_guard sg (*lexer_, body);
+
+ // This can be a block or a single line, similar to if-else.
+ //
+ bool block (next (t, tt) == type::lcbrace && peek () == type::newline);
+
+ if (block)
+ {
+ next (t, tt); // Get newline.
+ next (t, tt);
+
+ skip_block (t, tt);
+ sg.stop ();
+
+ if (tt != type::rcbrace)
+ fail (t) << "expected '}' instead of " << t << " at the end of "
+ << "for-block";
+
+ next (t, tt); // Presumably newline after '}'.
+ next_after_newline (t, tt, '}'); // Should be on its own line.
+ }
+ else
+ {
+ skip_line (t, tt);
+ sg.stop ();
+
+ if (tt == type::newline)
+ next (t, tt);
+ }
+
+ // Iterate.
+ //
+ names& ns (val.as<names> ());
+
+ if (ns.empty ())
+ return;
+
+ value& v (scope_->assign (var));
+
+ istringstream is (move (body));
+
+ for (auto i (ns.begin ()), e (ns.end ());; )
+ {
+ // Set the variable value.
+ //
+ bool pair (i->pair);
+ names n;
+ n.push_back (move (*i));
+ if (pair) n.push_back (move (*++i));
+ v = value (move (n));
+
+ if (etype != nullptr)
+ typify (v, *etype, &var);
+
+ lexer l (is, *path_, line);
+ lexer* ol (lexer_);
+ lexer_ = &l;
+
+ token t;
+ type tt;
+ next (t, tt);
+
+ if (block)
+ {
+ next (t, tt); // {
+ next (t, tt); // <newline>
+ }
+ parse_clause (t, tt);
+ assert (tt == (block ? type::rcbrace : type::eos));
+
+ lexer_ = ol;
+
+ if (++i == e)
+ break;
+
+ // Rewind the stream.
+ //
+ is.clear ();
+ is.seekg (0);
+ }
+ }
+
+ void parser::
+ parse_assert (token& t, type& tt)
+ {
+ bool neg (t.value.back () == '!');
+ const location al (get_location (t));
+
+ // Parse the next chunk as names to get variable expansion, evaluation,
+ // etc. Do it in the value mode so that we don't treat ':', etc., as
+ // special.
+ //
+ mode (lexer_mode::value);
+ next (t, tt);
+
+ const location el (get_location (t));
+
+ try
+ {
+ // Should evaluate to 'true' or 'false'.
+ //
+ bool e (
+ convert<bool> (
+ parse_value (t, tt,
+ pattern_mode::expand,
+ "expression",
+ nullptr,
+ true)));
+ e = (neg ? !e : e);
+
+ if (e)
+ {
+ skip_line (t, tt);
+
+ if (tt != type::eos)
+ next (t, tt); // Swallow newline.
+
+ return;
+ }
+ }
+ catch (const invalid_argument& e) { fail (el) << e; }
+
+ // Being here means things didn't end up well. Parse the description, if
+ // any, with expansion. Then fail.
+ //
+ names ns (tt != type::newline && tt != type::eos
+ ? parse_names (t, tt,
+ pattern_mode::ignore,
+ false,
+ "description",
+ nullptr)
+ : names ());
+
+ diag_record dr (fail (al));
+
+ if (ns.empty ())
+ dr << "assertion failed";
+ else
+ dr << ns;
+ }
+
+ void parser::
+ parse_print (token& t, type& tt)
+ {
+ // Parse the rest as a variable value to get expansion, attributes, etc.
+ //
+ value rhs (parse_variable_value (t, tt));
+
+ value lhs;
+ apply_value_attributes (nullptr, lhs, move (rhs), type::assign);
+
+ if (lhs)
+ {
+ names storage;
+ cout << reverse (lhs, storage) << endl;
+ }
+ else
+ cout