From 3cc5e3bd441fc9d18fece3d9e99fae75c78438e7 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Thu, 28 Jun 2018 09:44:15 +0200 Subject: 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 --- build2/action.hxx | 200 +++++++++++++++++++++++++++++ build2/algorithm.cxx | 38 +++++- build2/algorithm.hxx | 15 ++- build2/cc/common.cxx | 5 +- build2/cc/compile-rule.cxx | 48 +++++-- build2/cc/install-rule.cxx | 2 + build2/cc/link-rule.cxx | 50 ++++++-- build2/cc/windows-rpath.cxx | 4 +- build2/cli/rule.cxx | 77 ++++++----- build2/config/operation.cxx | 4 +- build2/context.cxx | 3 + build2/context.hxx | 25 ++++ build2/dist/operation.cxx | 25 +++- build2/dist/rule.cxx | 5 + build2/dist/rule.hxx | 4 +- build2/install/rule.cxx | 20 ++- build2/install/rule.hxx | 4 +- build2/operation.cxx | 9 +- build2/operation.hxx | 199 ++-------------------------- build2/prerequisite.cxx | 25 ++++ build2/prerequisite.hxx | 35 ++++- build2/prerequisite.ixx | 31 +++++ build2/rule-map.hxx | 2 +- build2/rule.hxx | 2 +- build2/target.hxx | 16 ++- build2/test/rule.cxx | 6 + build2/test/rule.hxx | 2 +- build2/variable.hxx | 7 + build2/variable.ixx | 14 ++ build2/version/rule.cxx | 3 + old-tests/cli/simple/build/bootstrap.build | 3 + 31 files changed, 600 insertions(+), 283 deletions(-) create mode 100644 build2/action.hxx create mode 100644 build2/prerequisite.ixx 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 +#include + +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 + 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 ). 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)) { + // 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 (action, target&, const target* const*, size_t); + match_members (action, target&, + const target* const*, size_t); template void - match_members ( - action, target&, prerequisite_target const*, size_t); + match_members (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 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 #include +#include #include -#include 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 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 ()) @@ -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 ()) @@ -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 () || - p.is_a () || - p.is_a () || - p.is_a ()) + if (pi == include_type::normal && + (p.is_a () || + p.is_a () || + p.is_a () || + p.is_a ())) { 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 () || p.is_a (tt.bmi)) + else if (pi == include_type::normal && + (p.is_a () || 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 () || t.is_a ()); // 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 ()) + // Find the .cli source file. + // + auto find = [&trace, a, &t] (auto&& r) -> optional { - // 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 ()) { // 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 ()) + { + // 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 (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 p = find ( + prerequisite_members (a, t))) { - if (p.is_a ()) - { - // Check that the stems match. - // - if (match_stem (t.name, p.name ())) - { - if (g == nullptr) - g = &targets.insert (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 (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 ("clean", v_t); var_backlink = &vp.insert ("backlink", v_t); + var_include = &vp.insert ("include", v_t); + vp.insert (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 ()) { 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 #include +#include #include -#include 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 #include +#include #include -#include #include 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 #include +#include #include +#include #include 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 - 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 ). 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 - #include #include +#include #include #include #include @@ -182,6 +181,38 @@ namespace build2 } using prerequisites = vector; + + // 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 + #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 (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 #include -#include +#include 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 #include +#include #include -#include 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 #include +#include #include -#include #include #include #include @@ -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; @@ -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 ()) { 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 (vars[test_roundtrip])); bool si (rt || cast_false (vars[test_stdin])); bool so (rt || cast_false (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 #include -#include +#include #include 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 const T& cast_empty (const value&); template 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 T cast_default (const value&, const T&); + template 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 inline T + cast_default (const value& v, const T& d) + { + return v ? cast (v) : d; + } + + template + inline T + cast_default (const lookup& l, const T& d) + { + return l ? cast (l) : d; + } + + template + inline T cast_false (const value& v) { return v && cast (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 (); } 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 -- cgit v1.1