aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2018-06-28 09:44:15 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2018-06-28 09:44:15 +0200
commit3cc5e3bd441fc9d18fece3d9e99fae75c78438e7 (patch)
treea9a08c453370847e0d352d47e19fbfcb7cc757ef
parentc0f72d47fc25981dcc1f55e12dfa0fdba7b70242 (diff)
Implement support for excluded and ad hoc prerequisites
The inclusion/exclusion is controlled via the 'include' prerequisite-specific variable. Valid values are: false - exclude true - include adhoc - include but treat as an ad hoc input For example: lib{foo}: cxx{win32-utility}: include = ($cxx.targe.class == 'windows') exe{bar}: libs{plugin}: include = adhoc
-rw-r--r--build2/action.hxx200
-rw-r--r--build2/algorithm.cxx38
-rw-r--r--build2/algorithm.hxx15
-rw-r--r--build2/cc/common.cxx5
-rw-r--r--build2/cc/compile-rule.cxx48
-rw-r--r--build2/cc/install-rule.cxx2
-rw-r--r--build2/cc/link-rule.cxx50
-rw-r--r--build2/cc/windows-rpath.cxx4
-rw-r--r--build2/cli/rule.cxx77
-rw-r--r--build2/config/operation.cxx4
-rw-r--r--build2/context.cxx3
-rw-r--r--build2/context.hxx25
-rw-r--r--build2/dist/operation.cxx25
-rw-r--r--build2/dist/rule.cxx5
-rw-r--r--build2/dist/rule.hxx4
-rw-r--r--build2/install/rule.cxx20
-rw-r--r--build2/install/rule.hxx4
-rw-r--r--build2/operation.cxx9
-rw-r--r--build2/operation.hxx199
-rw-r--r--build2/prerequisite.cxx25
-rw-r--r--build2/prerequisite.hxx35
-rw-r--r--build2/prerequisite.ixx31
-rw-r--r--build2/rule-map.hxx2
-rw-r--r--build2/rule.hxx2
-rw-r--r--build2/target.hxx16
-rw-r--r--build2/test/rule.cxx6
-rw-r--r--build2/test/rule.hxx2
-rw-r--r--build2/variable.hxx7
-rw-r--r--build2/variable.ixx14
-rw-r--r--build2/version/rule.cxx3
-rw-r--r--old-tests/cli/simple/build/bootstrap.build3
31 files changed, 600 insertions, 283 deletions
diff --git a/build2/action.hxx b/build2/action.hxx
new file mode 100644
index 0000000..8c9b7e5
--- /dev/null
+++ b/build2/action.hxx
@@ -0,0 +1,200 @@
+// file : build2/action.hxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2018 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_ACTION_HXX
+#define BUILD2_ACTION_HXX
+
+#include <build2/types.hxx>
+#include <build2/utility.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;
+
+ ostream&
+ operator<< (ostream&, action); // operation.cxx
+
+ // Inner/outer operation state container.
+ //
+ template <typename T>
+ struct action_state
+ {
+ T states[2]; // [0] -- inner, [1] -- outer.
+
+ T& operator[] (action a) {return states[a.inner () ? 0 : 1];}
+ const T& operator[] (action a) const {return states[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 // BUILD2_ACTION_HXX
diff --git a/build2/algorithm.cxx b/build2/algorithm.cxx
index 7e68a36..5dc3637 100644
--- a/build2/algorithm.cxx
+++ b/build2/algorithm.cxx
@@ -668,13 +668,20 @@ namespace build2
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;
+
const target& pt (search (t, p));
if (s != nullptr && !pt.in (*s))
continue;
match_async (a, pt, target::count_busy (), t[a].task_count);
- pts.push_back (&pt);
+ pts.push_back (prerequisite_target (&pt, pi));
}
wg.wait ();
@@ -737,11 +744,12 @@ namespace build2
// Instantiate only for what we need.
//
template void
- match_members<const target*> (action, target&, const target* const*, size_t);
+ match_members<const target*> (action, target&,
+ const target* const*, size_t);
template void
- match_members<prerequisite_target> (
- action, target&, prerequisite_target const*, size_t);
+ match_members<prerequisite_target> (action, target&,
+ prerequisite_target const*, size_t);
const fsdir*
inject_fsdir (action a, target& t, bool parent)
@@ -1567,6 +1575,21 @@ namespace build2
return t.executed_state (a);
}
+ static inline bool
+ adhoc_member (const target*&)
+ {
+ return false;
+ }
+
+ static inline bool
+ adhoc_member (prerequisite_target& pt)
+ {
+ if (pt.adhoc)
+ pt.target = nullptr;
+
+ return pt.adhoc;
+ }
+
template <typename T>
target_state
straight_execute_members (action a, atomic_count& tc,
@@ -1615,6 +1638,8 @@ namespace build2
sched.wait (target::count_executed (), tc, scheduler::work_none);
r |= mt.executed_state (a);
+
+ adhoc_member (ts[i]);
}
return r;
@@ -1662,6 +1687,8 @@ namespace build2
sched.wait (target::count_executed (), tc, scheduler::work_none);
r |= mt.executed_state (a);
+
+ adhoc_member (ts[i]);
}
return r;
@@ -1766,6 +1793,9 @@ namespace build2
}
}
+ if (adhoc_member (pts[i]))
+ continue;
+
if (rt == nullptr && pt.is_a (*tt))
rt = &pt;
}
diff --git a/build2/algorithm.hxx b/build2/algorithm.hxx
index 6e261da..48f48c8 100644
--- a/build2/algorithm.hxx
+++ b/build2/algorithm.hxx
@@ -8,8 +8,8 @@
#include <build2/types.hxx>
#include <build2/utility.hxx>
+#include <build2/action.hxx>
#include <build2/target.hxx>
-#include <build2/operation.hxx>
namespace build2
{
@@ -442,10 +442,10 @@ namespace build2
// 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, then set its pointer in prerequisite_targets to
- // NULL (since its state cannot be queried MT-safely). If count is not 0,
- // then only the first count prerequisites are executed beginning from
- // start.
+ // 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.
//
target_state
straight_execute_prerequisites (action, const target&,
@@ -483,7 +483,7 @@ namespace build2
// the first count prerequisites are executed.
//
// Note that the return value is an optional target state. If the target
- // needs updating, then the value absent. Otherwise it is the state that
+ // 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
@@ -535,7 +535,8 @@ namespace build2
// 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.
+ // T can only be const target* or prerequisite_target. If it is the latter,
+ // the ad hoc semantics described in execute_prerequsites() is in effect.
//
template <typename T>
target_state
diff --git a/build2/cc/common.cxx b/build2/cc/common.cxx
index 0778acf..3c4994e 100644
--- a/build2/cc/common.cxx
+++ b/build2/cc/common.cxx
@@ -224,7 +224,10 @@ namespace build2
{
for (const prerequisite_target& pt: l.prerequisite_targets[a])
{
- if (pt == nullptr)
+ // Note: adhoc prerequisites are not part of the library meta-
+ // information protocol.
+ //
+ if (pt == nullptr || pt.adhoc)
continue;
bool la;
diff --git a/build2/cc/compile-rule.cxx b/build2/cc/compile-rule.cxx
index 079be88..61d5984 100644
--- a/build2/cc/compile-rule.cxx
+++ b/build2/cc/compile-rule.cxx
@@ -237,6 +237,11 @@ namespace build2
//
for (prerequisite_member p: reverse_group_prerequisite_members (a, t))
{
+ // If excluded or ad hoc, then don't factor it into our tests.
+ //
+ if (include (a, t, p) != include_type::normal)
+ continue;
+
if (p.is_a (mod ? *x_mod : x_src))
{
// Save in the target's auxiliary storage. Translation type will
@@ -292,6 +297,9 @@ namespace build2
for (prerequisite_member p: group_prerequisite_members (a, t))
{
+ if (include (a, t, p) != include_type::normal) // Excluded/ad hoc.
+ continue;
+
// Should be already searched and matched for libraries.
//
if (const target* pt = p.load ())
@@ -342,6 +350,9 @@ namespace build2
for (prerequisite_member p: group_prerequisite_members (a, t))
{
+ if (include (a, t, p) != include_type::normal) // Excluded/ad hoc.
+ continue;
+
if (const target* pt = p.load ())
{
if (const libx* l = pt->is_a<libx> ())
@@ -393,6 +404,9 @@ namespace build2
for (prerequisite_member p: group_prerequisite_members (a, t))
{
+ if (include (a, t, p) != include_type::normal) // Excluded/ad hoc.
+ continue;
+
if (const target* pt = p.load ())
{
if (const libx* l = pt->is_a<libx> ())
@@ -599,15 +613,20 @@ namespace build2
for (prerequisite_member p: group_prerequisite_members (a, t))
{
const target* pt (nullptr);
+ include_type pi (include (a, t, p));
+
+ if (!pi)
+ continue;
// A dependency on a library is there so that we can get its
- // *.export.poptions, modules, etc. This is the "library
- // meta-information protocol". See also append_lib_options().
+ // *.export.poptions, modules, etc. This is the library
+ // meta-information protocol. See also append_lib_options().
//
- if (p.is_a<libx> () ||
- p.is_a<liba> () ||
- p.is_a<libs> () ||
- p.is_a<libux> ())
+ if (pi == include_type::normal &&
+ (p.is_a<libx> () ||
+ p.is_a<liba> () ||
+ p.is_a<libs> () ||
+ p.is_a<libux> ()))
{
if (a.operation () == update_id)
{
@@ -638,7 +657,8 @@ namespace build2
// else (normally library/executable) also depends on it and will
// clean it up.
//
- else if (p.is_a<bmi> () || p.is_a (tt.bmi))
+ else if (pi == include_type::normal &&
+ (p.is_a<bmi> () || p.is_a (tt.bmi)))
continue;
else
{
@@ -649,7 +669,7 @@ namespace build2
}
match_async (a, *pt, target::count_busy (), t[a].task_count);
- pts.push_back (pt);
+ pts.push_back (prerequisite_target (pt, pi));
}
wg.wait ();
@@ -3529,6 +3549,9 @@ namespace build2
for (prerequisite_member p: group_prerequisite_members (a, t))
{
+ if (include (a, t, p) != include_type::normal) // Excluded/ad hoc.
+ continue;
+
const target* pt (p.load ()); // Should be cached for libraries.
if (pt != nullptr)
@@ -3625,6 +3648,9 @@ namespace build2
for (prerequisite_member p:
prerequisite_members (a, t, group_prerequisites (*pt, pg)))
{
+ if (include (a, t, p) != include_type::normal) // Excluded/ad hoc.
+ continue;
+
if (p.is_a (*x_mod))
{
// Check for an explicit module name. Only look for an existing
@@ -3739,6 +3765,9 @@ namespace build2
//
for (prerequisite_member p: group_prerequisite_members (a, *bt))
{
+ if (include (a, t, p) != include_type::normal) // Excluded/ad hoc.
+ continue;
+
if (p.is_a (*x_mod)) // Got to be there.
{
fail (relative (src))
@@ -3961,6 +3990,9 @@ namespace build2
ps.push_back (prerequisite (lt));
for (prerequisite_member p: group_prerequisite_members (a, lt))
{
+ if (include (a, lt, p) != include_type::normal) // Excluded/ad hoc.
+ continue;
+
// @@ TODO: will probably need revision if using sidebuild for
// non-installed libraries (e.g., direct BMI dependencies
// will probably have to be translated to mxx{} or some such).
diff --git a/build2/cc/install-rule.cxx b/build2/cc/install-rule.cxx
index a7e05b0..ee290e3 100644
--- a/build2/cc/install-rule.cxx
+++ b/build2/cc/install-rule.cxx
@@ -41,6 +41,8 @@ namespace build2
// Note: for now we assume these prerequisites never come from see-
// through groups.
//
+ // Note: we install ad hoc prerequisites by default.
+ //
otype ot (link_type (t).type);
bool st (t.is_a<exe> () || t.is_a<libs> ()); // Target needs shared.
diff --git a/build2/cc/link-rule.cxx b/build2/cc/link-rule.cxx
index e4f176a..154cadb 100644
--- a/build2/cc/link-rule.cxx
+++ b/build2/cc/link-rule.cxx
@@ -77,6 +77,11 @@ namespace build2
for (prerequisite_member p: group_prerequisite_members (a, t))
{
+ // If excluded or ad hoc, then don't factor it into our tests.
+ //
+ if (include (a, t, p) != include_type::normal)
+ continue;
+
if (p.is_a (x_src) || (x_mod != nullptr && p.is_a (*x_mod)))
{
seen_x = seen_x || true;
@@ -562,12 +567,17 @@ namespace build2
for (prerequisite_member p: group_prerequisite_members (a, t))
{
+ include_type pi (include (a, t, p));
+
// We pre-allocate a NULL slot for each (potential; see clean)
// prerequisite target.
//
- pts.push_back (nullptr);
+ pts.push_back (prerequisite_target (nullptr, pi));
const target*& pt (pts.back ());
+ if (pi != include_type::normal) // Skip excluded and ad hoc.
+ continue;
+
// Mark:
// 0 - lib
// 1 - src
@@ -699,8 +709,8 @@ namespace build2
// for update. This allows operations like test and install to
// skip such tacked on stuff.
//
- // @@ This is broken since, for example, update for install will
- // ignore ad hoc inputs.
+ // Note that ad hoc inputs have to be explicitly marked with the
+ // include=adhoc prerequisite-specific variable.
//
if (current_outer_oif != nullptr)
continue;
@@ -730,9 +740,9 @@ namespace build2
// bmi{} targets we haven't completed yet. Hairy, I know.
//
- // Parallel prerequisite_targets loop.
+ // Parallel prerequisites/prerequisite_targets loop.
//
- size_t i (start), n (pts.size ());
+ size_t i (start);
for (prerequisite_member p: group_prerequisite_members (a, t))
{
const target*& pt (pts[i].target);
@@ -807,7 +817,7 @@ namespace build2
{
const target* pt (pts[j++]);
- if (pt == nullptr)
+ if (pt == nullptr) // Note: ad hoc is taken care of.
continue;
// NOTE: pt may be marked (even for a library -- see clean
@@ -945,14 +955,25 @@ namespace build2
//
wait_guard wg (target::count_busy (), t[a].task_count, true);
- for (i = start; i != n; ++i)
+ i = start;
+ for (prerequisite_member p: group_prerequisite_members (a, t))
{
- const target*& pt (pts[i]);
+ bool adhoc (pts[i].adhoc);
+ const target*& pt (pts[i++]);
+
+ uint8_t m;
if (pt == nullptr)
- continue;
+ {
+ // Handle ad hoc prerequisities.
+ //
+ if (!adhoc)
+ continue;
- if (uint8_t m = unmark (pt))
+ pt = &p.search (t);
+ m = 1; // Mark for completion.
+ }
+ else if ((m = unmark (pt)) != 0)
{
// If this is a library not to be cleaned, we can finally blank it
// out.
@@ -962,10 +983,10 @@ namespace build2
pt = nullptr;
continue;
}
-
- match_async (a, *pt, target::count_busy (), t[a].task_count);
- mark (pt, m);
}
+
+ match_async (a, *pt, target::count_busy (), t[a].task_count);
+ mark (pt, m);
}
wg.wait ();
@@ -1342,6 +1363,9 @@ namespace build2
// Update prerequisites. We determine if any relevant ones render us
// out-of-date manually below.
//
+ // Note that straight_execute_prerequisites() will blank out all the ad
+ // hoc prerequisites so we don't need to worry about them from now on.
+ //
bool update (false);
timestamp mt (t.load_mtime ());
target_state ts (straight_execute_prerequisites (a, t));
diff --git a/build2/cc/windows-rpath.cxx b/build2/cc/windows-rpath.cxx
index ee75b12..c710e66 100644
--- a/build2/cc/windows-rpath.cxx
+++ b/build2/cc/windows-rpath.cxx
@@ -105,7 +105,7 @@ namespace build2
for (const prerequisite_target& pt: t.prerequisite_targets[a])
{
- if (pt == nullptr)
+ if (pt == nullptr || pt.adhoc)
continue;
bool la;
@@ -195,7 +195,7 @@ namespace build2
for (const prerequisite_target& pt: t.prerequisite_targets[a])
{
- if (pt == nullptr)
+ if (pt == nullptr || pt.adhoc)
continue;
bool la;
diff --git a/build2/cli/rule.cxx b/build2/cli/rule.cxx
index f621f02..45b8185 100644
--- a/build2/cli/rule.cxx
+++ b/build2/cli/rule.cxx
@@ -44,41 +44,48 @@ namespace build2
}
bool compile_rule::
- match (action a, target& xt, const string&) const
+ match (action a, target& t, const string&) const
{
tracer trace ("cli::compile_rule::match");
- if (cli_cxx* pt = xt.is_a<cli_cxx> ())
+ // Find the .cli source file.
+ //
+ auto find = [&trace, a, &t] (auto&& r) -> optional<prerequisite_member>
{
- // The cli.cxx{} group.
- //
- cli_cxx& t (*pt);
-
- // See if we have a .cli source file.
- //
- bool r (false);
- for (prerequisite_member p: group_prerequisite_members (a, t))
+ for (prerequisite_member p: r)
{
+ // If excluded or ad hoc, then don't factor it into our tests.
+ //
+ if (include (a, t, p) != include_type::normal)
+ continue;
+
if (p.is_a<cli> ())
{
// Check that the stem match.
//
- if (!match_stem (t.name, p.name ()))
- {
- l4 ([&]{trace << ".cli file stem '" << p.name () << "' "
- << "doesn't match target " << t;});
- return false;
- }
-
- r = true;
- break;
+ if (match_stem (t.name, p.name ()))
+ return p;
+
+ l4 ([&]{trace << ".cli file stem '" << p.name () << "' "
+ << "doesn't match target " << t;});
}
}
- if (!r)
+ return nullopt;
+ };
+
+ if (cli_cxx* pt = t.is_a<cli_cxx> ())
+ {
+ // The cli.cxx{} group.
+ //
+ cli_cxx& t (*pt);
+
+ // See if we have a .cli source file.
+ //
+ if (!find (group_prerequisite_members (a, t)))
{
l4 ([&]{trace << "no .cli source file for target " << t;});
- return r;
+ return false;
}
// Figure out the member list.
@@ -95,13 +102,12 @@ namespace build2
? nullptr
: &search<cxx::ixx> (t, t.dir, t.out, t.name);
- return r;
+ return true;
}
else
{
// One of the ?xx{} members.
//
- target& t (xt);
// Check if there is a corresponding cli.cxx{} group.
//
@@ -113,24 +119,13 @@ namespace build2
//
if (g == nullptr || !g->has_prerequisites ())
{
- for (prerequisite_member p: prerequisite_members (a, t))
+ if (optional<prerequisite_member> p = find (
+ prerequisite_members (a, t)))
{
- if (p.is_a<cli> ())
- {
- // Check that the stems match.
- //
- if (match_stem (t.name, p.name ()))
- {
- if (g == nullptr)
- g = &targets.insert<cli_cxx> (t.dir, t.out, t.name, trace);
-
- g->prerequisites (prerequisites {p.as_prerequisite ()});
- }
- else
- l4 ([&]{trace << ".cli file stem '" << p.name () << "' "
- << "doesn't match target " << t;});
- break;
- }
+ if (g == nullptr)
+ g = &targets.insert<cli_cxx> (t.dir, t.out, t.name, trace);
+
+ g->prerequisites (prerequisites {p->as_prerequisite ()});
}
}
@@ -175,7 +170,7 @@ namespace build2
{
case perform_update_id: return &perform_update;
case perform_clean_id: return &perform_clean_group; // Standard impl.
- default: return noop_recipe; // Configure update.
+ default: return noop_recipe; // Configure/dist update.
}
}
else
diff --git a/build2/config/operation.cxx b/build2/config/operation.cxx
index 1e6a387..f9e9253 100644
--- a/build2/config/operation.cxx
+++ b/build2/config/operation.cxx
@@ -570,7 +570,8 @@ namespace build2
&configure_match,
&configure_execute,
nullptr, // operation post
- nullptr // meta-operation post
+ nullptr, // meta-operation post
+ nullptr // include
};
// disfigure
@@ -820,6 +821,7 @@ namespace build2
&disfigure_execute,
nullptr, // operation post
nullptr, // meta-operation post
+ nullptr // include
};
// create
diff --git a/build2/context.cxx b/build2/context.cxx
index c704cd2..4884145 100644
--- a/build2/context.cxx
+++ b/build2/context.cxx
@@ -343,6 +343,7 @@ namespace build2
const variable* var_clean;
const variable* var_backlink;
+ const variable* var_include;
const char var_extension[10] = "extension";
@@ -715,6 +716,8 @@ namespace build2
var_clean = &vp.insert<bool> ("clean", v_t);
var_backlink = &vp.insert<string> ("backlink", v_t);
+ var_include = &vp.insert<string> ("include", v_t);
+
vp.insert<string> (var_extension, v_t);
// Backlink executables and (generated) documentation by default.
diff --git a/build2/context.hxx b/build2/context.hxx
index 9c43da1..cfe770f 100644
--- a/build2/context.hxx
+++ b/build2/context.hxx
@@ -289,6 +289,31 @@ namespace build2
//
extern const variable* var_backlink; // [string] target visibility
+ // 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.
+ //
+ extern const variable* var_include; // [string] prereq visibility
+
extern const char var_extension[10]; // "extension"
// The build.* namespace.
diff --git a/build2/dist/operation.cxx b/build2/dist/operation.cxx
index f980568..44cdfab 100644
--- a/build2/dist/operation.cxx
+++ b/build2/dist/operation.cxx
@@ -623,6 +623,28 @@ namespace build2
out_rm.cancel ();
}
+ static include_type
+ dist_include (action,
+ const target&,
+ const prerequisite_member& p,
+ include_type i)
+ {
+ tracer trace ("dist_include");
+
+ // Override excluded to adhoc so that every source is included into the
+ // distribution. Note that this should be harmless to a custom rule
+ // given the prescribed semantics of adhoc (match/execute but otherwise
+ // ignore) is followed.
+ //
+ if (i == include_type::excluded)
+ {
+ l5 ([&]{trace << "overriding exclusion of " << p;});
+ i = include_type::adhoc;
+ }
+
+ return i;
+ }
+
const meta_operation_info mo_dist {
dist_id,
"dist",
@@ -638,7 +660,8 @@ namespace build2
nullptr, // no match (see execute()).
&dist_execute,
nullptr, // operation post
- nullptr // meta-operation post
+ nullptr, // meta-operation post
+ &dist_include
};
}
}
diff --git a/build2/dist/rule.cxx b/build2/dist/rule.cxx
index 5ae1f66..d5f42f0 100644
--- a/build2/dist/rule.cxx
+++ b/build2/dist/rule.cxx
@@ -31,6 +31,9 @@ namespace build2
for (prerequisite_member p:
group_prerequisite_members (a, t, members_mode::maybe))
{
+ // Note: no exclusion tests, we want all of them (and see also the
+ // dist_include() override).
+
// Skip prerequisites imported from other projects.
//
if (p.proj ())
@@ -49,6 +52,8 @@ namespace build2
// is mentioned as a target, then it is in out (we don't do the same
// target in both src/out).
//
+ // @@ Note that this is still an issue in a custom dist rule.
+ //
const target* pt (nullptr);
if (p.is_a<file> ())
{
diff --git a/build2/dist/rule.hxx b/build2/dist/rule.hxx
index 2050a5b..d384b32 100644
--- a/build2/dist/rule.hxx
+++ b/build2/dist/rule.hxx
@@ -9,8 +9,8 @@
#include <build2/utility.hxx>
#include <build2/rule.hxx>
+#include <build2/action.hxx>
#include <build2/target.hxx>
-#include <build2/operation.hxx>
namespace build2
{
@@ -19,7 +19,7 @@ namespace build2
// This is the default rule that simply matches all the prerequisites.
//
// A custom rule (usually the same as perform_update) may be necessary to
- // establishing group links (so that we see the dist variable set on a
+ // establish group links (so that we see the dist variable set on a
// group).
//
class rule: public build2::rule
diff --git a/build2/install/rule.cxx b/build2/install/rule.cxx
index 4b50a35..58182cb 100644
--- a/build2/install/rule.cxx
+++ b/build2/install/rule.cxx
@@ -82,6 +82,13 @@ namespace build2
{
const prerequisite& p (i->prerequisite);
+ // Ignore excluded.
+ //
+ include_type pi (include (a, t, p));
+
+ if (!pi)
+ continue;
+
// Ignore unresolved targets that are imported from other projects.
// We are definitely not installing those.
//
@@ -119,7 +126,7 @@ namespace build2
}
build2::match (a, *pt);
- pts.push_back (pt);
+ pts.push_back (prerequisite_target (pt, pi));
}
return default_recipe;
@@ -228,7 +235,7 @@ namespace build2
}
build2::match (a, *mt);
- pts.push_back (mt);
+ pts.push_back (mt); // Never ad hoc.
}
}
@@ -292,6 +299,13 @@ namespace build2
{
const prerequisite& p (i->prerequisite);
+ // Ignore excluded.
+ //
+ include_type pi (include (a, t, p));
+
+ if (!pi)
+ continue;
+
// Ignore unresolved targets that are imported from other projects.
// We are definitely not installing those.
//
@@ -327,7 +341,7 @@ namespace build2
// static installable content (headers, documentation, etc).
//
if (!build2::match (a, *pt, unmatch::unchanged))
- pts.push_back (pt);
+ pts.push_back (prerequisite_target (pt, pi));
}
if (a.operation () == update_id)
diff --git a/build2/install/rule.hxx b/build2/install/rule.hxx
index 8bb2fa3..20b30a1 100644
--- a/build2/install/rule.hxx
+++ b/build2/install/rule.hxx
@@ -9,8 +9,8 @@
#include <build2/utility.hxx>
#include <build2/rule.hxx>
+#include <build2/action.hxx>
#include <build2/target.hxx>
-#include <build2/operation.hxx>
#include <build2/filesystem.hxx>
namespace build2
@@ -24,7 +24,7 @@ namespace build2
match (action, target&, const string&) const override;
// Return NULL if this prerequisite should be ignored and pointer to its
- // target otherwise. The default implementation accepts prerequsites
+ // target otherwise. The default implementation accepts all prerequsites
// from the target's (weak) amalgamation.
//
// The prerequisite it passed as an iterator allowing the filter to
diff --git a/build2/operation.cxx b/build2/operation.cxx
index d5e2299..37e7c91 100644
--- a/build2/operation.cxx
+++ b/build2/operation.cxx
@@ -60,7 +60,8 @@ namespace build2
nullptr, // match
nullptr, // execute
nullptr, // operation post
- nullptr // meta-operation post
+ nullptr, // meta-operation post
+ nullptr // include
};
// perform
@@ -425,7 +426,8 @@ namespace build2
&match,
&execute,
nullptr, // operation post
- nullptr // meta-operation post
+ nullptr, // meta-operation post
+ nullptr // include
};
// info
@@ -517,7 +519,8 @@ namespace build2
nullptr, // match
&info_execute,
nullptr, // operation post
- nullptr // meta-operation post
+ nullptr, // meta-operation post
+ nullptr // include
};
// operations
diff --git a/build2/operation.hxx b/build2/operation.hxx
index f5a1cf3..1fc6abf 100644
--- a/build2/operation.hxx
+++ b/build2/operation.hxx
@@ -10,7 +10,9 @@
#include <build2/types.hxx>
#include <build2/utility.hxx>
+#include <build2/action.hxx>
#include <build2/variable.hxx>
+#include <build2/prerequisite.hxx>
#include <build2/target-state.hxx>
namespace build2
@@ -19,195 +21,10 @@ namespace build2
class scope;
class target_key;
class target;
+ struct prerequisite_member;
struct opspec;
- // 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;
-
- ostream&
- operator<< (ostream&, action);
-
- // Inner/outer operation state container.
- //
- template <typename T>
- struct action_state
- {
- T states[2]; // [0] -- inner, [1] -- outer.
-
- T& operator[] (action a) {return states[a.inner () ? 0 : 1];}
- const T& operator[] (action a) const {return states[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};
-
// Meta-operation info.
//
@@ -309,10 +126,18 @@ namespace build2
void (*execute) (const values&, action, action_targets&,
uint16_t diag, bool progress);
- // Start of operation and meta-operation batches.
+ // 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.
diff --git a/build2/prerequisite.cxx b/build2/prerequisite.cxx
index 8edcc74..5900589 100644
--- a/build2/prerequisite.cxx
+++ b/build2/prerequisite.cxx
@@ -94,4 +94,29 @@ namespace build2
return r;
}
+
+ // include()
+ //
+ include_type
+ include_impl (action a,
+ const target& t,
+ const string& v,
+ const prerequisite& p,
+ const target* m)
+ {
+ include_type r (false);
+
+ if (v == "false") r = include_type::excluded;
+ else if (v == "adhoc") r = include_type::adhoc;
+ else if (v == "true") r = include_type::normal;
+ else
+ fail << "invalid " << var_include->name << " variable value "
+ << "'" << v << "' specified for prerequisite " << p;
+
+ // Call the meta-operation override, if any (currently used by dist).
+ //
+ return current_mif->include == nullptr
+ ? r
+ : current_mif->include (a, t, prerequisite_member {p, m}, r);
+ }
}
diff --git a/build2/prerequisite.hxx b/build2/prerequisite.hxx
index eb9f3b3..6ae6ec7 100644
--- a/build2/prerequisite.hxx
+++ b/build2/prerequisite.hxx
@@ -5,11 +5,10 @@
#ifndef BUILD2_PREREQUISITE_HXX
#define BUILD2_PREREQUISITE_HXX
-#include <unordered_set>
-
#include <build2/types.hxx>
#include <build2/utility.hxx>
+#include <build2/action.hxx>
#include <build2/variable.hxx>
#include <build2/target-key.hxx>
#include <build2/diagnostics.hxx>
@@ -182,6 +181,38 @@ namespace build2
}
using prerequisites = vector<prerequisite>;
+
+ // Helpers for dealing with the prerequisite inclusion/exclusion (the
+ // 'include' buildfile variable, see var_include in context.hxx).
+ //
+ // Note that the include(prerequisite_member) overload is also provided.
+ //
+ // @@ Maybe this filtering should be incorporated into *_prerequisites() and
+ // *_prerequisite_members() logic? Could make normal > adhoc > excluded and
+ // then pass the "threshold".
+ //
+ class include_type
+ {
+ public:
+ enum value {excluded, adhoc, normal};
+
+ include_type (value v): v_ (v) {}
+ include_type (bool v): v_ (v ? normal : excluded) {}
+
+ operator value () const {return v_;}
+ explicit operator bool () const {return v_ != excluded;}
+
+ private:
+ value v_;
+ };
+
+ include_type
+ include (action,
+ const target&,
+ const prerequisite&,
+ const target* = nullptr);
}
+#include <build2/prerequisite.ixx>
+
#endif // BUILD2_PREREQUISITE_HXX
diff --git a/build2/prerequisite.ixx b/build2/prerequisite.ixx
new file mode 100644
index 0000000..aeec6b4
--- /dev/null
+++ b/build2/prerequisite.ixx
@@ -0,0 +1,31 @@
+// file : build2/prerequisite.ixx -*- C++ -*-
+// copyright : Copyright (c) 2014-2018 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+namespace build2
+{
+ include_type
+ include_impl (action,
+ const target&,
+ const string&,
+ const prerequisite&,
+ const target*);
+
+ extern const variable* var_include; // context.cxx
+
+ inline include_type
+ include (action a, const target& t, const prerequisite& p, const target* m)
+ {
+ // Most of the time this variable will not be specified, so let's optimize
+ // for that.
+ //
+ if (p.vars.empty ())
+ return true;
+
+ const string* v (cast_null<string> (p.vars[var_include]));
+
+ if (v == nullptr)
+ return true;
+
+ return include_impl (a, t, *v, p, m);
+ }
+}
diff --git a/build2/rule-map.hxx b/build2/rule-map.hxx
index b0ec742..30c55f7 100644
--- a/build2/rule-map.hxx
+++ b/build2/rule-map.hxx
@@ -12,7 +12,7 @@
#include <build2/types.hxx>
#include <build2/utility.hxx>
-#include <build2/operation.hxx>
+#include <build2/action.hxx>
namespace build2
{
diff --git a/build2/rule.hxx b/build2/rule.hxx
index 4a91842..c1dbc3a 100644
--- a/build2/rule.hxx
+++ b/build2/rule.hxx
@@ -8,8 +8,8 @@
#include <build2/types.hxx>
#include <build2/utility.hxx>
+#include <build2/action.hxx>
#include <build2/target.hxx>
-#include <build2/operation.hxx>
namespace build2
{
diff --git a/build2/target.hxx b/build2/target.hxx
index 761cb4e..515a082 100644
--- a/build2/target.hxx
+++ b/build2/target.hxx
@@ -15,8 +15,8 @@
#include <build2/utility.hxx>
#include <build2/scope.hxx>
+#include <build2/action.hxx>
#include <build2/variable.hxx>
-#include <build2/operation.hxx>
#include <build2/target-key.hxx>
#include <build2/target-type.hxx>
#include <build2/target-state.hxx>
@@ -92,14 +92,18 @@ namespace build2
{
using target_type = build2::target;
- prerequisite_target (const target_type* t, uintptr_t d = 0)
- : target (t), data (d) {}
+ prerequisite_target (const target_type* t, bool a = false, uintptr_t d = 0)
+ : target (t), adhoc (a), data (d) {}
+
+ prerequisite_target (const target_type* t, include_type a, uintptr_t d = 0)
+ : prerequisite_target (t, a == include_type::adhoc, d) {}
operator const target_type*& () {return target;}
operator const target_type* () const {return target;}
const target_type* operator-> () const {return target;}
const target_type* target;
+ bool adhoc; // True if include=adhoc.
uintptr_t data;
};
using prerequisite_targets = vector<prerequisite_target>;
@@ -932,6 +936,12 @@ namespace build2
return os << pm.key ();
}
+ inline include_type
+ include (action a, const target& t, const prerequisite_member& pm)
+ {
+ return include (a, t, pm.prerequisite, pm.member);
+ }
+
// A "range" that presents a sequence of prerequisites (e.g., from
// group_prerequisites()) as a sequence of prerequisite_member's. For each
// group prerequisite you will "see" either the prerequisite itself or all
diff --git a/build2/test/rule.cxx b/build2/test/rule.cxx
index f877a4d..282540c 100644
--- a/build2/test/rule.cxx
+++ b/build2/test/rule.cxx
@@ -126,6 +126,9 @@ namespace build2
for (prerequisite_member p:
group_prerequisite_members (a, t, members_mode::maybe))
{
+ if (include (a, t, p) != include_type::normal) // Excluded/ad hoc.
+ continue;
+
if (p.is_a<testscript> ())
{
if (!script)
@@ -205,6 +208,9 @@ namespace build2
if (vars.empty ()) // Common case.
continue;
+ if (include (a, t, p) != include_type::normal) // Excluded/ad hoc.
+ continue;
+
bool rt ( cast_false<bool> (vars[test_roundtrip]));
bool si (rt || cast_false<bool> (vars[test_stdin]));
bool so (rt || cast_false<bool> (vars[test_stdout]));
diff --git a/build2/test/rule.hxx b/build2/test/rule.hxx
index e4fd28d..af42d89 100644
--- a/build2/test/rule.hxx
+++ b/build2/test/rule.hxx
@@ -9,7 +9,7 @@
#include <build2/utility.hxx>
#include <build2/rule.hxx>
-#include <build2/operation.hxx>
+#include <build2/action.hxx>
#include <build2/test/common.hxx>
diff --git a/build2/variable.hxx b/build2/variable.hxx
index 808efc9..4d11b25 100644
--- a/build2/variable.hxx
+++ b/build2/variable.hxx
@@ -330,6 +330,13 @@ namespace build2
template <typename T> const T& cast_empty (const value&);
template <typename T> const T& cast_empty (const lookup&);
+ // As above but returns the specified default if the value is NULL (or not
+ // defined, in case of lookup). Note that the return is by value, not by
+ // reference.
+ //
+ template <typename T> T cast_default (const value&, const T&);
+ template <typename T> T cast_default (const lookup&, const T&);
+
// As above but returns false/true if the value is NULL (or not defined,
// in case of lookup). Note that the template argument is only for
// documentation and should be bool (or semantically compatible).
diff --git a/build2/variable.ixx b/build2/variable.ixx
index 953bc86..991556a 100644
--- a/build2/variable.ixx
+++ b/build2/variable.ixx
@@ -201,6 +201,20 @@ namespace build2
template <typename T>
inline T
+ cast_default (const value& v, const T& d)
+ {
+ return v ? cast<T> (v) : d;
+ }
+
+ template <typename T>
+ inline T
+ cast_default (const lookup& l, const T& d)
+ {
+ return l ? cast<T> (l) : d;
+ }
+
+ template <typename T>
+ inline T
cast_false (const value& v)
{
return v && cast<T> (v);
diff --git a/build2/version/rule.cxx b/build2/version/rule.cxx
index 1a41cef..91b8c75 100644
--- a/build2/version/rule.cxx
+++ b/build2/version/rule.cxx
@@ -60,6 +60,9 @@ namespace build2
bool fi (false); // Found in.
for (prerequisite_member p: group_prerequisite_members (a, t))
{
+ if (include (a, t, p) != include_type::normal) // Excluded/ad hoc.
+ continue;
+
fm = fm || manifest_prerequisite (rs, p);
fi = fi || p.is_a<in> ();
}
diff --git a/old-tests/cli/simple/build/bootstrap.build b/old-tests/cli/simple/build/bootstrap.build
index 40e30ac..c7d6cab 100644
--- a/old-tests/cli/simple/build/bootstrap.build
+++ b/old-tests/cli/simple/build/bootstrap.build
@@ -4,3 +4,6 @@ amalgamation = # Disabled.
using config
using install
using test
+using dist
+
+dist.package = $project