From 9fb791e9fad6c63fc1dac49f4d05ae63b8a3db9b Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Tue, 5 Jan 2016 11:55:15 +0200 Subject: Rename build directory/namespace to build2 --- build2/.gitignore | 2 + build2/algorithm | 222 +++++ build2/algorithm.cxx | 504 ++++++++++ build2/algorithm.ixx | 197 ++++ build2/algorithm.txx | 58 ++ build2/b.cxx | 855 ++++++++++++++++ build2/bin/module | 23 + build2/bin/module.cxx | 188 ++++ build2/bin/rule | 39 + build2/bin/rule.cxx | 145 +++ build2/bin/target | 99 ++ build2/bin/target.cxx | 190 ++++ build2/buildfile | 64 ++ build2/cli/module | 23 + build2/cli/module.cxx | 244 +++++ build2/cli/rule | 32 + build2/cli/rule.cxx | 305 ++++++ build2/cli/target | 62 ++ build2/cli/target.cxx | 77 ++ build2/config/module | 26 + build2/config/module.cxx | 90 ++ build2/config/operation | 19 + build2/config/operation.cxx | 455 +++++++++ build2/config/utility | 128 +++ build2/config/utility.cxx | 92 ++ build2/config/utility.ixx | 17 + build2/config/utility.txx | 45 + build2/context | 165 ++++ build2/context.cxx | 391 ++++++++ build2/context.txx | 141 +++ build2/cxx/compile | 32 + build2/cxx/compile.cxx | 794 +++++++++++++++ build2/cxx/install | 29 + build2/cxx/install.cxx | 66 ++ build2/cxx/link | 70 ++ build2/cxx/link.cxx | 875 +++++++++++++++++ build2/cxx/module | 23 + build2/cxx/module.cxx | 230 +++++ build2/cxx/target | 78 ++ build2/cxx/target.cxx | 81 ++ build2/cxx/utility | 37 + build2/cxx/utility.cxx | 29 + build2/cxx/utility.txx | 35 + build2/diagnostics | 402 ++++++++ build2/diagnostics.cxx | 125 +++ build2/dist/module | 26 + build2/dist/module.cxx | 142 +++ build2/dist/operation | 18 + build2/dist/operation.cxx | 459 +++++++++ build2/dist/rule | 29 + build2/dist/rule.cxx | 55 ++ build2/dump | 18 + build2/dump.cxx | 253 +++++ build2/file | 144 +++ build2/file.cxx | 980 +++++++++++++++++++ build2/file.ixx | 12 + build2/install/module | 26 + build2/install/module.cxx | 188 ++++ build2/install/operation | 18 + build2/install/operation.cxx | 32 + build2/install/rule | 49 + build2/install/rule.cxx | 410 ++++++++ build2/install/utility | 40 + build2/lexer | 138 +++ build2/lexer.cxx | 431 +++++++++ build2/module | 86 ++ build2/module.cxx | 114 +++ build2/name | 113 +++ build2/name.cxx | 63 ++ build2/operation | 359 +++++++ build2/operation.cxx | 232 +++++ build2/options | 328 +++++++ build2/options.cli | 31 + build2/options.cxx | 549 +++++++++++ build2/options.ixx | 165 ++++ build2/parser | 296 ++++++ build2/parser.cxx | 2206 ++++++++++++++++++++++++++++++++++++++++++ build2/path-io | 26 + build2/path-io.cxx | 39 + build2/prerequisite | 129 +++ build2/prerequisite.cxx | 82 ++ build2/rule | 135 +++ build2/rule-map | 115 +++ build2/rule.cxx | 249 +++++ build2/scope | 312 ++++++ build2/scope.cxx | 317 ++++++ build2/search | 31 + build2/search.cxx | 171 ++++ build2/spec | 61 ++ build2/spec.cxx | 81 ++ build2/target | 1084 +++++++++++++++++++++ build2/target-key | 55 ++ build2/target-type | 97 ++ build2/target.cxx | 537 ++++++++++ build2/target.ixx | 85 ++ build2/target.txx | 58 ++ build2/test/module | 24 + build2/test/module.cxx | 88 ++ build2/test/operation | 18 + build2/test/operation.cxx | 32 + build2/test/rule | 30 + build2/test/rule.cxx | 439 +++++++++ build2/token | 73 ++ build2/token.cxx | 35 + build2/types | 55 ++ build2/utility | 85 ++ build2/utility.cxx | 94 ++ build2/variable | 831 ++++++++++++++++ build2/variable.cxx | 452 +++++++++ build2/variable.ixx | 398 ++++++++ build2/variable.txx | 168 ++++ build2/version | 37 + 112 files changed, 22337 insertions(+) create mode 100644 build2/.gitignore create mode 100644 build2/algorithm create mode 100644 build2/algorithm.cxx create mode 100644 build2/algorithm.ixx create mode 100644 build2/algorithm.txx create mode 100644 build2/b.cxx create mode 100644 build2/bin/module create mode 100644 build2/bin/module.cxx create mode 100644 build2/bin/rule create mode 100644 build2/bin/rule.cxx create mode 100644 build2/bin/target create mode 100644 build2/bin/target.cxx create mode 100644 build2/buildfile create mode 100644 build2/cli/module create mode 100644 build2/cli/module.cxx create mode 100644 build2/cli/rule create mode 100644 build2/cli/rule.cxx create mode 100644 build2/cli/target create mode 100644 build2/cli/target.cxx create mode 100644 build2/config/module create mode 100644 build2/config/module.cxx create mode 100644 build2/config/operation create mode 100644 build2/config/operation.cxx create mode 100644 build2/config/utility create mode 100644 build2/config/utility.cxx create mode 100644 build2/config/utility.ixx create mode 100644 build2/config/utility.txx create mode 100644 build2/context create mode 100644 build2/context.cxx create mode 100644 build2/context.txx create mode 100644 build2/cxx/compile create mode 100644 build2/cxx/compile.cxx create mode 100644 build2/cxx/install create mode 100644 build2/cxx/install.cxx create mode 100644 build2/cxx/link create mode 100644 build2/cxx/link.cxx create mode 100644 build2/cxx/module create mode 100644 build2/cxx/module.cxx create mode 100644 build2/cxx/target create mode 100644 build2/cxx/target.cxx create mode 100644 build2/cxx/utility create mode 100644 build2/cxx/utility.cxx create mode 100644 build2/cxx/utility.txx create mode 100644 build2/diagnostics create mode 100644 build2/diagnostics.cxx create mode 100644 build2/dist/module create mode 100644 build2/dist/module.cxx create mode 100644 build2/dist/operation create mode 100644 build2/dist/operation.cxx create mode 100644 build2/dist/rule create mode 100644 build2/dist/rule.cxx create mode 100644 build2/dump create mode 100644 build2/dump.cxx create mode 100644 build2/file create mode 100644 build2/file.cxx create mode 100644 build2/file.ixx create mode 100644 build2/install/module create mode 100644 build2/install/module.cxx create mode 100644 build2/install/operation create mode 100644 build2/install/operation.cxx create mode 100644 build2/install/rule create mode 100644 build2/install/rule.cxx create mode 100644 build2/install/utility create mode 100644 build2/lexer create mode 100644 build2/lexer.cxx create mode 100644 build2/module create mode 100644 build2/module.cxx create mode 100644 build2/name create mode 100644 build2/name.cxx create mode 100644 build2/operation create mode 100644 build2/operation.cxx create mode 100644 build2/options create mode 100644 build2/options.cli create mode 100644 build2/options.cxx create mode 100644 build2/options.ixx create mode 100644 build2/parser create mode 100644 build2/parser.cxx create mode 100644 build2/path-io create mode 100644 build2/path-io.cxx create mode 100644 build2/prerequisite create mode 100644 build2/prerequisite.cxx create mode 100644 build2/rule create mode 100644 build2/rule-map create mode 100644 build2/rule.cxx create mode 100644 build2/scope create mode 100644 build2/scope.cxx create mode 100644 build2/search create mode 100644 build2/search.cxx create mode 100644 build2/spec create mode 100644 build2/spec.cxx create mode 100644 build2/target create mode 100644 build2/target-key create mode 100644 build2/target-type create mode 100644 build2/target.cxx create mode 100644 build2/target.ixx create mode 100644 build2/target.txx create mode 100644 build2/test/module create mode 100644 build2/test/module.cxx create mode 100644 build2/test/operation create mode 100644 build2/test/operation.cxx create mode 100644 build2/test/rule create mode 100644 build2/test/rule.cxx create mode 100644 build2/token create mode 100644 build2/token.cxx create mode 100644 build2/types create mode 100644 build2/utility create mode 100644 build2/utility.cxx create mode 100644 build2/variable create mode 100644 build2/variable.cxx create mode 100644 build2/variable.ixx create mode 100644 build2/variable.txx create mode 100644 build2/version (limited to 'build2') diff --git a/build2/.gitignore b/build2/.gitignore new file mode 100644 index 0000000..c9855e8 --- /dev/null +++ b/build2/.gitignore @@ -0,0 +1,2 @@ +b +b-* diff --git a/build2/algorithm b/build2/algorithm new file mode 100644 index 0000000..2cfec62 --- /dev/null +++ b/build2/algorithm @@ -0,0 +1,222 @@ +// file : build2/algorithm -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_ALGORITHM +#define BUILD2_ALGORITHM + +#include +#include // pair + +#include +#include +#include + +namespace build2 +{ + class scope; + class prerequisite; + class prerequisite_key; + + // The default prerequisite search implementation. It first calls the + // target-type-specific search function. If that doesn't yeld anything, + // it creates a new target. + // + target& + search (prerequisite&); + + // As above but specify the prerequisite to search as a key. + // + target& + search (const prerequisite_key&); + + // As above but override the target type. Useful for searching for + // target group members where we need to search for a different + // target type. + // + target& + search (const target_type&, const prerequisite_key&); + + // As above but specify the prerequisite to search as individual + // key components. + // + target& + search (const target_type& type, + const dir_path& dir, + const std::string& name, + const std::string* ext, + scope*); + + // As above but specify the target type as template argument. + // + template + T& + search (const dir_path& dir, + const std::string& name, + const std::string* ext, + scope*); + + // Search for a target identified by the name. The semantics + // is "as if" we first created a prerequisite based on this + // name in exactly the same way as the parser would and then + // searched based on this prerequisite. + // + target& + search (name, scope&); + + // Match and apply a rule to the action/target with ambiguity + // detection. Increment the target's dependents count, which + // means that you should call this function with the intent + // to also call execute(). In case of optimizations that would + // avoid calling execute(), call unmatch() to indicate this. + // + void + match (action, target&); + + // Note that calling this function only makes sense if the + // target itself doesn't have its own dependents. + // + void + unmatch (action, target&); + + // Match (but do not apply) a rule to the action/target with + // ambiguity detection. Note that this function does not touch + // the dependents count. + // + void + match_only (action, target&); + + // Match a "delegate rule" from withing another rules' apply() + // function. Return recipe and recipe action (if any). Note + // that unlike match(), this call doesn't increment the + // dependents count. See also the companion execute_delegate(). + // + std::pair + match_delegate (action, target&); + + // The standard prerequisite search and match implementations. They call + // search_and_match_*() versions below passing non-empty directory for + // the clean operation. + // + void + search_and_match_prerequisites (action, target&); + + // If we are cleaning, this function doesn't go into group members, + // as an optimization (the group should clean everything up). + // + void + search_and_match_prerequisite_members (action, target&); + + // The actual prerequisite search and match implementations. They call + // search() and then match() for each prerequisite in a loop. If this + // target is a member of a group, then they first do this to the group's + // prerequisites. + // + // If the directory argument is not empty, then they ignore (do not + // match) prerequisites that are not in the same or its subdirectory. + // + void + search_and_match_prerequisites (action, target&, const dir_path&); + + void + search_and_match_prerequisite_members (action, target&, const dir_path&); + + // Unless already available, match, and, if necessary, execute the group + // in order to obtain its members list. Note that even after that the + // member's list might still not be available (e.g., if some wildcard/ + // fallback rule matched). + // + group_view + resolve_group_members (action, target&); + + // Inject dependency on the parent directory's fsdir{}, unless it is + // the project's out_root (or is outside of any project; say, for + // example, an install directory). Normally this function is called + // from the rule's apply() function. + // + void + inject_parent_fsdir (action, target&); + + // Execute the action on target, assuming a rule has been matched + // and the recipe for this action has been set. This is the default + // executor implementation. Decrements the dependents count. + // + target_state + execute (action, target&); + + // Execute the recipe obtained with match_delegate(). Note that + // the target's state is neither checked nor updated by this + // function. In other words, the appropriate usage is to call + // this function from another recipe and to factor the obtained + // state into the one returned. + // + target_state + execute_delegate (const recipe&, action, target&); + + // A special version of the above that should be used for "direct" + // and "now" execution, that is, side-stepping the normal target- + // prerequisite relationship (so no dependents count is decremented) + // and execution order (so this function will never return postponed + // target state). + // + target_state + execute_direct (action, target&); + + // The default prerequisite execute implementation. It calls execute() + // on each non-ignored (non-NULL) prerequisite target in a loop. If this + // target is a member of a group, then it first does this to the group's + // prerequisites. Returns target_state::changed if any of them were + // changed and target_state::unchanged otherwise. Note that this + // function can be used as a recipe. + // + target_state + execute_prerequisites (action, target&); + + // As above but iterates over the prerequisites in reverse. + // + target_state + reverse_execute_prerequisites (action, target&); + + // A version of the above that also determines whether the action + // needs to be executed on the target based on the passed mtime + // timestamp. + // + // Note that because we use mtime, this function should normally + // only be used in the perform_update action. + // + bool + execute_prerequisites (action, target&, const timestamp&); + + // Another version of the above that does two extra things for the + // caller: it determines whether the action needs to be executed on + // the target based on the passed timestamp and, if so, finds a + // prerequisite of the specified type (e.g., a source file). If + // there are multiple prerequisites of this type, then the last + // is returned. + // + template + T* + execute_prerequisites (action, target&, const timestamp&); + + // Return noop_recipe instead of using this function directly. + // + target_state + noop_action (action, target&); + + // Default action implementation which forwards to the prerequisites. + // Use default_recipe instead of using this function directly. + // + target_state + default_action (action, target&); + + // Standard perform(clean) action implementation for the file target + // or derived. + // + target_state + perform_clean (action, target&); +} + +#include +#include + +#endif // BUILD2_ALGORITHM diff --git a/build2/algorithm.cxx b/build2/algorithm.cxx new file mode 100644 index 0000000..243519e --- /dev/null +++ b/build2/algorithm.cxx @@ -0,0 +1,504 @@ +// file : build2/algorithm.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include // unique_ptr +#include // size_t +#include // move +#include + +#include // reverse_iterate + +#include +#include +#include +#include +#include // import() +#include +#include +#include +#include + +using namespace std; +using namespace butl; + +namespace build2 +{ + target& + search (const prerequisite_key& pk) + { + // If this is a project-qualified prerequisite, then this + // is import's business. + // + if (pk.proj != nullptr) + return import (pk); + + if (target* t = pk.tk.type->search (pk)) + return *t; + + return create_new_target (pk); + } + + target& + search (name n, scope& s) + { + const string* e; + const target_type* tt (s.find_target_type (n, e)); + + if (tt == nullptr) + fail << "unknown target type " << n.type << " in name " << n; + + n.dir.normalize (); + + return search (*tt, move (n.dir), move (n.value), e, &s); + } + + pair + match_impl (action a, target& t, bool apply) + { + pair r; + + // By default, clear the resolved targets list before calling + // match(). The rule is free to modify this list in match() + // (provided that it matches) in order to, for example, prepare + // it for apply(). + // + t.reset (a); + + // If this is a nested operation, first try the outer operation. + // This allows a rule to implement a "precise match", that is, + // both inner and outer operations match. + // + for (operation_id oo (a.outer_operation ()), io (a.operation ()), + o (oo != 0 ? oo : io); o != 0; o = (oo != 0 ? io : 0)) + { + // Adjust action for recipe: on the first iteration we want it + // {inner, outer} (which is the same as 'a') while on the second + // -- {inner, 0}. Note that {inner, 0} is the same or "stronger" + // (i.e., overrides; see action::operator<()) than 'a'. This + // allows "unconditional inner" to override "inner for outer" + // recipes. + // + action ra (a.meta_operation (), io, o != oo ? 0 : oo); + + scope& bs (t.base_scope ()); + + for (auto tt (&t.type ()); tt != nullptr; tt = tt->base) + { + // Search scopes outwards, stopping at the project root. + // + for (const scope* s (&bs); + s != nullptr; + s = s->root () ? global_scope : s->parent_scope ()) + { + const operation_rule_map* om (s->rules[a.meta_operation ()]); + + if (om == nullptr) + continue; // No entry for this meta-operation id. + + // First try the map for the actual operation. If that + // doesn't yeld anything, try the wildcard map. + // + for (size_t oi (o), oip (o); oip != 0; oip = oi, oi = 0) + { + const target_type_rule_map* ttm ((*om)[oi]); + + if (ttm == nullptr) + continue; // No entry for this operation id. + + if (ttm->empty ()) + continue; // Empty map for this operation id. + + auto i (ttm->find (tt)); + + if (i == ttm->end () || i->second.empty ()) + continue; // No rules registered for this target type. + + const auto& rules (i->second); // Hint map. + + // @@ TODO + // + // Different rules can be used for different operations (update + // vs test is a good example). So, at some point, we will probably + // have to support a list of hints or even an operation-hint map + // (e.g., 'hint=cxx test=foo' if cxx supports the test operation + // but we want the foo rule instead). This is also the place where + // the '{build clean}=cxx' construct (which we currently do not + // support) can come handy. + // + // Also, ignore the hint (that is most likely ment for a different + // operation) if this is a unique match. + // + string hint; + auto rs (rules.size () == 1 + ? make_pair (rules.begin (), rules.end ()) + : rules.find_prefix (hint)); + + for (auto i (rs.first); i != rs.second; ++i) + { + const string& n (i->first); + const rule& ru (i->second); + + match_result m; + { + auto g ( + make_exception_guard ( + [ra, &t, &n]() + { + info << "while matching rule " << n << " to " + << diag_do (ra, t); + })); + + if (!(m = ru.match (ra, t, hint))) + continue; + + if (!m.recipe_action.valid ()) + m.recipe_action = ra; // Default, if not set. + } + + // Do the ambiguity test. + // + bool ambig (false); + + diag_record dr; + + for (++i; i != rs.second; ++i) + { + const string& n1 (i->first); + const rule& ru1 (i->second); + + { + auto g ( + make_exception_guard ( + [ra, &t, &n1]() + { + info << "while matching rule " << n1 << " to " + << diag_do (ra, t); + })); + + if (!ru1.match (ra, t, hint)) + continue; + } + + if (!ambig) + { + dr << fail << "multiple rules matching " + << diag_doing (ra, t) + << info << "rule " << n << " matches"; + ambig = true; + } + + dr << info << "rule " << n1 << " also matches"; + } + + if (!ambig) + { + ra = m.recipe_action; // Use custom, if set. + + if (apply) + { + auto g ( + make_exception_guard ( + [ra, &t, &n]() + { + info << "while applying rule " << n << " to " + << diag_do (ra, t); + })); + + // @@ We could also allow the rule to change the recipe + // action in apply(). Could be useful with delegates. + // + t.recipe (ra, ru.apply (ra, t, m)); + } + else + { + r.first = &ru; + r.second = move (m); + } + + return r; + } + else + dr << info << "use rule hint to disambiguate this match"; + } + } + } + } + } + + diag_record dr; + dr << fail << "no rule to " << diag_do (a, t); + + if (verb < 4) + dr << info << "re-run with --verbose 4 for more information"; + + return r; + } + + group_view + resolve_group_members_impl (action a, target& g) + { + group_view r; + + // Unless we already have a recipe, try matching the target to + // the rule. + // + if (!g.recipe (a)) + { + auto rp (match_impl (a, g, false)); + + r = g.group_members (a); + if (r.members != nullptr) + return r; + + // That didn't help, so apply the rule and go to the building + // phase. + // + const match_result& mr (rp.second); + g.recipe (mr.recipe_action, rp.first->apply (mr.recipe_action, g, mr)); + } + + // Note that we use execute_direct() rather than execute() here to + // sidestep the dependents count logic. In this context, this is by + // definition the first attempt to execute this rule (otherwise we + // would have already known the members list) and we really do need + // to execute it now. + // + execute_direct (a, g); + + r = g.group_members (a); + return r; // Might still be unresolved. + } + + void + search_and_match_prerequisites (action a, target& t, const dir_path& d) + { + const bool e (d.empty ()); + + for (prerequisite p: group_prerequisites (t)) + { + target& pt (search (p)); + + if (e || pt.dir.sub (d)) + { + match (a, pt); + t.prerequisite_targets.push_back (&pt); + } + } + } + + void + search_and_match_prerequisite_members (action a, + target& t, + const dir_path& d) + { + const bool e (d.empty ()); + + for (prerequisite_member p: group_prerequisite_members (a, t)) + { + target& pt (p.search ()); + + if (e || pt.dir.sub (d)) + { + match (a, pt); + t.prerequisite_targets.push_back (&pt); + } + } + } + + void + inject_parent_fsdir (action a, target& t) + { + tracer trace ("inject_parent_fsdir"); + + scope& s (t.base_scope ()); + scope* rs (s.root_scope ()); + + if (rs == nullptr) // Could be outside any project. + return; + + const dir_path& out_root (rs->out_path ()); + + // If t is a directory (name is empty), say foo/bar/, then + // t is bar and its parent directory is foo/. + // + const dir_path& d (t.name.empty () ? t.dir.directory () : t.dir); + + if (!d.sub (out_root) || d == out_root) + return; + + level6 ([&]{trace << "for " << t;}); + + fsdir& dt (search (d, string (), nullptr, &s)); + match (a, dt); + t.prerequisite_targets.emplace_back (&dt); + } + + target_state + execute_impl (action a, target& t) + { + // Implementation with some multi-threading ideas in mind. + // + switch (t.raw_state) + { + case target_state::group: // Means group's state is unknown. + case target_state::unknown: + case target_state::postponed: + { + auto g ( + make_exception_guard ( + [a, &t]() + { + t.raw_state = target_state::failed; + info << "while " << diag_doing (a, t); + })); + + target_state ts (t.recipe (a) (a, t)); + assert (ts != target_state::unknown && ts != target_state::failed); + + // Set the target's state unless it should be the group's state. + // + if (t.raw_state != target_state::group) + t.raw_state = ts; + + return ts; + } + case target_state::unchanged: + case target_state::changed: + // Should have been handled by inline execute(). + assert (false); + case target_state::failed: + break; + } + + throw failed (); + } + + target_state + execute_prerequisites (action a, target& t) + { + target_state r (target_state::unchanged); + + for (target* pt: t.prerequisite_targets) + { + if (pt == nullptr) // Skipped. + continue; + + r |= execute (a, *pt); + } + + return r; + } + + target_state + reverse_execute_prerequisites (action a, target& t) + { + target_state r (target_state::unchanged); + + for (target* pt: reverse_iterate (t.prerequisite_targets)) + { + if (pt == nullptr) // Skipped. + continue; + + r |= execute (a, *pt); + } + + return r; + } + + bool + execute_prerequisites (action a, target& t, const timestamp& mt) + { + bool e (mt == timestamp_nonexistent); + + for (target* pt: t.prerequisite_targets) + { + if (pt == nullptr) // Skipped. + continue; + + target_state ts (execute (a, *pt)); + + if (!e) + { + // If this is an mtime-based target, then compare timestamps. + // + if (auto mpt = dynamic_cast (pt)) + { + timestamp mp (mpt->mtime ()); + + // What do we do if timestamps are equal? This can happen, for + // example, on filesystems that don't have subsecond resolution. + // There is not much we can do here except detect the case where + // the prerequisite was changed in this run which means the + // action must be executed on the target as well. + // + if (mt < mp || (mt == mp && ts == target_state::changed)) + e = true; + } + else + { + // Otherwise we assume the prerequisite is newer if it was changed. + // + if (ts == target_state::changed) + e = true; + } + } + } + + return e; + } + + target_state + noop_action (action, target&) + { + assert (false); // We shouldn't be called, see target::recipe(). + return target_state::unchanged; + } + + target_state + group_action (action a, target& t) + { + target_state r (execute (a, *t.group)); + + // Indicate to the standard execute() logic that this target's + // state comes from the group. + // + t.raw_state = target_state::group; + + return r; + } + + target_state + default_action (action a, target& t) + { + return current_mode == execution_mode::first + ? execute_prerequisites (a, t) + : reverse_execute_prerequisites (a, t); + } + + target_state + perform_clean (action a, target& t) + { + // The reverse order of update: first delete the file, then clean + // prerequisites. + // + file& ft (dynamic_cast (t)); + + target_state r (rmfile (ft.path (), ft) + ? target_state::changed + : target_state::unchanged); + + // Update timestamp in case there are operations after us that + // could use the information. + // + ft.mtime (timestamp_nonexistent); + + // Clean prerequisites. + // + r |= reverse_execute_prerequisites (a, t); + + return r; + } +} diff --git a/build2/algorithm.ixx b/build2/algorithm.ixx new file mode 100644 index 0000000..92cc66d --- /dev/null +++ b/build2/algorithm.ixx @@ -0,0 +1,197 @@ +// file : build2/algorithm.ixx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include // pair + +#include +#include +#include + +namespace build2 +{ + inline target& + search (prerequisite& p) + { + if (p.target == nullptr) + p.target = &search (p.key ()); + + return *p.target; + } + + inline target& + search (const target_type& t, const prerequisite_key& k) + { + return search ( + prerequisite_key + {k.proj, {&t, k.tk.dir, k.tk.name, k.tk.ext}, k.scope}); + } + + inline target& + search (const target_type& type, + const dir_path& dir, + const std::string& name, + const std::string* ext, + scope* scope) + { + return search ( + prerequisite_key {nullptr, {&type, &dir, &name, &ext}, scope}); + } + + template + inline T& + search (const dir_path& dir, + const std::string& name, + const std::string* ext, + scope* scope) + { + return static_cast (search (T::static_type, dir, name, ext, scope)); + } + + std::pair + match_impl (action, target&, bool apply); + + inline void + match (action a, target& t) + { + if (!t.recipe (a)) + match_impl (a, t, true); + + t.dependents++; + dependency_count++; + + // text << "M " << t << ": " << t.dependents << " " << dependency_count; + } + + inline void + unmatch (action, target& t) + { + // text << "U " << t << ": " << t.dependents << " " << dependency_count; + + assert (t.dependents != 0 && dependency_count != 0); + t.dependents--; + dependency_count--; + } + + inline void + match_only (action a, target& t) + { + if (!t.recipe (a)) + match_impl (a, t, false); + } + + inline std::pair + match_delegate (action a, target& t) + { + auto rp (match_impl (a, t, false)); + const match_result& mr (rp.second); + return std::make_pair (rp.first->apply (mr.recipe_action, t, mr), + mr.recipe_action); + } + + group_view + resolve_group_members_impl (action, target&); + + inline group_view + resolve_group_members (action a, target& g) + { + group_view r (g.group_members (a)); + return r.members != nullptr ? r : resolve_group_members_impl (a, g); + } + + inline void + search_and_match_prerequisites (action a, target& t) + { + search_and_match_prerequisites ( + a, + t, + a.operation () != clean_id + ? dir_path () + : t.strong_scope ().out_path ()); + } + + inline void + search_and_match_prerequisite_members (action a, target& t) + { + if (a.operation () != clean_id) + search_and_match_prerequisite_members (a, t, dir_path ()); + else + // Note that here we don't iterate over members even for see- + // through groups since the group target should clean eveything + // up. A bit of an optimization. + // + search_and_match_prerequisites (a, t, t.strong_scope ().out_path ()); + } + + target_state + execute_impl (action, target&); + + inline target_state + execute (action a, target& t) + { + // text << "E " << t << ": " << t.dependents << " " << dependency_count; + + if (dependency_count != 0) // Re-examination of a postponed target? + { + assert (t.dependents != 0); + t.dependents--; + dependency_count--; + } + + switch (target_state ts = t.state ()) + { + case target_state::unchanged: + case target_state::changed: + return ts; + default: + { + // Handle the "last" execution mode. + // + // This gets interesting when we consider interaction with + // groups. It seem to make sense to treat group members as + // dependents of the group, so, for example, if we try to + // clean the group via three of its members, only the last + // attempt will actually execute the clean. This means that + // when we match a group member, inside we should also match + // the group in order to increment the dependents count. This + // seems to be a natural requirement: if we are delegating to + // the group, we need to find a recipe for it, just like we + // would for a prerequisite. + // + // Note that below we are going to change the group state + // to postponed. This is not a mistake: until we execute + // the recipe, we want to keep returning postponed. And + // once the recipe is executed, it will reset the state + // to group (see group_action()). To put it another way, + // the execution of this member is postponed, not of the + // group. + // + // One important invariant to keep in mind: the return + // value from execute() should always be the same as what + // would get returned by a subsequent call to state(). + // + if (current_mode == execution_mode::last && t.dependents != 0) + return (t.raw_state = target_state::postponed); + + return execute_impl (a, t); + } + } + } + + inline target_state + execute_delegate (const recipe& r, action a, target& t) + { + return r (a, t); + } + + inline target_state + execute_direct (action a, target& t) + { + switch (target_state ts = t.state ()) + { + case target_state::unchanged: + case target_state::changed: return ts; + default: return execute_impl (a, t); + } + } +} diff --git a/build2/algorithm.txx b/build2/algorithm.txx new file mode 100644 index 0000000..7b0e498 --- /dev/null +++ b/build2/algorithm.txx @@ -0,0 +1,58 @@ +// file : build2/algorithm.txx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +namespace build2 +{ + template + T* + execute_prerequisites (action a, target& t, const timestamp& mt) + { + //@@ Can factor the bulk of it into a non-template code. Can + // either do a function template that will do dynamic_cast check + // or can scan the target type info myself. I think latter. + // + bool e (mt == timestamp_nonexistent); + T* r (nullptr); + + for (target* pt: t.prerequisite_targets) + { + if (pt == nullptr) // Skip ignored. + continue; + + target_state ts (execute (a, *pt)); + + if (!e) + { + // If this is an mtime-based target, then compare timestamps. + // + if (auto mpt = dynamic_cast (pt)) + { + timestamp mp (mpt->mtime ()); + + // What do we do if timestamps are equal? This can happen, for + // example, on filesystems that don't have subsecond resolution. + // There is not much we can do here except detect the case where + // the prerequisite was changed in this run which means the + // action must be executed on the target as well. + // + if (mt < mp || (mt == mp && ts == target_state::changed)) + e = true; + } + else + { + // Otherwise we assume the prerequisite is newer if it was changed. + // + if (ts == target_state::changed) + e = true; + } + } + + if (T* tmp = dynamic_cast (pt)) + r = tmp; + } + + assert (r != nullptr); + return e ? r : nullptr; + } +} diff --git a/build2/b.cxx b/build2/b.cxx new file mode 100644 index 0000000..42c59f7 --- /dev/null +++ b/build2/b.cxx @@ -0,0 +1,855 @@ +// file : build2/b.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include // tzset() +#include // strerror() + +#include // getenv() +#include // getuid() +#include // uid_t +#include // struct passwd, getpwuid() + +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +using namespace std; + +#include +#include +#include +#include +#include +#include +#include + +using namespace build2; + +int +main (int argc, char* argv[]) +{ + try + { + tracer trace ("main"); + + cl::argv_scanner scan (argc, argv, true); + options ops (scan); + + // Version. + // + if (ops.version ()) + { + cout << "build2 " << BUILD2_VERSION_STR<< endl + << "libbutl " << LIBBUTL_VERSION_STR << endl + << "Copyright (c) 2014-2015 Code Synthesis Ltd" << endl + << "This is free software released under the MIT license." << endl; + return 0; + } + + // Help. + // + if (ops.help ()) + { + ostream& o (cout); + + o << "Usage: " << argv[0] << " [options] [variables] [buildspec]" << endl + << "Options:" << endl; + + options::print_usage (o); + return 0; + } + + // Diagnostics verbosity. + // + verb = ops.verbose_specified () + ? ops.verbose () + : ops.v () ? 2 : ops.q () ? 0 : 1; + + // Initialize time conversion data that is used by localtime_r(). + // + tzset (); + + // Register builtin modules. + // + builtin_modules["config"] = module_functions {&config::config_boot, + &config::config_init}; + builtin_modules["dist"] = module_functions {&dist::dist_boot, + &dist::dist_init}; + builtin_modules["test"] = module_functions {&test::test_boot, + &test::test_init}; + builtin_modules["install"] = module_functions {&install::install_boot, + &install::install_init}; + + builtin_modules["bin"] = module_functions {nullptr, &bin::bin_init}; + builtin_modules["cxx"] = module_functions {nullptr, &cxx::cxx_init}; + builtin_modules["cli"] = module_functions {nullptr, &cli::cli_init}; + + // Figure out work and home directories. + // + work = dir_path::current (); + + if (const char* h = getenv ("HOME")) + home = dir_path (h); + else + { + struct passwd* pw (getpwuid (getuid ())); + + if (pw == nullptr) + { + const char* msg (strerror (errno)); + fail << "unable to determine home directory: " << msg; + } + + home = dir_path (pw->pw_dir); + } + + if (verb >= 5) + { + trace << "work dir: " << work; + trace << "home dir: " << home; + } + + // Initialize the dependency state. + // + reset (); + + // Parse command line variables. They should come before the + // buildspec. + // + int argi (1); + for (; argi != argc; argi++) + { + const char* s (argv[argi]); + + istringstream is (s); + is.exceptions (istringstream::failbit | istringstream::badbit); + lexer l (is, ""); + token t (l.next ()); + + if (t.type == token_type::eos) + continue; // Whitespace-only argument. + + // Unless this is a name followed by = or +=, assume it is + // a start of the buildspec. + // + if (t.type != token_type::name) + break; + + token_type tt (l.next ().type); + + if (tt != token_type::equal && + tt != token_type::equal_plus && + tt != token_type::plus_equal) + break; + + parser p; + t = p.parse_variable (l, *global_scope, t.value, tt); + + if (t.type != token_type::eos) + fail << "unexpected " << t << " in variable " << s; + } + + // Parse the buildspec. + // + buildspec bspec; + { + // Merge all the individual buildspec arguments into a single + // string. Instead, we could also parse them individually ( + // and merge the result). The benefit of doing it this way + // is potentially better diagnostics (i.e., we could have + // used , to give the idea about + // which argument is invalid). + // + string s; + for (; argi != argc;) + { + s += argv[argi]; + if (++argi != argc) + s += ' '; + } + + try + { + istringstream is (s); + is.exceptions (istringstream::failbit | istringstream::badbit); + + parser p; + bspec = p.parse_buildspec (is, ""); + } + catch (const istringstream::failure&) + { + fail << "unable to parse buildspec '" << s << "'"; + } + } + + level5 ([&]{trace << "buildspec: " << bspec;}); + + if (bspec.empty ()) + bspec.push_back (metaopspec ()); // Default meta-operation. + + for (metaopspec& ms: bspec) + { + if (ms.empty ()) + ms.push_back (opspec ()); // Default operation. + + meta_operation_id mid (0); // Not yet translated. + const meta_operation_info* mif (nullptr); + + bool lifted (false); // See below. + + for (opspec& os: ms) + { + const location l ("", 1, 0); //@@ TODO + + if (os.empty ()) // Default target: dir{}. + os.push_back (targetspec (name ("dir", string ()))); + + operation_id oid (0); // Not yet translated. + const operation_info* oif (nullptr); + + operation_id pre_oid (0); + const operation_info* pre_oif (nullptr); + + operation_id post_oid (0); + const operation_info* post_oif (nullptr); + + // We do meta-operation and operation batches sequentially (no + // parallelism). But multiple targets in an operation batch + // can be done in parallel. + // + action_targets tgs; + tgs.reserve (os.size ()); + + // If the previous operation was lifted to meta-operation, + // end the meta-operation batch. + // + if (lifted) + { + if (mif->meta_operation_post != nullptr) + mif->meta_operation_post (); + + level5 ([&]{trace << "end meta-operation batch " << mif->name + << ", id " << static_cast (mid);}); + + mid = 0; + lifted = false; + } + + for (targetspec& ts: os) + { + name& tn (ts.name); + + // First figure out the out_base of this target. The logic + // is as follows: if a directory was specified in any form, + // then that's the out_base. Otherwise, we check if the name + // value has a directory prefix. This has a good balance of + // control and the expected result in most cases. + // + dir_path out_base (tn.dir); + if (out_base.empty ()) + { + const string& v (tn.value); + + // Handle a few common cases as special: empty name, '.', + // '..', as well as dir{foo/bar} (without trailing '/'). + // This code must be consistent with find_target_type(). + // + if (v.empty () || v == "." || v == ".." || tn.type == "dir") + out_base = dir_path (v); + // + // Otherwise, if this is a simple name, see if there is a + // directory part in value. + // + else if (tn.untyped ()) + { + // We cannot assume it is a valid filesystem name so we + // will have to do the splitting manually. + // + path::size_type i (path::traits::rfind_separator (v)); + + if (i != string::npos) + out_base = dir_path (v, i != 0 ? i : 1); // Special case: "/". + } + } + + if (out_base.relative ()) + out_base = work / out_base; + + out_base.normalize (); + + // The order in which we determine the roots depends on whether + // src_base was specified explicitly. There will also be a few + // cases where we are guessing things that can turn out wrong. + // Keep track of that so that we can issue more extensive + // diagnostics for such cases. + // + bool guessing (false); + dir_path src_root; + dir_path out_root; + + dir_path& src_base (ts.src_base); // Update it in buildspec. + + if (!src_base.empty ()) + { + // Make sure it exists. While we will fail further down + // if it doesn't, the diagnostics could be confusing (e.g., + // unknown operation because we don't load bootstrap.build). + // + if (!dir_exists (src_base)) + fail << "src_base directory " << src_base << " does not exist"; + + if (src_base.relative ()) + src_base = work / src_base; + + src_base.normalize (); + + // If the src_base was explicitly specified, search for src_root. + // + src_root = find_src_root (src_base); + + // If not found, assume this is a simple project with src_root + // being the same as src_base. + // + if (src_root.empty ()) + { + src_root = src_base; + out_root = out_base; + } + else + // Calculate out_root based on src_root/src_base. + // + try + { + out_root = out_base.directory (src_base.leaf (src_root)); + } + catch (const invalid_path&) + { + fail << "out_base suffix does not match src_root" << + info << "src_root: " << src_root << + info << "out_base: " << out_base; + } + } + else + { + // If no src_base was explicitly specified, search for out_root. + // + bool src; + out_root = find_out_root (out_base, &src); + + // If not found (i.e., we have no idea where the roots are), + // then this can mean two things: an in-tree build of a + // simple project or a fresh out-of-tree build. To test for + // the latter, try to find src_root starting from work. If + // we can't, then assume it is the former case. + // + if (out_root.empty ()) + { + src_root = find_src_root (work); + + if (!src_root.empty ()) + { + src_base = work; + + if (src_root != src_base) + { + try + { + out_root = out_base.directory (src_base.leaf (src_root)); + } + catch (const invalid_path&) + { + fail << "out_base directory suffix does not match src_base" + << info << "src_base is " << src_base + << info << "src_root is " << src_root + << info << "out_base is " << out_base + << info << "consider explicitly specifying src_base " + << "for " << tn; + } + } + else + out_root = out_base; + } + else + src_root = src_base = out_root = out_base; + + guessing = true; + } + else if (src) + src_root = out_root; + } + + // Now we know out_root and, if it was explicitly specified + // or the same as out_root, src_root. The next step is to + // create the root scope and load the out_root bootstrap + // files, if any. Note that we might already have done this + // as a result of one of the preceding target processing. + // + // If we know src_root, set that variable as well. This could + // be of use to the bootstrap files (other than src-root.build, + // which, BTW, doesn't need to exist if src_root == out_root). + // + scope& rs (create_root (out_root, src_root)); + + bootstrap_out (rs); + + // See if the bootstrap process set/changed src_root. + // + value& v (rs.assign ("src_root")); + + if (v) + { + // If we also have src_root specified by the user, make + // sure they match. + // + const dir_path& p (as (v)); + + if (src_root.empty ()) + src_root = p; + else if (src_root != p) + fail << "bootstrapped src_root " << p << " does not match " + << "specified " << src_root; + } + else + { + // Neither bootstrap nor the user produced src_root. + // + if (src_root.empty ()) + { + // If it also wasn't explicitly specified, see if it is + // the same as out_root. + // + if (is_src_root (out_root)) + src_root = out_root; + else + { + // If not, then assume we are running from src_base + // and calculate src_root based on out_root/out_base. + // + src_base = work; + src_root = src_base.directory (out_base.leaf (out_root)); + guessing = true; + } + } + + v = src_root; + } + + setup_root (rs); + + // At this stage we should have both roots and out_base figured + // out. If src_base is still undetermined, calculate it. + // + if (src_base.empty ()) + src_base = src_root / out_base.leaf (out_root); + + // Now that we have src_root, load the src_root bootstrap file, + // if there is one. + // + bool bootstrapped (bootstrap_src (rs)); + + // Check that out_root that we have found is the innermost root + // for this project. If it is not, then it means we are trying + // to load a disfigured sub-project and that we do not support. + // Why don't we support it? Because things are already complex + // enough here. + // + if (auto l = rs.vars["subprojects"]) + { + for (const name& n: *l) + { + if (n.pair != '\0') + continue; // Skip project names. + + if (out_base.sub (out_root / n.dir)) + fail << tn << " is in a subproject of " << out_root << + info << "explicitly specify src_base for this target"; + } + } + + // Create and bootstrap outer roots if any. Loading is done + // by load_root_pre() (that would normally be called by the + // meta-operation's load() callback below). + // + create_bootstrap_outer (rs); + + // The src bootstrap should have loaded all the modules that + // may add new meta/operations. So at this stage they should + // all be known. We store the combined action id in uint8_t; + // see for details. + // + assert (operation_table.size () <= 128); + assert (meta_operation_table.size () <= 128); + + // Since we now know all the names of meta-operations and + // operations, "lift" names that we assumed (from buildspec + // syntax) were operations but are actually meta-operations. + // Also convert empty names (which means they weren't explicitly + // specified) to the defaults and verify that all the names are + // known. + // + { + const auto& mn (ms.name); + const auto& on (os.name); + + meta_operation_id m (0); + operation_id o (0); + + if (!on.empty ()) + { + m = meta_operation_table.find (on); + + if (m != 0) + { + if (!mn.empty ()) + fail (l) << "nested meta-operation " << mn + << '(' << on << ')'; + + if (!lifted) // If this is the first target. + { + // End the previous meta-operation batch if there was one + // and start a new one. + // + if (mid != 0) + { + assert (oid == 0); + + if (mif->meta_operation_post != nullptr) + mif->meta_operation_post (); + + level5 ([&]{trace << "end meta-operation batch " + << mif->name << ", id " + << static_cast (mid);}); + + mid = 0; + } + + lifted = true; // Flag to also end it; see above. + } + } + else + { + o = operation_table.find (on); + + if (o == 0) + { + diag_record dr; + dr << fail (l) << "unknown operation " << on; + + // If we guessed src_root and didn't load anything during + // bootstrap, then this is probably a meta-operation that + // would have been added by the module if src_root was + // correct. + // + if (guessing && !bootstrapped) + dr << info << "consider explicitly specifying src_base " + << "for " << tn; + } + } + } + + if (!mn.empty ()) + { + m = meta_operation_table.find (mn); + + if (m == 0) + { + diag_record dr; + dr << fail (l) << "unknown meta-operation " << mn; + + // Same idea as for the operation case above. + // + if (guessing && !bootstrapped) + dr << info << "consider explicitly specifying src_base " + << "for " << tn; + } + } + + // The default meta-operation is perform. The default + // operation is assigned by the meta-operation below. + // + if (m == 0) + m = perform_id; + + // If this is the first target in the meta-operation batch, + // then set the batch meta-operation id. + // + if (mid == 0) + { + mid = m; + mif = rs.meta_operations[m]; + + if (mif == nullptr) + fail (l) << "target " << tn << " does not support meta-" + << "operation " << meta_operation_table[m]; + + level5 ([&]{trace << "start meta-operation batch " << mif->name + << ", id " << static_cast (mid);}); + + if (mif->meta_operation_pre != nullptr) + mif->meta_operation_pre (); + + current_mif = mif; + } + // + // Otherwise, check that all the targets in a meta-operation + // batch have the same meta-operation implementation. + // + else + { + const meta_operation_info* mi (rs.meta_operations[mid]); + + if (mi == nullptr) + fail (l) << "target " << tn << " does not support meta-" + << "operation " << meta_operation_table[mid]; + + if (mi != mif) + fail (l) << "different implementations of meta-operation " + << mif->name << " in the same meta-operation batch"; + } + + // If this is the first target in the operation batch, then set + // the batch operation id. + // + if (oid == 0) + { + auto lookup = + [&rs, &l, &tn] (operation_id o) -> const operation_info* + { + const operation_info* r (rs.operations[o]); + + if (r == nullptr) + fail (l) << "target " << tn << " does not support " + << "operation " << operation_table[o]; + return r; + }; + + if (o == 0) + o = default_id; + + oif = lookup (o); + + level5 ([&]{trace << "start operation batch " << oif->name + << ", id " << static_cast (o);}); + + // Allow the meta-operation to translate the operation. + // + if (mif->operation_pre != nullptr) + oid = mif->operation_pre (o); + else // Otherwise translate default to update. + oid = (o == default_id ? update_id : o); + + if (o != oid) + { + oif = lookup (oid); + level5 ([&]{trace << "operation translated to " << oif->name + << ", id " << static_cast (oid);}); + } + + // Handle pre/post operations. + // + if (oif->pre != nullptr && (pre_oid = oif->pre (mid)) != 0) + { + assert (pre_oid != default_id); + pre_oif = lookup (pre_oid); + } + + if (oif->post != nullptr && (post_oid = oif->post (mid)) != 0) + { + assert (post_oid != default_id); + post_oif = lookup (post_oid); + } + } + // + // Similar to meta-operations, check that all the targets in + // an operation batch have the same operation implementation. + // + else + { + auto check = + [&rs, &l, &tn] (operation_id o, const operation_info* i) + { + const operation_info* r (rs.operations[o]); + + if (r == nullptr) + fail (l) << "target " << tn << " does not support " + << "operation " << operation_table[o]; + + if (r != i) + fail (l) << "different implementations of operation " + << i->name << " in the same operation batch"; + }; + + check (oid, oif); + + if (pre_oid != 0) + check (pre_oid, pre_oif); + + if (post_oid != 0) + check (post_oid, post_oif); + } + } + + if (verb >= 5) + { + trace << "target " << tn << ':'; + trace << " out_base: " << out_base; + trace << " src_base: " << src_base; + trace << " out_root: " << out_root; + trace << " src_root: " << src_root; + } + + path bf (src_base / path ("buildfile")); + + // If we were guessing src_base, check that the buildfile + // exists and if not, issue more detailed diagnostics. + // + if (guessing && !file_exists (bf)) + fail << bf << " does not exist" + << info << "consider explicitly specifying src_base " + << "for " << tn; + + // Load the buildfile. + // + mif->load (bf, rs, out_base, src_base, l); + + // Next search and match the targets. We don't want to start + // building before we know how to for all the targets in this + // operation batch. + // + { + scope& bs (scopes.find (out_base)); + + const string* e; + const target_type* ti (bs.find_target_type (tn, e)); + + if (ti == nullptr) + fail (l) << "unknown target type " << tn.type; + + // If the directory is relative, assume it is relative to work + // (must be consistent with how we derived out_base above). + // + dir_path& d (tn.dir); + + if (d.relative ()) + d = work / d; + + d.normalize (); + + mif->search (rs, target_key {ti, &d, &tn.value, &e}, l, tgs); + } + } + + if (pre_oid != 0) + { + level5 ([&]{trace << "start pre-operation batch " << pre_oif->name + << ", id " << static_cast (pre_oid);}); + + if (mif->operation_pre != nullptr) + mif->operation_pre (pre_oid); // Cannot be translated. + + current_inner_oif = pre_oif; + current_outer_oif = oif; + current_mode = pre_oif->mode; + dependency_count = 0; + + action a (mid, pre_oid, oid); + + mif->match (a, tgs); + mif->execute (a, tgs, true); // Run quiet. + + if (mif->operation_post != nullptr) + mif->operation_post (pre_oid); + + level5 ([&]{trace << "end pre-operation batch " << pre_oif->name + << ", id " << static_cast (pre_oid);}); + } + + current_inner_oif = oif; + current_outer_oif = nullptr; + current_mode = oif->mode; + dependency_count = 0; + + action a (mid, oid, 0); + + mif->match (a, tgs); + mif->execute (a, tgs, verb == 0); + + if (post_oid != 0) + { + level5 ([&]{trace << "start post-operation batch " << post_oif->name + << ", id " << static_cast (post_oid);}); + + if (mif->operation_pre != nullptr) + mif->operation_pre (post_oid); // Cannot be translated. + + current_inner_oif = post_oif; + current_outer_oif = oif; + current_mode = post_oif->mode; + dependency_count = 0; + + action a (mid, post_oid, oid); + + mif->match (a, tgs); + mif->execute (a, tgs, true); // Run quiet. + + if (mif->operation_post != nullptr) + mif->operation_post (post_oid); + + level5 ([&]{trace << "end post-operation batch " << post_oif->name + << ", id " << static_cast (post_oid);}); + } + + if (mif->operation_post != nullptr) + mif->operation_post (oid); + + level5 ([&]{trace << "end operation batch " << oif->name + << ", id " << static_cast (oid);}); + } + + if (mif->meta_operation_post != nullptr) + mif->meta_operation_post (); + + level5 ([&]{trace << "end meta-operation batch " << mif->name + << ", id " << static_cast (mid);}); + } + } + catch (const failed&) + { + return 1; // Diagnostics has already been issued. + } + /* + catch (const std::exception& e) + { + error << e.what (); + return 1; + } + */ +} diff --git a/build2/bin/module b/build2/bin/module new file mode 100644 index 0000000..85be444 --- /dev/null +++ b/build2/bin/module @@ -0,0 +1,23 @@ +// file : build2/bin/module -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_BIN_MODULE +#define BUILD2_BIN_MODULE + +#include +#include + +#include + +namespace build2 +{ + namespace bin + { + extern "C" bool + bin_init ( + scope&, scope&, const location&, unique_ptr&, bool, bool); + } +} + +#endif // BUILD2_BIN_MODULE diff --git a/build2/bin/module.cxx b/build2/bin/module.cxx new file mode 100644 index 0000000..624a8d3 --- /dev/null +++ b/build2/bin/module.cxx @@ -0,0 +1,188 @@ +// file : build2/bin/module.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include +#include +#include + +#include +#include + +#include +#include + +using namespace std; + +namespace build2 +{ + namespace bin + { + static obj_rule obj_; + static lib_rule lib_; + + // Default config.bin.*.lib values. + // + static const strings exe_lib {"shared", "static"}; + static const strings liba_lib {"static"}; + static const strings libso_lib {"shared"}; + + extern "C" bool + bin_init (scope& r, + scope& b, + const location&, + std::unique_ptr&, + bool first, + bool) + { + tracer trace ("bin::init"); + level5 ([&]{trace << "for " << b.out_path ();}); + + // Enter module variables. + // + if (first) + { + auto& v (var_pool); + + v.find ("config.bin.lib", string_type); + v.find ("config.bin.exe.lib", strings_type); + v.find ("config.bin.liba.lib", strings_type); + v.find ("config.bin.libso.lib", strings_type); + v.find ("config.bin.rpath", strings_type); //@@ VAR paths_type + + v.find ("bin.lib", string_type); + v.find ("bin.exe.lib", strings_type); + v.find ("bin.liba.lib", strings_type); + v.find ("bin.libso.lib", strings_type); + v.find ("bin.rpath", strings_type); //@@ VAR paths_type + + v.find ("bin.libprefix", string_type); + } + + // Register target types. + // + { + auto& t (b.target_types); + + t.insert (); + t.insert (); + t.insert (); + t.insert (); + t.insert (); + t.insert (); + t.insert (); + } + + // Register rules. + // + { + auto& r (b.rules); + + r.insert (perform_update_id, "bin.obj", obj_); + r.insert (perform_clean_id, "bin.obj", obj_); + + r.insert (perform_update_id, "bin.lib", lib_); + r.insert (perform_clean_id, "bin.lib", lib_); + + // Configure member. + // + r.insert (configure_update_id, "bin.lib", lib_); + + //@@ Should we check if the install module was loaded + // (by checking if install operation is registered + // for this project)? If we do that, then install + // will have to be loaded before bin. Perhaps we + // should enforce loading of all operation-defining + // modules before all others? + // + r.insert (perform_install_id, "bin.lib", lib_); + } + + // Configure. + // + using config::required; + + // The idea here is as follows: if we already have one of + // the bin.* variables set, then we assume this is static + // project configuration and don't bother setting the + // corresponding config.bin.* variable. + // + //@@ Need to validate the values. Would be more efficient + // to do it once on assignment than every time on query. + // Custom var type? + // + + // config.bin.lib + // + { + value& v (b.assign ("bin.lib")); + if (!v) + v = required (r, "config.bin.lib", "both").first; + } + + // config.bin.exe.lib + // + { + value& v (b.assign ("bin.exe.lib")); + if (!v) + v = required (r, "config.bin.exe.lib", exe_lib).first; + } + + // config.bin.liba.lib + // + { + value& v (b.assign ("bin.liba.lib")); + if (!v) + v = required (r, "config.bin.liba.lib", liba_lib).first; + } + + // config.bin.libso.lib + // + { + value& v (b.assign ("bin.libso.lib")); + if (!v) + v = required (r, "config.bin.libso.lib", libso_lib).first; + } + + // config.bin.rpath + // + // This one is optional and we merge it into bin.rpath, if any. + // See the cxx module for details on merging. + // + if (const value& v = config::optional (r, "config.bin.rpath")) + b.assign ("bin.rpath") += as (v); + + + // Configure "installability" of our target types. + // + install::path (b, dir_path ("bin")); // Install into install.bin. + + // Should shared libraries have executable bit? That depends on + // who you ask. In Debian, for example, it should not unless, it + // really is executable (i.e., has main()). On the other hand, on + // some systems, this may be required in order for the dynamic + // linker to be able to load the library. So, by default, we will + // keep it executable, especially seeing that this is also the + // behavior of autotools. At the same time, it is easy to override + // this, for example: + // + // config.install.lib.mode=644 + // + // And a library that wants to override any such overrides (e.g., + // because it does have main()) can do: + // + // libso{foo}: install.mode=755 + // + // Everyone is happy then? + // + install::path (b, dir_path ("lib")); // Install into install.lib. + + install::path (b, dir_path ("lib")); // Install into install.lib. + install::mode (b, "644"); + + return true; + } + } +} diff --git a/build2/bin/rule b/build2/bin/rule new file mode 100644 index 0000000..ad52396 --- /dev/null +++ b/build2/bin/rule @@ -0,0 +1,39 @@ +// file : build2/bin/rule -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_BIN_RULE +#define BUILD2_BIN_RULE + +#include + +namespace build2 +{ + namespace bin + { + class obj_rule: public rule + { + public: + virtual match_result + match (action, target&, const std::string& hint) const; + + virtual recipe + apply (action, target&, const match_result&) const; + }; + + class lib_rule: public rule + { + public: + virtual match_result + match (action, target&, const std::string& hint) const; + + virtual recipe + apply (action, target&, const match_result&) const; + + static target_state + perform (action, target&); + }; + } +} + +#endif // BUILD2_BIN_RULE diff --git a/build2/bin/rule.cxx b/build2/bin/rule.cxx new file mode 100644 index 0000000..9d93b0a --- /dev/null +++ b/build2/bin/rule.cxx @@ -0,0 +1,145 @@ +// file : build2/bin/rule.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include +#include +#include +#include + +#include + +using namespace std; + +namespace build2 +{ + namespace bin + { + // obj + // + match_result obj_rule:: + match (action a, target& t, const std::string&) const + { + fail << diag_doing (a, t) << " target group" << + info << "explicitly select either obja{} or objso{} member"; + + return nullptr; + } + + recipe obj_rule:: + apply (action, target&, const match_result&) const {return empty_recipe;} + + // lib + // + // The whole logic is pretty much as if we had our two group + // members as our prerequisites. + // + match_result lib_rule:: + match (action a, target& xt, const std::string&) const + { + lib& t (static_cast (xt)); + + // @@ We have to re-query it on each match_only()! + + // Get the library type to build. If not set for a target, this + // should be configured at the project scope by init_lib(). + // + const string& type (as (*t["bin.lib"])); + + bool ar (type == "static" || type == "both"); + bool so (type == "shared" || type == "both"); + + if (!ar && !so) + fail << "unknown library type: " << type << + info << "'static', 'shared', or 'both' expected"; + + // Search and pre-match the members. The pre-match here is part + // of the "library meta-information protocol" that could be used + // by the module that actually builds the members. The idea is + // that pre-matching members may populate our prerequisite_targets + // with prerequisite libraries from which others can extract the + // meta-information about the library, such as the options to use + // when linking it, etc. + // + if (ar) + { + if (t.a == nullptr) + t.a = &search (t.dir, t.name, t.ext, nullptr); + + match_only (a, *t.a); + } + + if (so) + { + if (t.so == nullptr) + t.so = &search (t.dir, t.name, t.ext, nullptr); + + match_only (a, *t.so); + } + + match_result mr (t, &type); + + // If there is an outer operation, indicate that we match + // unconditionally so that we don't override ourselves. + // + if (a.outer_operation () != 0) + mr.recipe_action = action (a.meta_operation (), a.operation ()); + + return mr; + } + + recipe lib_rule:: + apply (action a, target& xt, const match_result& mr) const + { + lib& t (static_cast (xt)); + + const string& type (*static_cast (mr.cpvalue)); + + bool ar (type == "static" || type == "both"); + bool so (type == "shared" || type == "both"); + + // Now we do full match. + // + if (ar) + build2::match (a, *t.a); + + if (so) + build2::match (a, *t.so); + + return &perform; + } + + target_state lib_rule:: + perform (action a, target& xt) + { + lib& t (static_cast (xt)); + + //@@ Not cool we have to do this again. Looks like we need + // some kind of a cache vs resolved pointer, like in + // prerequisite vs prerequisite_target. + // + // + const string& type (as (*t["bin.lib"])); + bool ar (type == "static" || type == "both"); + bool so (type == "shared" || type == "both"); + + target* m1 (ar ? t.a : nullptr); + target* m2 (so ? t.so : nullptr); + + if (current_mode == execution_mode::last) + swap (m1, m2); + + target_state r (target_state::unchanged); + + if (m1 != nullptr) + r |= execute (a, *m1); + + if (m2 != nullptr) + r |= execute (a, *m2); + + return r; + } + } +} diff --git a/build2/bin/target b/build2/bin/target new file mode 100644 index 0000000..e785ac0 --- /dev/null +++ b/build2/bin/target @@ -0,0 +1,99 @@ +// file : build2/bin/target -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_BIN_TARGET +#define BUILD2_BIN_TARGET + +#include + +namespace build2 +{ + namespace bin + { + // The obj{} target group. + // + class obja: public file + { + public: + using file::file; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + + class objso: public file + { + public: + using file::file; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + + class obj: public target + { + public: + using target::target; + + obja* a {nullptr}; + objso* so {nullptr}; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + + class exe: public file + { + public: + using file::file; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + + // The lib{} target group. + // + class liba: public file + { + public: + using file::file; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + + class libso: public file + { + public: + using file::file; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + + class lib: public target + { + public: + using target::target; + + liba* a {nullptr}; + libso* so {nullptr}; + + virtual void + reset (action_type); + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + } +} + +#endif // BUILD2_BIN_TARGET diff --git a/build2/bin/target.cxx b/build2/bin/target.cxx new file mode 100644 index 0000000..6241a7b --- /dev/null +++ b/build2/bin/target.cxx @@ -0,0 +1,190 @@ +// file : build2/bin/target.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +using namespace std; + +namespace build2 +{ + namespace bin + { + static target* + obja_factory (const target_type&, dir_path d, string n, const string* e) + { + obj* o (targets.find (d, n)); + obja* a (new obja (move (d), move (n), e)); + + if ((a->group = o)) + o->a = a; + + return a; + } + + const target_type obja::static_type + { + "obja", + &file::static_type, + &obja_factory, + nullptr, + &search_target, // Note: not _file(); don't look for an existing file. + false + }; + + static target* + objso_factory (const target_type&, dir_path d, string n, const string* e) + { + obj* o (targets.find (d, n)); + objso* so (new objso (move (d), move (n), e)); + + if ((so->group = o)) + o->so = so; + + return so; + } + + const target_type objso::static_type + { + "objso", + &file::static_type, + &objso_factory, + nullptr, + &search_target, // Note: not _file(); don't look for an existing file. + false + }; + + static target* + obj_factory (const target_type&, dir_path d, string n, const string* e) + { + obja* a (targets.find (d, n)); + objso* so (targets.find (d, n)); + obj* o (new obj (move (d), move (n), e)); + + if ((o->a = a)) + a->group = o; + + if ((o->so = so)) + so->group = o; + + return o; + } + + const target_type obj::static_type + { + "obj", + &target::static_type, + &obj_factory, + nullptr, + &search_target, + false + }; + + const target_type exe::static_type + { + "exe", + &file::static_type, + &target_factory, + nullptr, + &search_file, + false + }; + + static target* + liba_factory (const target_type& t, dir_path d, string n, const string* e) + { + // Only link-up to the group if the types match exactly. + // + lib* l (t == liba::static_type ? targets.find (d, n) : nullptr); + liba* a (new liba (move (d), move (n), e)); + + if ((a->group = l)) + l->a = a; + + return a; + } + + // @@ + // + // What extensions should we use? At the outset, this is platform- + // dependent. And if we consider cross-compilation, is it build or + // host-dependent? Feels like it should be host-dependent so that + // we can copy things between cross and native environments. So + // these will have to be determined based on what we are building. + // As if this is not complicated enough, the bin module doesn't + // know anything about building. So perhaps the extension should + // come from a variable that is set not by bin but by the module + // whose rule matched the target (e.g., cxx::link). + // + constexpr const char a_ext[] = "a"; + const target_type liba::static_type + { + "liba", + &file::static_type, + &liba_factory, + &target_extension_fix, + &search_file, + false + }; + + static target* + libso_factory (const target_type& t, dir_path d, string n, const string* e) + { + // Only link-up to the group if the types match exactly. + // + lib* l (t == libso::static_type ? targets.find (d, n) : nullptr); + libso* so (new libso (move (d), move (n), e)); + + if ((so->group = l)) + l->so = so; + + return so; + } + + constexpr const char so_ext[] = "so"; + const target_type libso::static_type + { + "libso", + &file::static_type, + &libso_factory, + &target_extension_fix, + &search_file, + false + }; + + // lib + // + void lib:: + reset (action_type) + { + // Don't clear prerequisite_targets since it is "given" to our + // members to implement "library meta-information protocol". + } + + static target* + lib_factory (const target_type&, dir_path d, string n, const string* e) + { + liba* a (targets.find (d, n)); + libso* so (targets.find (d, n)); + lib* l (new lib (move (d), move (n), e)); + + if ((l->a = a)) + a->group = l; + + if ((l->so = so)) + so->group = l; + + return l; + } + + const target_type lib::static_type + { + "lib", + &target::static_type, + &lib_factory, + nullptr, + &search_target, + false + }; + } +} diff --git a/build2/buildfile b/build2/buildfile new file mode 100644 index 0000000..0ba7535 --- /dev/null +++ b/build2/buildfile @@ -0,0 +1,64 @@ +# file : build2/buildfile +# copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +# license : MIT; see accompanying LICENSE file + +import libs = libbutl%lib{butl} + +exe{b}: \ + {hxx ixx txx cxx}{ algorithm } \ + { cxx}{ b } \ + {hxx txx cxx}{ context } \ + {hxx cxx}{ diagnostics } \ + {hxx cxx}{ dump } \ + {hxx ixx cxx}{ file } \ + {hxx cxx}{ lexer } \ + {hxx cxx}{ module } \ + {hxx cxx}{ name } \ + {hxx cxx}{ operation } \ + {hxx ixx cxx}{ options } \ + {hxx cxx}{ parser } \ + {hxx cxx}{ path-io } \ + {hxx cxx}{ prerequisite } \ + {hxx cxx}{ rule } \ + {hxx }{ rule-map } \ + {hxx cxx}{ scope } \ + {hxx cxx}{ search } \ + {hxx cxx}{ spec } \ + {hxx ixx txx cxx}{ target } \ + {hxx }{ target-key } \ + {hxx }{ target-type } \ + {hxx cxx}{ token } \ + {hxx }{ types } \ + {hxx cxx}{ utility } \ + {hxx ixx txx cxx}{ variable } \ + {hxx }{ version } \ + bin/{hxx cxx}{ module } \ + bin/{hxx cxx}{ rule } \ + bin/{hxx cxx}{ target } \ + cli/{hxx cxx}{ module } \ + cli/{hxx cxx}{ rule } \ + cli/{hxx cxx}{ target } \ + config/{hxx cxx}{ module } \ + config/{hxx cxx}{ operation } \ + config/{hxx ixx txx cxx}{ utility } \ + cxx/{hxx cxx}{ compile } \ + cxx/{hxx cxx}{ install } \ + cxx/{hxx cxx}{ link } \ + cxx/{hxx cxx}{ module } \ + cxx/{hxx cxx}{ target } \ + cxx/{hxx txx cxx}{ utility } \ + dist/{hxx cxx}{ module } \ + dist/{hxx cxx}{ operation } \ + dist/{hxx cxx}{ rule } \ +install/{hxx cxx}{ module } \ +install/{hxx cxx}{ operation } \ +install/{hxx cxx}{ rule } \ +install/{hxx }{ utility } \ + test/{hxx cxx}{ module } \ + test/{hxx cxx}{ operation } \ + test/{hxx cxx}{ rule } \ +$libs + +#@@ TODO, also remove gen files from the repository/add to .gitignore +# +# cli --generate-specifier --cli-namespace cl --include-with-brackets --include-prefix build2 --guard-prefix BUILD2 --hxx-suffix "" options.cli diff --git a/build2/cli/module b/build2/cli/module new file mode 100644 index 0000000..f2890c9 --- /dev/null +++ b/build2/cli/module @@ -0,0 +1,23 @@ +// file : build2/cli/module -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_CLI_MODULE +#define BUILD2_CLI_MODULE + +#include +#include + +#include + +namespace build2 +{ + namespace cli + { + extern "C" bool + cli_init ( + scope&, scope&, const location&, unique_ptr&, bool, bool); + } +} + +#endif // BUILD2_CLI_MODULE diff --git a/build2/cli/module.cxx b/build2/cli/module.cxx new file mode 100644 index 0000000..eafa4a0 --- /dev/null +++ b/build2/cli/module.cxx @@ -0,0 +1,244 @@ +// file : build2/cli/module.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include +#include + +#include +#include +#include +#include + +#include + +#include + +#include +#include + +using namespace std; +using namespace butl; + +namespace build2 +{ + namespace cli + { + static compile compile_; + + extern "C" bool + cli_init (scope& root, + scope& base, + const location& loc, + std::unique_ptr&, + bool first, + bool optional) + { + tracer trace ("cli::init"); + level5 ([&]{trace << "for " << base.out_path ();}); + + // Make sure the cxx module has been loaded since we need its + // targets types (?xx{}). Note that we don't try to load it + // ourselves because of the non-trivial variable merging + // semantics. So it is better to let the user load cxx + // explicitly. + // + { + auto l (base["cxx.loaded"]); + + if (!l || !as (*l)) + fail (loc) << "cxx module must be loaded before cli"; + } + + // Enter module variables. + // + if (first) + { + auto& v (var_pool); + + v.find ("config.cli.configured", bool_type); + + v.find ("config.cli", string_type); //@@ VAR type + + v.find ("config.cli.options", strings_type); + v.find ("cli.options", strings_type); + } + + // Register target types. + // + { + auto& t (base.target_types); + + t.insert (); + t.insert (); + } + + // Configure. + // + // The plan is as follows: try to configure the module. If this fails, + // we are using default values, and the module is optional, leave it + // unconfigured. + // + + // We will only honor optional if the user didn't specify any cli + // configuration explicitly. + // + optional = optional && !config::specified (root, "config.cli"); + + // Don't re-run tests if the configuration says we are unconfigured. + // + if (optional) + { + auto l (root["config.cli.configured"]); + + if (l && !as (*l)) + return false; + } + + // config.cli + // + if (first) + { + // Return version or empty string if unable to execute (e.g., + // the cli executable is not found). + // + auto test = [optional] (const char* cli) -> string + { + const char* args[] = {cli, "--version", nullptr}; + + if (verb >= 2) + print_process (args); + else if (verb) + text << "test " << cli; + + string ver; + try + { + process pr (args, 0, -1); // Open pipe to stdout. + ifdstream is (pr.in_ofd); + + // The version should be the last word on the first line. + // + getline (is, ver); + auto p (ver.rfind (' ')); + if (p != string::npos) + ver = string (ver, p + 1); + + is.close (); // Don't block the other end. + + if (!pr.wait ()) + return string (); // Not found. + + if (ver.empty ()) + fail << "unexpected output from " << cli; + + return ver; + } + catch (const process_error& e) + { + // In some cases this is not enough (e.g., the runtime linker + // will print scary errors if some shared libraries are not + // found. So it would be good to redirect child's STDERR. + // + if (!optional) + error << "unable to execute " << cli << ": " << e.what (); + + if (e.child ()) + exit (1); + + throw failed (); + } + }; + + string ver; + const char* cli ("cli"); // Default. + + if (optional) + { + // Test the default value before setting any config.cli.* values + // so that if we fail to configure, nothing will be written to + // config.build. + // + ver = test (cli); + + if (ver.empty ()) + { + // Note that we are unconfigured so that we don't keep re-testing + // this on each run. + // + root.assign ("config.cli.configured") = false; + + if (verb >= 2) + text << cli << " not found, leaving cli module unconfigured"; + + return false; + } + else + { + auto p (config::required (root, "config.cli", cli)); + assert (p.second && as (p.first) == cli); + } + } + else + { + auto p (config::required (root, "config.cli", cli)); + + // If we actually set a new value, test it by trying to execute. + // + if (p.second) + { + cli = as (p.first).c_str (); + ver = test (cli); + + if (ver.empty ()) + throw failed (); + } + } + + // Clear the unconfigured flag, if any. + // + root.assign ("config.cli.configured") = true; + + if (!ver.empty () && verb >= 2) + text << cli << " " << ver; + } + + // config.cli.options + // + // This one is optional. We also merge it into the corresponding + // cli.* variables. See the cxx module for more information on + // this merging semantics and some of its tricky aspects. + // + if (const value& v = config::optional (root, "config.cli.options")) + base.assign ("cli.options") += as (v); + + // Register our rules. + // + { + auto& r (base.rules); + + r.insert (perform_update_id, "cli.compile", compile_); + r.insert (perform_clean_id, "cli.compile", compile_); + + r.insert (perform_update_id, "cli.compile", compile_); + r.insert (perform_clean_id, "cli.compile", compile_); + + r.insert (perform_update_id, "cli.compile", compile_); + r.insert (perform_clean_id, "cli.compile", compile_); + + r.insert (perform_update_id, "cli.compile", compile_); + r.insert (perform_clean_id, "cli.compile", compile_); + + // Other rules (e.g., cxx::compile) may need to have the group + // members resolved. Looks like a general pattern: groups should + // resolve on configure(update). + // + r.insert (configure_update_id, "cli.compile", compile_); + } + + return true; + } + } +} diff --git a/build2/cli/rule b/build2/cli/rule new file mode 100644 index 0000000..6c154e2 --- /dev/null +++ b/build2/cli/rule @@ -0,0 +1,32 @@ +// file : build2/cli/rule -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_CLI_RULE +#define BUILD2_CLI_RULE + +#include + +namespace build2 +{ + namespace cli + { + class compile: public rule + { + public: + virtual match_result + match (action, target&, const std::string& hint) const; + + virtual recipe + apply (action, target&, const match_result&) const; + + static target_state + perform_update (action, target&); + + static target_state + perform_clean (action, target&); + }; + } +} + +#endif // BUILD2_CLI_RULE diff --git a/build2/cli/rule.cxx b/build2/cli/rule.cxx new file mode 100644 index 0000000..ae1f48a --- /dev/null +++ b/build2/cli/rule.cxx @@ -0,0 +1,305 @@ +// file : build2/cli/rule.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include + +using namespace std; +using namespace butl; + +namespace build2 +{ + namespace cli + { + match_result compile:: + match (action a, target& xt, const std::string&) const + { + tracer trace ("cli::compile::match"); + + if (cli_cxx* pt = xt.is_a ()) + { + // The cli.cxx{} group. + // + cli_cxx& t (*pt); + + // See if we have a .cli source file. + // + match_result r; + for (prerequisite_member p: group_prerequisite_members (a, t)) + { + if (p.is_a ()) + { + // Check that the stems match. + // + if (t.name != p.name ()) + { + level4 ([&]{trace << ".cli file stem '" << p.name () << "' " + << "doesn't match target " << t;}); + return r; + } + + r = p; + break; + } + } + + if (!r) + { + level4 ([&]{trace << "no .cli source file for target " << t;}); + return r; + } + + // If we still haven't figured out the member list, we can do + // that now. Specifically, at this stage, no further changes to + // cli.options are possible and we can determine whether the + // --suppress-inline option is present. + // + if (t.h == nullptr) + { + t.h = &search (t.dir, t.name, nullptr, nullptr); + t.h->group = &t; + + t.c = &search (t.dir, t.name, nullptr, nullptr); + t.c->group = &t; + + if (!config::find_option ("--suppress-inline", t, "cli.options")) + { + t.i = &search (t.dir, t.name, nullptr, nullptr); + t.i->group = &t; + } + } + + return r; + } + else + { + // One of the ?xx{} members. + // + target& t (xt); + + // First see if we are already linked-up to the cli.cxx{} group. + // If it is some other group, then we are definitely not a match. + // + if (t.group != nullptr) + return t.group->is_a (); + + // Then check if there is a corresponding cli.cxx{} group. + // + cli_cxx* g (targets.find (t.dir, t.name)); + + // If not or if it has no prerequisites (happens when we use it to + // set cli.options) and this target has a cli{} prerequisite, then + // synthesize the group. + // + if (g == nullptr || !g->has_prerequisites ()) + { + for (prerequisite_member p: group_prerequisite_members (a, t)) + { + if (p.is_a ()) + { + // Check that the stems match. + // + if (t.name == p.name ()) + { + if (g == nullptr) + g = &targets.insert (t.dir, t.name, trace); + + g->prerequisites.emplace_back (p.as_prerequisite (trace)); + } + else + level4 ([&]{trace << ".cli file stem '" << p.name () << "' " + << "doesn't match target " << t;}); + break; + } + } + } + + if (g != nullptr) + { + // Resolve the group's members. This should link us up to + // the group. + // + resolve_group_members (a, *g); + + // For ixx{}, verify it is part of the group. + // + if (t.is_a () && g->i == nullptr) + { + level4 ([&]{trace << "generation of inline file " << t + << " is disabled with --suppress-inline";}); + g = nullptr; + } + } + + assert (t.group == g); + return g; + } + } + + recipe compile:: + apply (action a, target& xt, const match_result& mr) const + { + if (cli_cxx* pt = xt.is_a ()) + { + cli_cxx& t (*pt); + + // Derive file names for the members. + // + t.h->derive_path (); + t.c->derive_path (); + if (t.i != nullptr) + t.i->derive_path (); + + // Inject dependency on the output directory. + // + inject_parent_fsdir (a, t); + + // Search and match prerequisite members. + // + search_and_match_prerequisite_members (a, t); + + switch (a) + { + case perform_update_id: return &perform_update; + case perform_clean_id: return &perform_clean; + default: return noop_recipe; // Configure update. + } + } + else + { + cli_cxx& g (*static_cast (mr.target)); + build2::match (a, g); + return group_recipe; // Execute the group's recipe. + } + } + + static void + append_extension (cstrings& args, + path_target& t, + const char* option, + const char* default_extension) + { + assert (t.ext != nullptr); // Should have been figured out in apply(). + + if (*t.ext != default_extension) + { + // CLI needs the extension with the leading dot (unless it is empty) + // while we store the extension without. But if there is an extension, + // then we can get it (with the dot) from the file name. + // + args.push_back (option); + args.push_back (t.ext->empty () + ? t.ext->c_str () + : t.path ().extension () - 1); + } + } + + target_state compile:: + perform_update (action a, target& xt) + { + cli_cxx& t (static_cast (xt)); + + // Execute our prerequsites and check if we are out of date. + // + cli* s (execute_prerequisites (a, t, t.mtime ())); + + if (s == nullptr) + return target_state::unchanged; + + // Translate paths to relative (to working directory). This + // results in easier to read diagnostics. + // + path relo (relative (t.dir)); + path rels (relative (s->path ())); + + scope& rs (t.root_scope ()); + const string& cli (as (*rs["config.cli"])); + + cstrings args {cli.c_str ()}; + + // See if we need to pass any --?xx-suffix options. + // + append_extension (args, *t.h, "--hxx-suffix", "hxx"); + append_extension (args, *t.c, "--cxx-suffix", "cxx"); + if (t.i != nullptr) + append_extension (args, *t.i, "--ixx-suffix", "ixx"); + + config::append_options (args, t, "cli.options"); + + if (!relo.empty ()) + { + args.push_back ("-o"); + args.push_back (relo.string ().c_str ()); + } + + args.push_back (rels.string ().c_str ()); + args.push_back (nullptr); + + if (verb >= 2) + print_process (args); + else if (verb) + text << "cli " << *s; + + try + { + process pr (args.data ()); + + if (!pr.wait ()) + throw failed (); + + t.mtime (system_clock::now ()); + } + catch (const process_error& e) + { + error << "unable to execute " << args[0] << ": " << e.what (); + + if (e.child ()) + exit (1); + + throw failed (); + } + + return target_state::changed; + } + + target_state compile:: + perform_clean (action a, target& xt) + { + cli_cxx& t (static_cast (xt)); + + // The reverse order of update: first delete the files, then clean + // prerequisites. Also update timestamp in case there are operations + // after us that could use the information. + // + bool r (false); + + if (t.i != nullptr) + r = rmfile (t.i->path (), *t.i) || r; + r = rmfile (t.c->path (), *t.c) || r; + r = rmfile (t.h->path (), *t.h) || r; + + t.mtime (timestamp_nonexistent); + + target_state ts (r ? target_state::changed : target_state::unchanged); + + // Clean prerequisites. + // + ts |= reverse_execute_prerequisites (a, t); + + return ts; + } + } +} diff --git a/build2/cli/target b/build2/cli/target new file mode 100644 index 0000000..dde20be --- /dev/null +++ b/build2/cli/target @@ -0,0 +1,62 @@ +// file : build2/cli/target -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_CLI_TARGET +#define BUILD2_CLI_TARGET + +#include + +#include + +namespace build2 +{ + namespace cli + { + class cli: public file + { + public: + using file::file; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + + class cli_cxx: public mtime_target + { + public: + using mtime_target::mtime_target; + + union + { + // It is theoretically possible that the compiler will add + // padding between the members of this struct. This would + // mean that the optimal alignment for a pointer is greater + // than its size (and that an array of pointers is sub- + // optimally aligned). We will deal with such a beast of + // an architecture when we see it. + // + struct + { + cxx::hxx* h; + cxx::cxx* c; + cxx::ixx* i; + }; + target* m[3] = {nullptr, nullptr, nullptr}; + }; + + virtual group_view + group_members (action_type) const; + + virtual timestamp + load_mtime () const; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + } +} + +#endif // BUILD2_CLI_TARGET diff --git a/build2/cli/target.cxx b/build2/cli/target.cxx new file mode 100644 index 0000000..ef69e88 --- /dev/null +++ b/build2/cli/target.cxx @@ -0,0 +1,77 @@ +// file : build2/cli/target.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include + +using namespace std; +using namespace butl; + +namespace build2 +{ + namespace cli + { + // cli + // + constexpr const char cli_ext_var[] = "extension"; + constexpr const char cli_ext_def[] = "cli"; + + const target_type cli::static_type + { + "cli", + &file::static_type, + &target_factory, + &target_extension_var, + &search_file, + false + }; + + // cli.cxx + // + group_view cli_cxx:: + group_members (action_type) const + { + return h != nullptr + ? group_view {m, (i != nullptr ? 3U : 2U)} + : group_view {nullptr, 0}; + } + + timestamp cli_cxx:: + load_mtime () const + { + // The rule has been matched which means the members should + // be resolved and paths assigned. + // + return file_mtime (h->path ()); + } + + static target* + cli_cxx_factory (const target_type&, dir_path d, string n, const string* e) + { + tracer trace ("cli::cli_cxx_factory"); + + // Pre-enter (potential) members as targets. The main purpose + // of doing this is to avoid searching for existing files in + // src_base if the buildfile mentions some of them explicitly + // as prerequisites. + // + targets.insert (d, n, trace); + targets.insert (d, n, trace); + targets.insert (d, n, trace); + + return new cli_cxx (move (d), move (n), e); + } + + const target_type cli_cxx::static_type + { + "cli.cxx", + &mtime_target::static_type, + &cli_cxx_factory, + nullptr, + &search_target, + true // "See through" default iteration mode. + }; + } +} diff --git a/build2/config/module b/build2/config/module new file mode 100644 index 0000000..5888376 --- /dev/null +++ b/build2/config/module @@ -0,0 +1,26 @@ +// file : build2/config/module -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_CONFIG_MODULE +#define BUILD2_CONFIG_MODULE + +#include +#include + +#include + +namespace build2 +{ + namespace config + { + extern "C" void + config_boot (scope&, const location&, unique_ptr&); + + extern "C" bool + config_init ( + scope&, scope&, const location&, unique_ptr&, bool, bool); + } +} + +#endif // BUILD2_CONFIG_MODULE diff --git a/build2/config/module.cxx b/build2/config/module.cxx new file mode 100644 index 0000000..3d03a51 --- /dev/null +++ b/build2/config/module.cxx @@ -0,0 +1,90 @@ +// file : build2/config/module.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include + +#include +#include +#include +#include + +#include + +using namespace std; +using namespace butl; + +namespace build2 +{ + namespace config + { + //@@ Same as in operation.cxx + // + static const path config_file ("build/config.build"); + + extern "C" void + config_boot (scope& root, const location&, unique_ptr&) + { + tracer trace ("config::boot"); + + const dir_path& out_root (root.out_path ()); + level5 ([&]{trace << "for " << out_root;}); + + // Register meta-operations. + // + root.meta_operations.insert (configure_id, configure); + root.meta_operations.insert (disfigure_id, disfigure); + + // Load config.build if one exists. + // + // Note that we have to do this during bootstrap since the order in + // which the modules will be initialized is unspecified. So it is + // possible that some module which needs the configuration will get + // called first. + // + path f (out_root / config_file); + + if (file_exists (f)) + source (f, root, root); + } + + extern "C" bool + config_init (scope& root, + scope&, + const location& l, + std::unique_ptr&, + bool first, + bool) + { + tracer trace ("config::init"); + + if (!first) + { + warn (l) << "multiple config module initializations"; + return true; + } + + level5 ([&]{trace << "for " << root.out_path ();}); + + // Register alias and fallback rule for the configure meta-operation. + // + { + // We need this rule for out-of-any-project dependencies (e.g., + // libraries imported from /usr/lib). + // + global_scope->rules.insert ( + configure_id, 0, "config.file", file_rule::instance); + + auto& r (root.rules); + + r.insert (configure_id, 0, "config", fallback_rule::instance); + r.insert (configure_id, 0, "config.file", fallback_rule::instance); + r.insert (configure_id, 0, "config.alias", alias_rule::instance); + } + + return true; + } + } +} diff --git a/build2/config/operation b/build2/config/operation new file mode 100644 index 0000000..63eba7e --- /dev/null +++ b/build2/config/operation @@ -0,0 +1,19 @@ +// file : build2/config/operation -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_CONFIG_OPERATION +#define BUILD2_CONFIG_OPERATION + +#include + +namespace build2 +{ + namespace config + { + extern meta_operation_info configure; + extern meta_operation_info disfigure; + } +} + +#endif // BUILD2_CONFIG_OPERATION diff --git a/build2/config/operation.cxx b/build2/config/operation.cxx new file mode 100644 index 0000000..96d9be7 --- /dev/null +++ b/build2/config/operation.cxx @@ -0,0 +1,455 @@ +// file : build2/config/operation.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace butl; + +namespace build2 +{ + namespace config + { + static const path config_file ("build/config.build"); + + // configure + // + static operation_id + configure_operation_pre (operation_id o) + { + // Don't translate default to update. In our case unspecified + // means configure everything. + // + return o; + } + + static void + save_src_root (const dir_path& out_root, const dir_path& src_root) + { + path f (out_root / src_root_file); + + if (verb) + text << (verb >= 2 ? "config::save_src_root " : "save ") << f; + + try + { + ofstream ofs (f.string ()); + if (!ofs.is_open ()) + fail << "unable to open " << f; + + ofs.exceptions (ofstream::failbit | ofstream::badbit); + + //@@ TODO: quote path + // + ofs << "# Created automatically by the config module." << endl + << "#" << endl + << "src_root = " << src_root << endl; + } + catch (const ofstream::failure&) + { + fail << "unable to write " << f; + } + } + + static void + save_config (scope& root) + { + const dir_path& out_root (root.out_path ()); + path f (out_root / config_file); + + if (verb) + text << (verb >= 2 ? "config::save_config " : "save ") << f; + + try + { + ofstream ofs (f.string ()); + if (!ofs.is_open ()) + fail << "unable to open " << f; + + ofs.exceptions (ofstream::failbit | ofstream::badbit); + + ofs << "# Created automatically by the config module, but" << endl + << "# feel free to edit." << endl + << "#" << endl; + + if (auto l = root.vars["amalgamation"]) + { + const dir_path& d (as (*l)); + + ofs << "# Base configuration inherited from " << d << endl + << "#" << endl; + } + + // Save all the variables in the config namespace that are set + // on the project's root scope. + // + for (auto p (root.vars.find_namespace ("config")); + p.first != p.second; + ++p.first) + { + const variable& var (p.first->first); + const value& val (p.first->second); + const string& n (var.name); + + // Skip special variables. + // + if (n == "config.loaded" || + n == "config.configured") + continue; + + // We will only write config.*.configured if it is false + // (true is implied by its absence). + // + if (n.size () > 11 && + n.compare (n.size () - 11, 11, ".configured") == 0) + { + if (val == nullptr || as (val)) + continue; + } + + // Warn the user if the value that we are saving differs + // from the one they specified on the command line. + // + auto l ((*global_scope)[var]); + if (l.defined () && *l != val) + { + warn << "variable " << var.name << " configured value " + << "differs from command line value" << + info << "reconfigure the project to use command line value"; + } + + if (val) + { + ofs << var.name << " = " << val.data_ << endl; + //text << var.name << " = " << val.data_; + } + else + { + ofs << var.name << " = #[null]" << endl; // @@ TODO: [null] + //text << var.name << " = [null]"; + } + } + } + catch (const ofstream::failure&) + { + fail << "unable to write " << f; + } + } + + static void + configure_project (action a, scope& root) + { + tracer trace ("configure_project"); + + const dir_path& out_root (root.out_path ()); + const dir_path& src_root (root.src_path ()); + + // Make sure the directories exist. + // + if (out_root != src_root) + { + mkdir_p (out_root); + mkdir (out_root / build_dir); + mkdir (out_root / bootstrap_dir); + } + + // We distinguish between a complete configure and operation- + // specific. + // + if (a.operation () == default_id) + { + level5 ([&]{trace << "completely configuring " << out_root;}); + + // Save src-root.build unless out_root is the same as src. + // + if (out_root != src_root) + save_src_root (out_root, src_root); + + // Save config.build. + // + save_config (root); + } + else + { + } + + // Configure subprojects that have been loaded. + // + if (auto l = root.vars["subprojects"]) + { + for (auto p: as (*l)) + { + const dir_path& pd (p.second); + dir_path out_nroot (out_root / pd); + scope& nroot (scopes.find (out_nroot)); + + // @@ Strictly speaking we need to check whether the config + // module was loaded for this subproject. + // + if (nroot.out_path () != out_nroot) // This subproject not loaded. + continue; + + configure_project (a, nroot); + } + } + } + + static void + configure_match (action, action_targets&) + { + // Don't match anything -- see execute (). + } + + static void + configure_execute (action a, const action_targets& ts, bool) + { + // Match rules to configure every operation supported by each + // project. Note that we are not calling operation_pre/post() + // callbacks here since the meta operation is configure and we + // know what we are doing. + // + for (void* v: ts) + { + target& t (*static_cast (v)); + scope* rs (t.base_scope ().root_scope ()); + + if (rs == nullptr) + fail << "out of project target " << t; + + for (operations::size_type id (default_id + 1); // Skip default_id + id < rs->operations.size (); + ++id) + { + const operation_info* oi (rs->operations[id]); + if (oi == nullptr) + continue; + + current_inner_oif = oi; + current_outer_oif = nullptr; + current_mode = oi->mode; + dependency_count = 0; + + match (action (configure_id, id), t); + } + + configure_project (a, *rs); + } + } + + meta_operation_info configure { + "configure", + "configure", + "configuring", + "is configured", + nullptr, // meta-operation pre + &configure_operation_pre, + &load, // normal load + &search, // normal search + &configure_match, + &configure_execute, + nullptr, // operation post + nullptr // meta-operation post + }; + + // disfigure + // + static operation_id + disfigure_operation_pre (operation_id o) + { + // Don't translate default to update. In our case unspecified + // means disfigure everything. + // + return o; + } + + static void + disfigure_load (const path& bf, + scope&, + const dir_path&, + const dir_path&, + const location&) + { + tracer trace ("disfigure_load"); + level6 ([&]{trace << "skipping " << bf;}); + } + + static void + disfigure_search (scope& root, + const target_key&, + const location&, + action_targets& ts) + { + tracer trace ("disfigure_search"); + level6 ([&]{trace << "collecting " << root.out_path ();}); + ts.push_back (&root); + } + + static void + disfigure_match (action, action_targets&) {} + + static bool + disfigure_project (action a, scope& root) + { + tracer trace ("disfigure_project"); + + bool m (false); // Keep track of whether we actually did anything. + + const dir_path& out_root (root.out_path ()); + const dir_path& src_root (root.src_path ()); + + // Disfigure subprojects. Since we don't load buildfiles during + // disfigure, we do it for all known subprojects. + // + if (auto l = root.vars["subprojects"]) + { + for (auto p: as (*l)) + { + const dir_path& pd (p.second); + + // Create and bootstrap subproject's root scope. + // + dir_path out_nroot (out_root / pd); + + // The same logic for src_root as in create_bootstrap_inner(). + // + scope& nroot (create_root (out_nroot, dir_path ())); + bootstrap_out (nroot); + + value& val (nroot.assign ("src_root")); + + if (!val) + val = is_src_root (out_nroot) ? out_nroot : (src_root / pd); + + setup_root (nroot); + + bootstrap_src (nroot); + + m = disfigure_project (a, nroot) || m; + + // We use mkdir_p() to create the out_root of a subproject + // which means there could be empty parent directories left + // behind. Clean them up. + // + if (!pd.simple () && out_root != src_root) + { + for (dir_path d (pd.directory ()); + !d.empty (); + d = d.directory ()) + { + rmdir_status s (rmdir (out_root / d)); + + if (s == rmdir_status::not_empty) + break; // No use trying do remove parent ones. + + m = (s == rmdir_status::success) || m; + } + } + } + } + + // We distinguish between a complete disfigure and operation- + // specific. + // + if (a.operation () == default_id) + { + level5 ([&]{trace << "completely disfiguring " << out_root;}); + + m = rmfile (out_root / config_file) || m; + + if (out_root != src_root) + { + m = rmfile (out_root / src_root_file) || m; + + // Clean up the directories. + // + m = rmdir (out_root / bootstrap_dir) || m; + m = rmdir (out_root / build_dir) || m; + + switch (rmdir (out_root)) + { + case rmdir_status::not_empty: + { + warn << "directory " << out_root << " is " + << (out_root == work + ? "current working directory" + : "not empty") << ", not removing"; + break; + } + case rmdir_status::success: + m = true; + default: + break; + } + } + } + else + { + } + + return m; + } + + static void + disfigure_execute (action a, const action_targets& ts, bool quiet) + { + tracer trace ("disfigure_execute"); + + for (void* v: ts) + { + scope& root (*static_cast (v)); + + if (!disfigure_project (a, root)) + { + // Create a dir{$out_root/} target to signify the project's + // root in diagnostics. Not very clean but seems harmless. + // + target& t ( + targets.insert ( + dir::static_type, root.out_path (), "", nullptr, trace).first); + + if (!quiet) + info << diag_done (a, t); + } + } + } + + static void + disfigure_meta_operation_post () + { + tracer trace ("disfigure_meta_operation_post"); + + // Reset the dependency state since anything that could have been + // loaded earlier using a previous configuration is now invalid. + // + level6 ([&]{trace << "resetting dependency state";}); + reset (); + } + + meta_operation_info disfigure { + "disfigure", + "disfigure", + "disfiguring", + "is disfigured", + nullptr, // meta-operation pre + &disfigure_operation_pre, + &disfigure_load, + &disfigure_search, + &disfigure_match, + &disfigure_execute, + nullptr, // operation post + &disfigure_meta_operation_post + }; + } +} diff --git a/build2/config/utility b/build2/config/utility new file mode 100644 index 0000000..608cf5e --- /dev/null +++ b/build2/config/utility @@ -0,0 +1,128 @@ +// file : build2/config/utility -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_CONFIG_UTILITY +#define BUILD2_CONFIG_UTILITY + +#include +#include // pair +#include // reference_wrapper + +#include +#include +#include + +namespace build2 +{ + class scope; + + namespace config + { + // Set, if necessary, a required config.* variable. + // + // If override is true and the variable doesn't come from this root + // scope or from the command line, then its value is "overridden" + // for this root scope. + // + // Return the reference to the value as well as the indication of + // whether the variable has actually been set. + // + template + std::pair, bool> + required (scope& root, + const variable&, + const T& default_value, + bool override = false); + + template + inline std::pair, bool> + required (scope& root, + const std::string& name, + const T& default_value, + bool override = false) + { + return required (root, var_pool.find (name), default_value, override); + } + + inline std::pair, bool> + required (scope& root, + const std::string& name, + const char* default_value, + bool override = false) + { + return required (root, name, std::string (default_value), override); + } + + // Set, if necessary, an optional config.* variable. In particular, + // an unspecified variable is set to NULL which is used to distinguish + // between the "configured as unspecified" and "not yet configured" + // cases. + // + // Return the value, which can be NULL. + // + const value& + optional (scope& root, const variable&); + + inline const value& + optional (scope& root, const std::string& var) + { + return optional (root, var_pool.find (var)); + } + + // As above but assumes the value is dir_path and makes it + // absolute if the value specified on the command line is + // relative. + // + const value& + optional_absolute (scope& root, const variable&); + + inline const value& + optional_absolute (scope& root, const std::string& var) + { + return optional_absolute (root, var_pool.find (var)); + } + + // Check whether there are any variables specified from the config + // namespace. The idea is that we can check if there are any, say, + // config.install.* values. If there are none, then we can assume + // this functionality is not (yet) used and omit writing a whole + // bunch of NULL config.install.* values to the config.build file. + // We call it omitted/delayed configuration. + // + // Note that this function detects and ignores the special + // config.*.configured variable which may be used by a module to + // "remember" that it is unconfigured. + // + bool + specified (scope& root, const std::string& ns); + + // @@ Why are these here? + // + + // Add all the values from a variable to the C-string list. T is + // either target or scope. The variable is expected to be of type + // strings. + // + template + void + append_options (cstrings& args, T& s, const char* var); + + // As above but from the strings value directly. + // + void + append_options (cstrings& args, const const_strings_value&); + + // Check if a specified option is present in the variable value. + // T is either target or scope. + // + template + bool + find_option (const char* option, T& s, const char* var); + } +} + +#include +#include + +#endif // BUILD2_CONFIG_UTILITY diff --git a/build2/config/utility.cxx b/build2/config/utility.cxx new file mode 100644 index 0000000..361bd5e --- /dev/null +++ b/build2/config/utility.cxx @@ -0,0 +1,92 @@ +// file : build2/config/utility.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include + +using namespace std; + +namespace build2 +{ + namespace config + { + const value& + optional (scope& root, const variable& var) + { + auto l (root[var]); + + return l.defined () + ? l.belongs (*global_scope) ? (root.assign (var) = *l) : *l + : root.assign (var); // NULL + } + + const value& + optional_absolute (scope& root, const variable& var) + { + auto l (root[var]); + + if (!l.defined ()) + return root.assign (var); // NULL + + if (!l.belongs (*global_scope)) // Value from (some) root scope. + return *l; + + // Make the command-line value absolute. This is necessary to avoid + // a warning issued by the config module about global/root scope + // value mismatch. + // + value& v (const_cast (*l)); + + if (v && !v.empty ()) + { + dir_path& d (as (v)); + + if (d.relative ()) + { + d = work / d; + d.normalize (); + } + } + + return root.assign (var) = v; + } + + bool + specified (scope& r, const string& ns) + { + // Search all outer scopes for any value in this namespace. + // + for (scope* s (&r); s != nullptr; s = s->parent_scope ()) + { + for (auto p (s->vars.find_namespace (ns)); + p.first != p.second; + ++p.first) + { + const variable& var (p.first->first); + + // Ignore config.*.configured. + // + if (var.name.size () < 11 || + var.name.compare (var.name.size () - 11, 11, ".configured") != 0) + return true; + } + } + + return false; + } + + void + append_options (cstrings& args, const const_strings_value& sv) + { + if (!sv.empty ()) + { + args.reserve (args.size () + sv.size ()); + + for (const string& s: sv) + args.push_back (s.c_str ()); + } + } + } +} diff --git a/build2/config/utility.ixx b/build2/config/utility.ixx new file mode 100644 index 0000000..b665248 --- /dev/null +++ b/build2/config/utility.ixx @@ -0,0 +1,17 @@ +// file : build2/config/utility.ixx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +namespace build2 +{ + namespace config + { + template + inline void + append_options (cstrings& args, T& s, const char* var) + { + if (auto l = s[var]) + append_options (args, as (*l)); + } + } +} diff --git a/build2/config/utility.txx b/build2/config/utility.txx new file mode 100644 index 0000000..c88a34f --- /dev/null +++ b/build2/config/utility.txx @@ -0,0 +1,45 @@ +// file : build2/config/utility.txx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +namespace build2 +{ + namespace config + { + template + std::pair, bool> + required (scope& root, const variable& var, const T& def_value, bool ovr) + { + using result = std::pair, bool>; + + if (auto l = root[var]) + { + if (l.belongs (*global_scope)) + return result (root.assign (var) = *l, true); + + if (!ovr || l.belongs (root)) + return result (*l, false); + } + + return result (root.assign (var) = def_value, true); + } + + template + bool + find_option (const char* option, T& s, const char* var) + { + if (auto l = s[var]) + { + for (const std::string& s: as (*l)) + { + if (s == option) + return true; + } + } + + return false; + } + } +} diff --git a/build2/context b/build2/context new file mode 100644 index 0000000..22d4b52 --- /dev/null +++ b/build2/context @@ -0,0 +1,165 @@ +// file : build2/context -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_CONTEXT +#define BUILD2_CONTEXT + +#include +#include +#include // uint64_t + +#include + +#include +#include +#include + +namespace build2 +{ + class scope; + class file; + + extern dir_path work; + extern dir_path home; + + extern string_pool path_pool; + extern string_pool extension_pool; + extern string_pool project_name_pool; + + // Current action (meta/operation). + // + extern const meta_operation_info* current_mif; + extern const operation_info* current_inner_oif; + extern const operation_info* current_outer_oif; + + extern execution_mode current_mode; + + // Total number of dependency relationships in the current action. + // Together with the target::dependents count it is incremented + // during the rule search & match phase and is decremented during + // execution with the expectation of it reaching 0. Used as a sanity + // check. + // + extern std::uint64_t dependency_count; + + // Reset the dependency state. In particular, this removes all the + // targets, scopes, and variable names. + // + void + reset (); + + // The dual interface wrapper for the {mk,rm}{file,dir}() functions + // below that allows you to use it as a true/false return or a more + // detailed enum from + // + template + struct fs_status + { + T v; + fs_status (T v): v (v) {}; + operator T () const {return v;} + explicit operator bool () const {return v == T::success;} + }; + + // Create the directory and print the standard diagnostics. Note that + // this implementation is not suitable if it is expected that the + // directory will exist in the majority of case and performance is + // important. See the fsdir{} rule for details. + // + fs_status + mkdir (const dir_path&); + + fs_status + mkdir_p (const dir_path&); + + // Remove the file and print the standard diagnostics. The second + // argument is only used in diagnostics, to print the target name. + // Passing the path for target will result in the relative path + // being printed. + // + template + fs_status + rmfile (const path&, const T& target); + + inline fs_status + rmfile (const path& f) {return rmfile (f, f);} + + // Similar to rmfile() but for directories. + // + template + fs_status + rmdir (const dir_path&, const T& target); + + inline fs_status + rmdir (const dir_path& d) {return rmdir (d, d);} + + // Note that this function returns not_empty if we try to remove + // a working directory. + // + fs_status + rmdir_r (const dir_path&); + + // Return the src/out directory corresponding to the given out/src. The + // passed directory should be a sub-directory of out/src_root. + // + dir_path + src_out (const dir_path& out, scope&); + + dir_path + src_out (const dir_path& out, + const dir_path& out_root, const dir_path& src_root); + + dir_path + out_src (const dir_path& src, scope&); + + dir_path + out_src (const dir_path& src, + const dir_path& out_root, const dir_path& src_root); + + // If possible and beneficial, translate an absolute, normalized path + // into relative to the relative_base directory, which is normally + // work. Note that if the passed path is the same as relative_base, + // then this function returns empty path. + // + template + basic_path + relative (const basic_path&); + + // By default this points to work. Setting this to something else + // should only be done in tightly controlled, non-parallel + // situations (see dump). If base is empty, then relative() + // returns the original path. + // + extern const dir_path* relative_base; + + // In addition to calling relative(), this function also uses shorter + // notations such as '~/'. + // + std::string + diag_relative (const path&); + + // As above but also adds trailing '/'. If the path is the same as + // base, returns "./" if current is true and empty string otherwise. + // + std::string + diag_relative (const dir_path&, bool current = true); + + // Action phrases, e.g., "configure update exe{foo}", "updating exe{foo}", + // and "updating exe{foo} is configured". + // + class target; + + std::string + diag_do (const action&, const target&); + + std::string + diag_doing (const action&, const target&); + + std::string + diag_done (const action&, const target&); +} + +#include + +#endif // BUILD2_CONTEXT diff --git a/build2/context.cxx b/build2/context.cxx new file mode 100644 index 0000000..34408e9 --- /dev/null +++ b/build2/context.cxx @@ -0,0 +1,391 @@ +// file : build2/context.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +using namespace std; +using namespace butl; + +namespace build2 +{ + dir_path work; + dir_path home; + + string_pool path_pool; + string_pool extension_pool; + string_pool project_name_pool; + + const meta_operation_info* current_mif; + const operation_info* current_inner_oif; + const operation_info* current_outer_oif; + execution_mode current_mode; + uint64_t dependency_count; + + void + reset () + { + path_pool.clear (); + extension_pool.clear (); + project_name_pool.clear (); + + targets.clear (); + scopes.clear (); + var_pool.clear (); + + // Reset meta/operation tables. Note that the order should match + // the id constants in . + // + meta_operation_table.clear (); + meta_operation_table.insert ("perform"); + meta_operation_table.insert ("configure"); + meta_operation_table.insert ("disfigure"); + meta_operation_table.insert ("dist"); + + operation_table.clear (); + operation_table.insert ("default"); + operation_table.insert ("update"); + operation_table.insert ("clean"); + operation_table.insert ("test"); + operation_table.insert ("install"); + + // Enter builtin variables. + // + { + auto& v (var_pool); + + v.find ("work", dir_path_type); + v.find ("home", dir_path_type); + + v.find ("src_root", dir_path_type); + v.find ("out_root", dir_path_type); + v.find ("src_base", dir_path_type); + v.find ("out_base", dir_path_type); + + v.find ("project", string_type); + v.find ("amalgamation", dir_path_type); + + // Shouldn't be typed since the value requires pre-processing. + // + v.find ("subprojects", nullptr, '='); + + v.find ("extension", string_type); + } + + // Create global scope. For Win32 this is not a "real" root path. + // On POSIX, however, this is a real path. See the comment in + // for details. + // + global_scope = scopes.insert ( + dir_path ("/"), nullptr, true, false)->second; + + global_scope->assign ("work") = work; + global_scope->assign ("home") = home; + + // Register builtin target types. + // + { + target_type_map& t (global_scope->target_types); + + t.insert (); + t.insert (); + t.insert (); + t.insert (); + t.insert (); + t.insert (); + t.insert (); + } + + // Register builtin rules. + // + { + rule_map& r (global_scope->rules); + + r.insert (perform_id, 0, "alias", alias_rule::instance); + + r.insert (perform_update_id, "fsdir", fsdir_rule::instance); + r.insert (perform_clean_id, "fsdir", fsdir_rule::instance); + + r.insert (perform_update_id, "file", file_rule::instance); + r.insert (perform_clean_id, "file", file_rule::instance); + } + } + + fs_status + mkdir (const dir_path& d) + { + // We don't want to print the command if the directory already + // exists. This makes the below code a bit ugly. + // + mkdir_status ms; + + try + { + ms = try_mkdir (d); + } + catch (const system_error& e) + { + if (verb) + text << "mkdir " << d; + + fail << "unable to create directory " << d << ": " << e.what (); + } + + if (ms == mkdir_status::success) + { + if (verb) + text << "mkdir " << d; + } + + return ms; + } + + fs_status + mkdir_p (const dir_path& d) + { + // We don't want to print the command if the directory already + // exists. This makes the below code a bit ugly. + // + mkdir_status ms; + + try + { + ms = try_mkdir_p (d); + } + catch (const system_error& e) + { + if (verb) + text << "mkdir -p " << d; + + fail << "unable to create directory " << d << ": " << e.what (); + } + + if (ms == mkdir_status::success) + { + if (verb) + text << "mkdir -p " << d; + } + + return ms; + } + + fs_status + rmdir_r (const dir_path& d) + { + using namespace butl; + + if (work.sub (d)) // Don't try to remove working directory. + return rmdir_status::not_empty; + + if (!dir_exists (d)) + return rmdir_status::not_exist; + + if (verb) + text << "rmdir -r " << d; + + try + { + butl::rmdir_r (d); + } + catch (const std::system_error& e) + { + fail << "unable to remove directory " << d << ": " << e.what (); + } + + return rmdir_status::success; + } + + dir_path + src_out (const dir_path& out, scope& s) + { + scope& rs (*s.root_scope ()); + return src_out (out, rs.out_path (), rs.src_path ()); + } + + dir_path + out_src (const dir_path& src, scope& s) + { + scope& rs (*s.root_scope ()); + return out_src (src, rs.out_path (), rs.src_path ()); + } + + dir_path + src_out (const dir_path& o, + const dir_path& out_root, const dir_path& src_root) + { + assert (o.sub (out_root)); + return src_root / o.leaf (out_root); + } + + dir_path + out_src (const dir_path& s, + const dir_path& out_root, const dir_path& src_root) + { + assert (s.sub (src_root)); + return out_root / s.leaf (src_root); + } + + // relative() + // + const dir_path* relative_base = &work; + + string + diag_relative (const path& p) + { + const path& b (*relative_base); + + if (p.absolute ()) + { + if (p == b) + return "."; + +#ifndef _WIN32 + if (p == home) + return "~"; +#endif + + path rb (relative (p)); + +#ifndef _WIN32 + if (rb.relative ()) + { + // See if the original path with the ~/ shortcut is better + // that the relative to base. + // + if (p.sub (home)) + { + path rh (p.leaf (home)); + if (rb.string ().size () > rh.string ().size () + 2) // 2 for '~/' + return "~/" + rh.string (); + } + } + else if (rb.sub (home)) + return "~/" + rb.leaf (home).string (); +#endif + + return rb.string (); + } + + return p.string (); + } + + string + diag_relative (const dir_path& d, bool cur) + { + string r (diag_relative (static_cast (d))); + + // Translate "." to empty. + // + if (!cur && d.absolute () && r == ".") + r.clear (); + + // Add trailing '/'. + // + if (!r.empty () && !dir_path::traits::is_separator (r.back ())) + r += '/'; + + return r; + } + + // diag_do(), etc. + // + string + diag_do (const action&, const target& t) + { + const meta_operation_info& m (*current_mif); + const operation_info& io (*current_inner_oif); + const operation_info* oo (current_outer_oif); + + ostringstream os; + + // perform(update(x)) -> "update x" + // configure(update(x)) -> "configure updating x" + // + if (m.name_do.empty ()) + os << io.name_do << ' '; + else + { + os << m.name_do << ' '; + + if (!io.name_doing.empty ()) + os << io.name_doing << ' '; + } + + if (oo != nullptr) + os << "(for " << oo->name << ") "; + + os << t; + return os.str (); + } + + string + diag_doing (const action&, const target& t) + { + const meta_operation_info& m (*current_mif); + const operation_info& io (*current_inner_oif); + const operation_info* oo (current_outer_oif); + + ostringstream os; + + // perform(update(x)) -> "updating x" + // configure(update(x)) -> "configuring updating x" + // + if (!m.name_doing.empty ()) + os << m.name_doing << ' '; + + if (!io.name_doing.empty ()) + os << io.name_doing << ' '; + + if (oo != nullptr) + os << "(for " << oo->name << ") "; + + os << t; + return os.str (); + } + + string + diag_done (const action&, const target& t) + { + const meta_operation_info& m (*current_mif); + const operation_info& io (*current_inner_oif); + const operation_info* oo (current_outer_oif); + + ostringstream os; + + // perform(update(x)) -> "x is up to date" + // configure(update(x)) -> "updating x is configured" + // + if (m.name_done.empty ()) + { + os << t; + + if (!io.name_done.empty ()) + os << " " << io.name_done; + + if (oo != nullptr) + os << "(for " << oo->name << ") "; + } + else + { + if (!io.name_doing.empty ()) + os << io.name_doing << ' '; + + if (oo != nullptr) + os << "(for " << oo->name << ") "; + + os << t << " " << m.name_done; + } + + return os.str (); + } +} diff --git a/build2/context.txx b/build2/context.txx new file mode 100644 index 0000000..cc37a97 --- /dev/null +++ b/build2/context.txx @@ -0,0 +1,141 @@ +// file : build2/context.txx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include + +namespace build2 +{ + template + fs_status + rmfile (const path& f, const T& t) + { + using namespace butl; + + // We don't want to print the command if we couldn't remove the + // file because it does not exist (just like we don't print the + // update command if the file is up to date). This makes the + // below code a bit ugly. + // + rmfile_status rs; + + try + { + rs = try_rmfile (f); + } + catch (const std::system_error& e) + { + if (verb >= 2) + text << "rm " << f; + else if (verb) + text << "rm " << t; + + fail << "unable to remove file " << f << ": " << e.what (); + } + + if (rs == rmfile_status::success) + { + if (verb >= 2) + text << "rm " << f; + else if (verb) + text << "rm " << t; + } + + return rs; + } + + template + fs_status + rmdir (const dir_path& d, const T& t) + { + using namespace butl; + + bool w (work.sub (d)); // Don't try to remove working directory. + rmdir_status rs; + + // We don't want to print the command if we couldn't remove the + // directory because it does not exist (just like we don't print + // mkdir if it already exists) or if it is not empty. This makes + // the below code a bit ugly. + // + try + { + rs = !w ? try_rmdir (d) : rmdir_status::not_empty; + } + catch (const std::system_error& e) + { + if (verb >= 2) + text << "rmdir " << d; + else if (verb) + text << "rmdir " << t; + + fail << "unable to remove directory " << d << ": " << e.what (); + } + + switch (rs) + { + case rmdir_status::success: + { + if (verb >= 2) + text << "rmdir " << d; + else if (verb) + text << "rmdir " << t; + + break; + } + case rmdir_status::not_empty: + { + if (verb >= 2) + text << "directory " << d << " is " + << (w ? "current working directory" : "not empty") + << ", not removing"; + + break; + } + case rmdir_status::not_exist: + break; + } + + return rs; + } + + template + basic_path + relative (const basic_path& p) + { + typedef basic_path path; + + const dir_path& b (*relative_base); + + if (b.empty ()) + return p; + + if (p.sub (b)) + return p.leaf (b); + + // If base is a sub-path of {src,out}_root and this path is also a + // sub-path of it, then use '..' to form a relative path. + // + // Don't think this is a good heuristic. For example, why shouldn't + // we display paths from imported projects as relative if they are + // more readable than absolute? + // + /* + if ((work.sub (src_root) && p.sub (src_root)) || + (work.sub (out_root) && p.sub (out_root))) + return p.relative (work); + */ + + if (p.root_directory () == b.root_directory ()) + { + path r (p.relative (b)); + + if (r.string ().size () < p.string ().size ()) + return r; + } + + return p; + } +} diff --git a/build2/cxx/compile b/build2/cxx/compile new file mode 100644 index 0000000..42e0f2e --- /dev/null +++ b/build2/cxx/compile @@ -0,0 +1,32 @@ +// file : build2/cxx/compile -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_CXX_COMPILE +#define BUILD2_CXX_COMPILE + +#include +#include + +namespace build2 +{ + namespace cxx + { + class compile: public rule + { + public: + virtual match_result + match (action, target&, const std::string& hint) const; + + virtual recipe + apply (action, target&, const match_result&) const; + + static target_state + perform_update (action, target&); + + static compile instance; + }; + } +} + +#endif // BUILD2_CXX_COMPILE diff --git a/build2/cxx/compile.cxx b/build2/cxx/compile.cxx new file mode 100644 index 0000000..bc332b4 --- /dev/null +++ b/build2/cxx/compile.cxx @@ -0,0 +1,794 @@ +// file : build2/cxx/compile.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include +#include +#include // size_t +#include // exit() +#include // move() + +#include +#include // reverse_iterate +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +using namespace std; +using namespace butl; + +namespace build2 +{ + namespace cxx + { + using namespace bin; + + match_result compile:: + match (action a, target& t, const string&) const + { + tracer trace ("cxx::compile::match"); + + // @@ TODO: + // + // - check prerequisites: single source file + // - if path already assigned, verify extension? + // + + // See if we have a C++ source file. Iterate in reverse so that + // a source file specified for an obj*{} member overrides the one + // specified for the group. Also "see through" groups. + // + for (prerequisite_member p: reverse_group_prerequisite_members (a, t)) + { + if (p.is_a ()) + return p; + } + + level4 ([&]{trace << "no c++ source file for target " << t;}); + return nullptr; + } + + static void + inject_prerequisites (action, target&, cxx&, scope&); + + recipe compile:: + apply (action a, target& xt, const match_result& mr) const + { + path_target& t (static_cast (xt)); + + // Derive file name from target name. + // + if (t.path ().empty ()) + t.derive_path ("o", nullptr, (t.is_a () ? "-so" : nullptr)); + + // Inject dependency on the output directory. + // + inject_parent_fsdir (a, t); + + // Search and match all the existing prerequisites. The injection + // code (below) takes care of the ones it is adding. + // + // When cleaning, ignore prerequisites that are not in the same + // or a subdirectory of our strong amalgamation. + // + const dir_path* amlg ( + a.operation () != clean_id + ? nullptr + : &t.strong_scope ().out_path ()); + + link::search_paths_cache lib_paths; // Extract lazily. + + for (prerequisite_member p: group_prerequisite_members (a, t)) + { + // A dependency on a library is there so that we can get its + // cxx.export.poptions. In particular, making sure it is + // executed before us will only restrict parallelism. But we + // do need to pre-match it in order to get its + // prerequisite_targets populated. This is the "library + // meta-information protocol". See also append_lib_options() + // above. + // + if (p.is_a () || p.is_a () || p.is_a ()) + { + if (a.operation () == update_id) + { + // Handle imported libraries. We know that for such libraries + // we don't need to do match() in order to get options (if + // any, they would be set by search_library()). + // + if (p.proj () == nullptr || + link::search_library (lib_paths, p.prerequisite) == nullptr) + { + match_only (a, p.search ()); + } + } + + continue; + } + + target& pt (p.search ()); + + if (a.operation () == clean_id && !pt.dir.sub (*amlg)) + continue; + + build2::match (a, pt); + t.prerequisite_targets.push_back (&pt); + } + + // Inject additional prerequisites. We only do it when + // performing update since chances are we will have to + // update some of our prerequisites in the process (auto- + // generated source code). + // + if (a == perform_update_id) + { + // The cached prerequisite target should be the same as what + // is in t.prerequisite_targets since we used standard + // search() and match() above. + // + // @@ Ugly. + // + cxx& st ( + dynamic_cast ( + mr.target != nullptr ? *mr.target : *mr.prerequisite->target)); + inject_prerequisites (a, t, st, mr.prerequisite->scope); + } + + switch (a) + { + case perform_update_id: return &perform_update; + case perform_clean_id: return &perform_clean; + default: return noop_recipe; // Configure update. + } + } + + // Reverse-lookup target type from extension. + // + static const target_type* + map_extension (scope& s, const string& n, const string& e) + { + // We will just have to try all of the possible ones, in the + // "most likely to match" order. + // + const variable& var (var_pool.find ("extension")); + + auto test = [&s, &n, &e, &var] (const target_type& tt) + -> const target_type* + { + if (auto l = s.lookup (tt, n, var)) + if (as (*l) == e) + return &tt; + + return nullptr; + }; + + if (auto r = test (hxx::static_type)) return r; + if (auto r = test (h::static_type)) return r; + if (auto r = test (ixx::static_type)) return r; + if (auto r = test (txx::static_type)) return r; + if (auto r = test (cxx::static_type)) return r; + if (auto r = test (c::static_type)) return r; + + return nullptr; + } + + // Mapping of include prefixes (e.g., foo in ) for auto- + // generated headers to directories where they will be generated. + // + // We are using a prefix map of directories (dir_path_map) instead + // of just a map in order also cover sub-paths (e.g., + // if we continue with the example). Specifically, we need to make + // sure we don't treat foobar as a sub-directory of foo. + // + // @@ The keys should be canonicalized. + // + using prefix_map = dir_path_map; + + static void + append_prefixes (prefix_map& m, target& t, const char* var) + { + tracer trace ("cxx::append_prefixes"); + + // If this target does not belong to any project (e.g, an + // "imported as installed" library), then it can't possibly + // generate any headers for us. + // + scope* rs (t.base_scope ().root_scope ()); + if (rs == nullptr) + return; + + const dir_path& out_base (t.dir); + const dir_path& out_root (rs->out_path ()); + + if (auto l = t[var]) + { + const auto& v (as (*l)); + + for (auto i (v.begin ()), e (v.end ()); i != e; ++i) + { + // -I can either be in the "-Ifoo" or "-I foo" form. + // + dir_path d; + if (*i == "-I") + { + if (++i == e) + break; // Let the compiler complain. + + d = dir_path (*i); + } + else if (i->compare (0, 2, "-I") == 0) + d = dir_path (*i, 2, string::npos); + else + continue; + + level6 ([&]{trace << "-I '" << d << "'";}); + + // If we are relative or not inside our project root, then + // ignore. + // + if (d.relative () || !d.sub (out_root)) + continue; + + // If the target directory is a sub-directory of the include + // directory, then the prefix is the difference between the + // two. Otherwise, leave it empty. + // + // The idea here is to make this "canonical" setup work auto- + // magically: + // + // 1. We include all files with a prefix, e.g., . + // 2. The library target is in the foo/ sub-directory, e.g., + // /tmp/foo/. + // 3. The poptions variable contains -I/tmp. + // + dir_path p (out_base.sub (d) ? out_base.leaf (d) : dir_path ()); + + auto j (m.find (p)); + + if (j != m.end ()) + { + if (j->second != d) + { + // We used to reject duplicates but it seems this can + // be reasonably expected to work according to the order + // of the -I options. + // + if (verb >= 4) + trace << "overriding dependency prefix '" << p << "'\n" + << " old mapping to " << j->second << "\n" + << " new mapping to " << d; + + j->second = d; + } + } + else + { + level6 ([&]{trace << "'" << p << "' = '" << d << "'";}); + m.emplace (move (p), move (d)); + } + } + } + } + + // Append library prefixes based on the cxx.export.poptions variables + // recursively, prerequisite libraries first. + // + static void + append_lib_prefixes (prefix_map& m, target& l) + { + for (target* t: l.prerequisite_targets) + { + if (t == nullptr) + continue; + + if (t->is_a () || t->is_a () || t->is_a ()) + append_lib_prefixes (m, *t); + } + + append_prefixes (m, l, "cxx.export.poptions"); + } + + static prefix_map + build_prefix_map (target& t) + { + prefix_map m; + + // First process the include directories from prerequisite + // libraries. Note that here we don't need to see group + // members (see apply()). + // + for (prerequisite& p: group_prerequisites (t)) + { + target& pt (*p.target); // Already searched and matched. + + if (pt.is_a () || pt.is_a () || pt.is_a ()) + append_lib_prefixes (m, pt); + } + + // Then process our own. + // + append_prefixes (m, t, "cxx.poptions"); + + return m; + } + + // Return the next make prerequisite starting from the specified + // position and update position to point to the start of the + // following prerequisite or l.size() if there are none left. + // + static string + next (const string& l, size_t& p) + { + size_t n (l.size ()); + + // Skip leading spaces. + // + for (; p != n && l[p] == ' '; p++) ; + + // Lines containing multiple prerequisites are 80 characters max. + // + string r; + r.reserve (n); + + // Scan the next prerequisite while watching out for escape sequences. + // + for (; p != n && l[p] != ' '; p++) + { + char c (l[p]); + + if (c == '\\') + c = l[++p]; + + r += c; + } + + // Skip trailing spaces. + // + for (; p != n && l[p] == ' '; p++) ; + + // Skip final '\'. + // + if (p == n - 1 && l[p] == '\\') + p++; + + return r; + } + + static void + inject_prerequisites (action a, target& t, cxx& s, scope& ds) + { + tracer trace ("cxx::compile::inject_prerequisites"); + + scope& rs (t.root_scope ()); + const string& cxx (as (*rs["config.cxx"])); + + cstrings args {cxx.c_str ()}; + + // Add cxx.export.poptions from prerequisite libraries. Note + // that here we don't need to see group members (see apply()). + // + for (prerequisite& p: group_prerequisites (t)) + { + target& pt (*p.target); // Already searched and matched. + + if (pt.is_a () || pt.is_a () || pt.is_a ()) + append_lib_options (args, pt, "cxx.export.poptions"); + } + + append_options (args, t, "cxx.poptions"); + + // @@ Some C++ options (e.g., -std, -m) affect the preprocessor. + // Or maybe they are not C++ options? Common options? + // + append_options (args, t, "cxx.coptions"); + + string std; // Storage. + append_std (args, t, std); + + if (t.is_a ()) + args.push_back ("-fPIC"); + + args.push_back ("-M"); // Note: -MM -MG skips missing <>-included. + args.push_back ("-MG"); // Treat missing headers as generated. + args.push_back ("-MQ"); // Quoted target name. + args.push_back ("*"); // Old versions can't handle empty target name. + + // We are using absolute source file path in order to get absolute + // paths in the result. Any relative paths in the result are non- + // existent, potentially auto-generated headers. + // + // @@ We will also have to use absolute -I paths to guarantee + // that. Or just detect relative paths and error out? + // + args.push_back (s.path ().string ().c_str ()); + args.push_back (nullptr); + + level6 ([&]{trace << "target: " << t;}); + + // Build the prefix map lazily only if we have non-existent files. + // Also reuse it over restarts since it doesn't change. + // + prefix_map pm; + + // If any prerequisites that we have extracted changed, then we + // have to redo the whole thing. The reason for this is auto- + // generated headers: the updated header may now include a yet- + // non-existent header. Unless we discover this and generate it + // (which, BTW, will trigger another restart since that header, + // in turn, can also include auto-generated headers), we will + // end up with an error during compilation proper. + // + // One complication with this restart logic is that we will see + // a "prefix" of prerequisites that we have already processed + // (i.e., they are already in our prerequisite_targets list) and + // we don't want to keep redoing this over and over again. One + // thing to note, however, is that the prefix that we have seen + // on the previous run must appear exactly the same in the + // subsequent run. The reason for this is that none of the files + // that it can possibly be based on have changed and thus it + // should be exactly the same. To put it another way, the + // presence or absence of a file in the dependency output can + // only depend on the previous files (assuming the compiler + // outputs them as it encounters them and it is hard to think + // of a reason why would someone do otherwise). And we have + // already made sure that all those files are up to date. And + // here is the way we are going to exploit this: we are going + // to keep track of how many prerequisites we have processed so + // far and on restart skip right to the next one. + // + // Also, before we do all that, make sure the source file itself + // if up to date. + // + execute_direct (a, s); + + size_t skip_count (0); + for (bool restart (true); restart; ) + { + restart = false; + + if (verb >= 3) + print_process (args); + + try + { + process pr (args.data (), 0, -1); // Open pipe to stdout. + ifdstream is (pr.in_ofd); + + size_t skip (skip_count); + for (bool first (true), second (true); !(restart || is.eof ()); ) + { + string l; + getline (is, l); + + if (is.fail () && !is.eof ()) + fail << "error reading C++ compiler -M output"; + + size_t pos (0); + + if (first) + { + // Empty output should mean the wait() call below will return + // false. + // + if (l.empty ()) + break; + + assert (l[0] == '*' && l[1] == ':' && l[2] == ' '); + + first = false; + + // While normally we would have the source file on the + // first line, if too long, it will be moved to the next + // line and all we will have on this line is "*: \". + // + if (l.size () == 4 && l[3] == '\\') + continue; + else + pos = 3; // Skip "*: ". + + // Fall through to the 'second' block. + } + + if (second) + { + second = false; + next (l, pos); // Skip the source file. + } + + // If things go wrong (and they often do in this area), give + // the user a bit extra context. + // + auto g ( + make_exception_guard ( + [&s]() + { + info << "while extracting dependencies from " << s; + })); + + while (pos != l.size ()) + { + string fs (next (l, pos)); + + // Skip until where we left off. + // + if (skip != 0) + { + skip--; + continue; + } + + path f (move (fs)); + f.normalize (); + + if (!f.absolute ()) + { + // This is probably as often an error as an auto-generated + // file, so trace at level 4. + // + level4 ([&]{trace << "non-existent header '" << f << "'";}); + + // If we already did it and build_prefix_map() returned empty, + // then we would have failed below. + // + if (pm.empty ()) + pm = build_prefix_map (t); + + // First try the whole file. Then just the directory. + // + // @@ Has to be a separate map since the prefix can be + // the same as the file name. + // + // auto i (pm.find (f)); + + // Find the most qualified prefix of which we are a + // sub-path. + // + auto i (pm.end ()); + + if (!pm.empty ()) + { + const dir_path& d (f.directory ()); + i = pm.upper_bound (d); + + // Get the greatest less than, if any. We might + // still not be a sub. Note also that we still + // have to check the last element is upper_bound() + // returned end(). + // + if (i == pm.begin () || !d.sub ((--i)->first)) + i = pm.end (); + } + + if (i == pm.end ()) + fail << "unable to map presumably auto-generated header '" + << f << "' to a project"; + + f = i->second / f; + } + + level6 ([&]{trace << "injecting " << f;}); + + // Split the name into its directory part, the name part, and + // extension. Here we can assume the name part is a valid + // filesystem name. + // + // Note that if the file has no extension, we record an empty + // extension rather than NULL (which would signify that the + // default extension should be added). + // + dir_path d (f.directory ()); + string n (f.leaf ().base ().string ()); + const char* es (f.extension ()); + const string* e (&extension_pool.find (es != nullptr ? es : "")); + + // Determine the target type. + // + const target_type* tt (nullptr); + + // See if this directory is part of any project out_root + // hierarchy. Note that this will miss all the headers + // that come from src_root (so they will be treated as + // generic C headers below). Generally, we don't have + // the ability to determine that some file belongs to + // src_root of some project. But that's not a problem + // for our purposes: it is only important for us to + // accurately determine target types for headers that + // could be auto-generated. + // + scope& b (scopes.find (d)); + if (b.root_scope () != nullptr) + tt = map_extension (b, n, *e); + + // If it is outside any project, or the project doesn't have + // such an extension, assume it is a plain old C header. + // + if (tt == nullptr) + tt = &h::static_type; + + // Find or insert target. + // + path_target& pt ( + static_cast (search (*tt, d, n, e, &ds))); + + // Assign path. + // + if (pt.path ().empty ()) + pt.path (move (f)); + + // Match to a rule. + // + build2::match (a, pt); + + // Update it. + // + // There would normally be a lot of headers for every source + // file (think all the system headers) and this can get + // expensive. At the same time, most of these headers are + // existing files that we will never be updated (again, + // system headers, for example) and the rule that will match + // them is fallback file_rule. That rule has an optimization + // in that it returns noop_recipe (which causes the target + // state to be automatically set to unchanged) if the file + // is known to be up to date. + // + if (pt.state () != target_state::unchanged) + { + // We only want to restart if our call to execute() actually + // caused an update. In particular, the target could already + // have been in target_state::changed because of a dependency + // extraction run for some other source file. + // + target_state os (pt.state ()); + target_state ns (execute_direct (a, pt)); + + if (ns != os && ns != target_state::unchanged) + { + level6 ([&]{trace << "updated " << pt << ", restarting";}); + restart = true; + } + } + + // Add to our prerequisite target list. + // + t.prerequisite_targets.push_back (&pt); + skip_count++; + } + } + + // We may not have read all the output (e.g., due to a restart), + // so close the file descriptor before waiting to avoid blocking + // the other end. + // + is.close (); + + // We assume the child process issued some diagnostics. + // + if (!pr.wait ()) + { + // In case of a restarts, we closed our end of the pipe early + // which might have caused the other end to fail. So far we + // experienced this on Fedora 23 with GCC 5.3.1 and there were + // no diagnostics issued, just the non-zero exit status. If we + // do get diagnostics, then we will have to read and discard the + // output until eof. + // + if (!restart) + throw failed (); + } + } + catch (const process_error& e) + { + error << "unable to execute " << args[0] << ": " << e.what (); + + // In a multi-threaded program that fork()'ed but did not exec(), + // it is unwise to try to do any kind of cleanup (like unwinding + // the stack and running destructors). + // + if (e.child ()) + exit (1); + + throw failed (); + } + } + } + + target_state compile:: + perform_update (action a, target& xt) + { + path_target& t (static_cast (xt)); + cxx* s (execute_prerequisites (a, t, t.mtime ())); + + if (s == nullptr) + return target_state::unchanged; + + // Translate paths to relative (to working directory) ones. This + // results in easier to read diagnostics. + // + path relo (relative (t.path ())); + path rels (relative (s->path ())); + + scope& rs (t.root_scope ()); + const string& cxx (as (*rs["config.cxx"])); + + cstrings args {cxx.c_str ()}; + + // Add cxx.export.poptions from prerequisite libraries. Note that + // here we don't need to see group members (see apply()). + // + for (prerequisite& p: group_prerequisites (t)) + { + target& pt (*p.target); // Already searched and matched. + + if (pt.is_a () || pt.is_a () || pt.is_a ()) + append_lib_options (args, pt, "cxx.export.poptions"); + } + + append_options (args, t, "cxx.poptions"); + append_options (args, t, "cxx.coptions"); + + string std; // Storage. + append_std (args, t, std); + + if (t.is_a ()) + args.push_back ("-fPIC"); + + args.push_back ("-o"); + args.push_back (relo.string ().c_str ()); + + args.push_back ("-c"); + args.push_back (rels.string ().c_str ()); + + args.push_back (nullptr); + + if (verb >= 2) + print_process (args); + else if (verb) + text << "c++ " << *s; + + try + { + process pr (args.data ()); + + if (!pr.wait ()) + throw failed (); + + // Should we go to the filesystem and get the new mtime? We + // know the file has been modified, so instead just use the + // current clock time. It has the advantage of having the + // subseconds precision. + // + t.mtime (system_clock::now ()); + return target_state::changed; + } + catch (const process_error& e) + { + error << "unable to execute " << args[0] << ": " << e.what (); + + // In a multi-threaded program that fork()'ed but did not exec(), + // it is unwise to try to do any kind of cleanup (like unwinding + // the stack and running destructors). + // + if (e.child ()) + exit (1); + + throw failed (); + } + } + + compile compile::instance; + } +} diff --git a/build2/cxx/install b/build2/cxx/install new file mode 100644 index 0000000..154a62a --- /dev/null +++ b/build2/cxx/install @@ -0,0 +1,29 @@ +// file : build2/cxx/install -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_CXX_INSTALL +#define BUILD2_CXX_INSTALL + +#include +#include + +namespace build2 +{ + namespace cxx + { + class install: public build2::install::file_rule + { + public: + virtual target* + filter (action, target&, prerequisite_member) const; + + virtual match_result + match (action, target&, const std::string&) const; + + static install instance; + }; + } +} + +#endif // BUILD2_CXX_INSTALL diff --git a/build2/cxx/install.cxx b/build2/cxx/install.cxx new file mode 100644 index 0000000..1bde9ec --- /dev/null +++ b/build2/cxx/install.cxx @@ -0,0 +1,66 @@ +// file : build2/cxx/install.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include + +#include +#include + +using namespace std; + +namespace build2 +{ + namespace cxx + { + using namespace bin; + + target* install:: + filter (action a, target& t, prerequisite_member p) const + { + if (t.is_a ()) + { + // Don't install executable's prerequisite headers. + // + if (p.is_a () || p.is_a () || p.is_a () || p.is_a ()) + return nullptr; + } + + // If this is a shared library prerequisite, install it as long as it + // is in the same amalgamation as we are. + // + if ((t.is_a () || t.is_a ()) && + (p.is_a () || p.is_a ())) + { + target* pt (&p.search ()); + + // If this is the lib{} group, pick a member which we would link. + // + if (lib* l = pt->is_a ()) + pt = &link::link_member (*l, link::link_order (t)); + + if (pt->is_a ()) // Can be liba{}. + return pt->in (t.weak_scope ()) ? pt : nullptr; + } + + return file_rule::filter (a, t, p); + } + + match_result install:: + match (action a, target& t, const std::string& hint) const + { + // @@ How do we split the hint between the two? + // + + // We only want to handle installation if we are also the + // ones building this target. So first run link's match(). + // + match_result r (link::instance.match (a, t, hint)); + return r ? install::file_rule::match (a, t, "") : r; + } + + install install::instance; + } +} diff --git a/build2/cxx/link b/build2/cxx/link new file mode 100644 index 0000000..c7f7019 --- /dev/null +++ b/build2/cxx/link @@ -0,0 +1,70 @@ +// file : build2/cxx/link -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_CXX_LINK +#define BUILD2_CXX_LINK + +#include + +#include + +#include +#include + +#include + +namespace build2 +{ + namespace cxx + { + class link: public rule + { + public: + virtual match_result + match (action, target&, const std::string& hint) const; + + virtual recipe + apply (action, target&, const match_result&) const; + + static target_state + perform_update (action, target&); + + static link instance; + + public: + enum class type {e, a, so}; + enum class order {a, so, a_so, so_a}; + + static type + link_type (target& t) + { + return t.is_a () + ? type::e + : (t.is_a () ? type::a : type::so); + } + + static order + link_order (target&); + + // Determine the library member (liba or libso) to link. + // + static target& + link_member (bin::lib&, order); + + private: + friend class compile; + + using search_paths = std::vector; + using search_paths_cache = butl::optional; + + static target* + search_library (search_paths_cache&, prerequisite&); + + static search_paths + extract_library_paths (scope&); + }; + } +} + +#endif // BUILD2_CXX_LINK diff --git a/build2/cxx/link.cxx b/build2/cxx/link.cxx new file mode 100644 index 0000000..96584d9 --- /dev/null +++ b/build2/cxx/link.cxx @@ -0,0 +1,875 @@ +// file : build2/cxx/link.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include +#include +#include // size_t +#include // exit() +#include // move() + +#include +#include // reverse_iterate +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +using namespace std; +using namespace butl; + +namespace build2 +{ + namespace cxx + { + using namespace bin; + + link::order link:: + link_order (target& t) + { + const char* var; + + switch (link_type (t)) + { + case type::e: var = "bin.exe.lib"; break; + case type::a: var = "bin.liba.lib"; break; + case type::so: var = "bin.libso.lib"; break; + } + + const auto& v (as (*t[var])); + return v[0] == "shared" + ? v.size () > 1 && v[1] == "static" ? order::so_a : order::so + : v.size () > 1 && v[1] == "shared" ? order::a_so : order::a; + } + + target& link:: + link_member (bin::lib& l, order lo) + { + bool lso (true); + const string& at (as (*l["bin.lib"])); // Available types. + + switch (lo) + { + case order::a: + case order::a_so: + lso = false; // Fall through. + case order::so: + case order::so_a: + { + if (lso ? at == "static" : at == "shared") + { + if (lo == order::a_so || lo == order::so_a) + lso = !lso; + else + fail << (lso ? "shared" : "static") << " build of " << l + << " is not available"; + } + } + } + + target* r (lso ? static_cast (l.so) : l.a); + + if (r == nullptr) + r = &search (lso ? libso::static_type : liba::static_type, + prerequisite_key {nullptr, l.key (), nullptr}); + + return *r; + } + + link::search_paths link:: + extract_library_paths (scope& bs) + { + search_paths r; + scope& rs (*bs.root_scope ()); + + // Extract user-supplied search paths (i.e., -L). + // + if (auto l = bs["cxx.loptions"]) + { + const auto& v (as (*l)); + + for (auto i (v.begin ()), e (v.end ()); i != e; ++i) + { + // -L can either be in the "-Lfoo" or "-L foo" form. + // + dir_path d; + if (*i == "-L") + { + if (++i == e) + break; // Let the compiler complain. + + d = dir_path (*i); + } + else if (i->compare (0, 2, "-L") == 0) + d = dir_path (*i, 2, string::npos); + else + continue; + + // Ignore relative paths. Or maybe we should warn? + // + if (!d.relative ()) + r.push_back (move (d)); + } + } + + // Extract system search paths. + // + cstrings args; + string std_storage; + + args.push_back (as (*rs["config.cxx"]).c_str ()); + append_options (args, bs, "cxx.coptions"); + append_std (args, bs, std_storage); + append_options (args, bs, "cxx.loptions"); + args.push_back ("-print-search-dirs"); + args.push_back (nullptr); + + if (verb >= 3) + print_process (args); + + string l; + try + { + process pr (args.data (), 0, -1); // Open pipe to stdout. + ifdstream is (pr.in_ofd); + + while (!is.eof ()) + { + string s; + getline (is, s); + + if (is.fail () && !is.eof ()) + fail << "error reading C++ compiler -print-search-dirs output"; + + if (s.compare (0, 12, "libraries: =") == 0) + { + l.assign (s, 12, string::npos); + break; + } + } + + is.close (); // Don't block. + + if (!pr.wait ()) + throw failed (); + } + catch (const process_error& e) + { + error << "unable to execute " << args[0] << ": " << e.what (); + + if (e.child ()) + exit (1); + + throw failed (); + } + + if (l.empty ()) + fail << "unable to extract C++ compiler system library paths"; + + // Now the fun part: figuring out which delimiter is used. + // Normally it is ':' but on Windows it is ';' (or can be; + // who knows for sure). Also note that these paths are + // absolute (or should be). So here is what we are going + // to do: first look for ';'. If found, then that's the + // delimiter. If not found, then there are two cases: + // it is either a single Windows path or the delimiter + // is ':'. To distinguish these two cases we check if + // the path starts with a Windows drive. + // + char d (';'); + string::size_type e (l.find (d)); + + if (e == string::npos && + (l.size () < 2 || l[0] == '/' || l[1] != ':')) + { + d = ':'; + e = l.find (d); + } + + // Now chop it up. We already have the position of the + // first delimiter (if any). + // + for (string::size_type b (0);; e = l.find (d, (b = e + 1))) + { + r.emplace_back (l, b, (e != string::npos ? e - b : e)); + r.back ().normalize (); + + if (e == string::npos) + break; + } + + return r; + } + + target* link:: + search_library (search_paths_cache& spc, prerequisite& p) + { + tracer trace ("cxx::link::search_library"); + + // First check the cache. + // + if (p.target != nullptr) + return p.target; + + bool l (p.is_a ()); + const string* ext (l ? nullptr : p.ext); // Only for liba/libso. + + // Then figure out what we need to search for. + // + + // liba + // + path an; + const string* ae; + + if (l || p.is_a ()) + { + an = path ("lib" + p.name); + + // Note that p.scope should be the same as the target's for + // which we are looking for this library. The idea here is + // that we have to use the same "extension configuration" as + // the target's. + // + ae = ext == nullptr + ? &liba::static_type.extension (p.key ().tk, p.scope) + : ext; + + if (!ae->empty ()) + { + an += '.'; + an += *ae; + } + } + + // libso + // + path sn; + const string* se; + + if (l || p.is_a ()) + { + sn = path ("lib" + p.name); + se = ext == nullptr + ? &libso::static_type.extension (p.key ().tk, p.scope) + : ext; + + if (!se->empty ()) + { + sn += '.'; + sn += *se; + } + } + + // Now search. + // + if (!spc) + spc = extract_library_paths (p.scope); + + liba* a (nullptr); + libso* s (nullptr); + + path f; // Reuse the buffer. + const dir_path* pd; + for (const dir_path& d: *spc) + { + timestamp mt; + + // liba + // + if (!an.empty ()) + { + f = d; + f /= an; + + if ((mt = file_mtime (f)) != timestamp_nonexistent) + { + // Enter the target. Note that because the search paths are + // normalized, the result is automatically normalized as well. + // + a = &targets.insert (d, p.name, ae, trace); + + if (a->path ().empty ()) + a->path (move (f)); + + a->mtime (mt); + } + } + + // libso + // + if (!sn.empty ()) + { + f = d; + f /= sn; + + if ((mt = file_mtime (f)) != timestamp_nonexistent) + { + s = &targets.insert (d, p.name, se, trace); + + if (s->path ().empty ()) + s->path (move (f)); + + s->mtime (mt); + } + } + + if (a != nullptr || s != nullptr) + { + pd = &d; + break; + } + } + + if (a == nullptr && s == nullptr) + return nullptr; + + if (l) + { + // Enter the target group. + // + lib& l (targets.insert (*pd, p.name, p.ext, trace)); + + // It should automatically link-up to the members we have found. + // + assert (l.a == a); + assert (l.so == s); + + // Set the bin.lib variable to indicate what's available. + // + const char* bl (a != nullptr + ? (s != nullptr ? "both" : "static") + : "shared"); + l.assign ("bin.lib") = bl; + + p.target = &l; + } + else + p.target = p.is_a () ? static_cast (a) : s; + + return p.target; + } + + match_result link:: + match (action a, target& t, const string& hint) const + { + tracer trace ("cxx::link::match"); + + // @@ TODO: + // + // - if path already assigned, verify extension? + // + // @@ Q: + // + // - if there is no .o, are we going to check if the one derived + // from target exist or can be built? A: No. + // What if there is a library. Probably ok if .a, not if .so. + // (i.e., a utility library). + // + + type lt (link_type (t)); + + // Scan prerequisites and see if we can work with what we've got. + // + bool seen_cxx (false), seen_c (false), seen_obj (false), + seen_lib (false); + + for (prerequisite_member p: group_prerequisite_members (a, t)) + { + if (p.is_a ()) + { + seen_cxx = seen_cxx || true; + } + else if (p.is_a ()) + { + seen_c = seen_c || true; + } + else if (p.is_a ()) + { + if (lt == type::so) + fail << "shared library " << t << " prerequisite " << p + << " is static object"; + + seen_obj = seen_obj || true; + } + else if (p.is_a () || + p.is_a ()) + { + seen_obj = seen_obj || true; + } + else if (p.is_a () || + p.is_a () || + p.is_a ()) + { + seen_lib = seen_lib || true; + } + } + + // We will only chain a C source if there is also a C++ source or we + // were explicitly told to. + // + if (seen_c && !seen_cxx && hint < "cxx") + { + level4 ([&]{trace << "c prerequisite(s) without c++ or hint";}); + return nullptr; + } + + // If we have any prerequisite libraries (which also means that + // we match), search/import and pre-match them to implement the + // "library meta-information protocol". Don't do this if we are + // called from the install rule just to check if we would match. + // + if (seen_lib && lt != type::e && + a.operation () != install_id && a.outer_operation () != install_id) + { + if (t.group != nullptr) + t.group->prerequisite_targets.clear (); // lib{}'s + + search_paths_cache lib_paths; // Extract lazily. + + for (prerequisite_member p: group_prerequisite_members (a, t)) + { + if (p.is_a () || p.is_a () || p.is_a ()) + { + target* pt (nullptr); + + // Handle imported libraries. + // + if (p.proj () != nullptr) + pt = search_library (lib_paths, p.prerequisite); + + if (pt == nullptr) + { + pt = &p.search (); + match_only (a, *pt); + } + + // If the prerequisite came from the lib{} group, then also + // add it to lib's prerequisite_targets. + // + if (!p.prerequisite.belongs (t)) + t.group->prerequisite_targets.push_back (pt); + + t.prerequisite_targets.push_back (pt); + } + } + } + + return seen_cxx || seen_c || seen_obj || seen_lib ? &t : nullptr; + } + + recipe link:: + apply (action a, target& xt, const match_result&) const + { + tracer trace ("cxx::link::apply"); + + path_target& t (static_cast (xt)); + + type lt (link_type (t)); + bool so (lt == type::so); + order lo (link_order (t)); + + // Derive file name from target name. + // + if (t.path ().empty ()) + { + auto l (t["extension"]); + const char* e (l ? as (*l).c_str () : nullptr); + + switch (lt) + { + case type::e: + { + t.derive_path (e != nullptr ? e : ""); + break; + } + case type::a: + case type::so: + { + auto l (t["bin.libprefix"]); + t.derive_path (e != nullptr ? e : (lt == type::a ? "a" : "so"), + l ? as (*l).c_str () : "lib"); + break; + } + } + } + + t.prerequisite_targets.clear (); // See lib pre-match in match() above. + + // Inject dependency on the output directory. + // + inject_parent_fsdir (a, t); + + // We may need the project roots for rule chaining (see below). + // We will resolve them lazily only if needed. + // + scope* root (nullptr); + const dir_path* out_root (nullptr); + const dir_path* src_root (nullptr); + + search_paths_cache lib_paths; // Extract lazily. + + // Process prerequisites: do rule chaining for C and C++ source + // files as well as search and match. + // + // When cleaning, ignore prerequisites that are not in the same + // or a subdirectory of our strong amalgamation. + // + const dir_path* amlg ( + a.operation () != clean_id + ? nullptr + : &t.strong_scope ().out_path ()); + + for (prerequisite_member p: group_prerequisite_members (a, t)) + { + bool group (!p.prerequisite.belongs (t)); // Group's prerequisite. + target* pt (nullptr); + + if (!p.is_a () && !p.is_a ()) + { + // Handle imported libraries. + // + if (p.proj () != nullptr) + pt = search_library (lib_paths, p.prerequisite); + + // The rest is the same basic logic as in search_and_match(). + // + if (pt == nullptr) + pt = &p.search (); + + if (a.operation () == clean_id && !pt->dir.sub (*amlg)) + continue; // Skip. + + // If this is the obj{} or lib{} target group, then pick the + // appropriate member and make sure it is searched and matched. + // + if (obj* o = pt->is_a ()) + { + pt = so ? static_cast (o->so) : o->a; + + if (pt == nullptr) + pt = &search (so ? objso::static_type : obja::static_type, + p.key ()); + } + else if (lib* l = pt->is_a ()) + { + pt = &link_member (*l, lo); + } + + build2::match (a, *pt); + t.prerequisite_targets.push_back (pt); + continue; + } + + if (root == nullptr) + { + // Which scope shall we use to resolve the root? Unlikely, + // but possible, the prerequisite is from a different project + // altogether. So we are going to use the target's project. + // + root = &t.root_scope (); + out_root = &root->out_path (); + src_root = &root->src_path (); + } + + const prerequisite_key& cp (p.key ()); // c(xx){} prerequisite key. + const target_type& o_type ( + group + ? obj::static_type + : (so ? objso::static_type : obja::static_type)); + + // Come up with the obj*{} target. The c(xx){} prerequisite + // directory can be relative (to the scope) or absolute. If it is + // relative, then use it as is. If it is absolute, then translate + // it to the corresponding directory under out_root. While the + // c(xx){} directory is most likely under src_root, it is also + // possible it is under out_root (e.g., generated source). + // + dir_path d; + { + const dir_path& cpd (*cp.tk.dir); + + if (cpd.relative () || cpd.sub (*out_root)) + d = cpd; + else + { + if (!cpd.sub (*src_root)) + fail << "out of project prerequisite " << cp << + info << "specify corresponding " << o_type.name << "{} " + << "target explicitly"; + + d = *out_root / cpd.leaf (*src_root); + } + } + + target& ot (search (o_type, d, *cp.tk.name, nullptr, cp.scope)); + + // If we are cleaning, check that this target is in the same or + // a subdirectory of our strong amalgamation. + // + if (a.operation () == clean_id && !ot.dir.sub (*amlg)) + { + // If we shouldn't clean obj{}, then it is fair to assume + // we shouldn't clean cxx{} either (generated source will + // be in the same directory as obj{} and if not, well, go + // find yourself another build system ;-)). + // + continue; // Skip. + } + + // If we have created the obj{} target group, pick one of its + // members; the rest would be primarily concerned with it. + // + if (group) + { + obj& o (static_cast (ot)); + pt = so ? static_cast (o.so) : o.a; + + if (pt == nullptr) + pt = &search (so ? objso::static_type : obja::static_type, + o.dir, o.name, o.ext, nullptr); + } + else + pt = &ot; + + // If this obj*{} target already exists, then it needs to be + // "compatible" with what we are doing here. + // + // This gets a bit tricky. We need to make sure the source files + // are the same which we can only do by comparing the targets to + // which they resolve. But we cannot search the ot's prerequisites + // -- only the rule that matches can. Note, however, that if all + // this works out, then our next step is to match the obj*{} + // target. If things don't work out, then we fail, in which case + // searching and matching speculatively doesn't really hurt. + // + bool found (false); + for (prerequisite_member p1: + reverse_group_prerequisite_members (a, *pt)) + { + // Ignore some known target types (fsdir, headers, libraries). + // + if (p1.is_a () || + p1.is_a () || + (p.is_a () && (p1.is_a () || + p1.is_a () || + p1.is_a ())) || + p1.is_a () || + p1.is_a () || + p1.is_a ()) + { + continue; + } + + if (!p1.is_a ()) + fail << "synthesized target for prerequisite " << cp + << " would be incompatible with existing target " << *pt << + info << "unexpected existing prerequisite type " << p1 << + info << "specify corresponding obj{} target explicitly"; + + if (!found) + { + build2::match (a, *pt); // Now p1 should be resolved. + + // Searching our own prerequisite is ok. + // + if (&p.search () != &p1.search ()) + fail << "synthesized target for prerequisite " << cp << " would " + << "be incompatible with existing target " << *pt << + info << "existing prerequisite " << p1 << " does not match " + << cp << + info << "specify corresponding " << o_type.name << "{} target " + << "explicitly"; + + found = true; + // Check the rest of the prerequisites. + } + } + + if (!found) + { + // Note: add the source to the group, not the member. + // + ot.prerequisites.emplace_back (p.as_prerequisite (trace)); + + // Add our lib*{} prerequisites to the object file (see + // cxx.export.poptions above for details). Note: no need + // to go into group members. + // + // Initially, we were only adding imported libraries, but + // there is a problem with this approach: the non-imported + // library might depend on the imported one(s) which we will + // never "see" unless we start with this library. + // + for (prerequisite& p: group_prerequisites (t)) + { + if (p.is_a () || p.is_a () || p.is_a ()) + ot.prerequisites.emplace_back (p); + } + + build2::match (a, *pt); + } + + t.prerequisite_targets.push_back (pt); + } + + switch (a) + { + case perform_update_id: return &perform_update; + case perform_clean_id: return &perform_clean; + default: return noop_recipe; // Configure update. + } + } + + target_state link:: + perform_update (action a, target& xt) + { + path_target& t (static_cast (xt)); + + type lt (link_type (t)); + bool so (lt == type::so); + + if (!execute_prerequisites (a, t, t.mtime ())) + return target_state::unchanged; + + // Translate paths to relative (to working directory) ones. This + // results in easier to read diagnostics. + // + path relt (relative (t.path ())); + + scope& rs (t.root_scope ()); + cstrings args; + + // Storage. + // + string std; + string soname; + strings sargs; + + if (lt == type::a) + { + //@@ ranlib + // + args.push_back ("ar"); + args.push_back ("-rc"); + args.push_back (relt.string ().c_str ()); + } + else + { + args.push_back (as (*rs["config.cxx"]).c_str ()); + append_options (args, t, "cxx.coptions"); + append_std (args, t, std); + + if (so) + args.push_back ("-shared"); + + args.push_back ("-o"); + args.push_back (relt.string ().c_str ()); + + // Set soname. + // + if (so) + { + soname = "-Wl,-soname," + relt.leaf ().string (); + args.push_back (soname.c_str ()); + } + + // Add rpaths. First the ones specified by the user so that they + // take precedence. + // + if (auto l = t["bin.rpath"]) + for (const string& p: as (*l)) + sargs.push_back ("-Wl,-rpath," + p); + + // Then the paths of the shared libraries we are linking to. + // + for (target* pt: t.prerequisite_targets) + { + if (libso* ls = pt->is_a ()) + sargs.push_back ( + "-Wl,-rpath," + ls->path ().directory ().string ()); + } + } + + size_t oend (sargs.size ()); // Note the end of options. + + for (target* pt: t.prerequisite_targets) + { + path_target* ppt; + + if ((ppt = pt->is_a ()) || + (ppt = pt->is_a ()) || + (ppt = pt->is_a ()) || + (ppt = pt->is_a ())) + { + sargs.push_back (relative (ppt->path ()).string ()); // string()&& + } + } + + // Finish assembling args from sargs. + // + for (size_t i (0); i != sargs.size (); ++i) + { + if (lt != type::a && i == oend) + append_options (args, t, "cxx.loptions"); + + args.push_back (sargs[i].c_str ()); + } + + if (lt != type::a) + append_options (args, t, "cxx.libs"); + + args.push_back (nullptr); + + if (verb >= 2) + print_process (args); + else if (verb) + text << "ld " << t; + + try + { + process pr (args.data ()); + + if (!pr.wait ()) + throw failed (); + + // Should we go to the filesystem and get the new mtime? We + // know the file has been modified, so instead just use the + // current clock time. It has the advantage of having the + // subseconds precision. + // + t.mtime (system_clock::now ()); + return target_state::changed; + } + catch (const process_error& e) + { + error << "unable to execute " << args[0] << ": " << e.what (); + + // In a multi-threaded program that fork()'ed but did not exec(), + // it is unwise to try to do any kind of cleanup (like unwinding + // the stack and running destructors). + // + if (e.child ()) + exit (1); + + throw failed (); + } + } + + link link::instance; + } +} diff --git a/build2/cxx/module b/build2/cxx/module new file mode 100644 index 0000000..c712d0b --- /dev/null +++ b/build2/cxx/module @@ -0,0 +1,23 @@ +// file : build2/cxx/module -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_CXX_MODULE +#define BUILD2_CXX_MODULE + +#include +#include + +#include + +namespace build2 +{ + namespace cxx + { + extern "C" bool + cxx_init ( + scope&, scope&, const location&, unique_ptr&, bool, bool); + } +} + +#endif // BUILD2_CXX_MODULE diff --git a/build2/cxx/module.cxx b/build2/cxx/module.cxx new file mode 100644 index 0000000..4829a17 --- /dev/null +++ b/build2/cxx/module.cxx @@ -0,0 +1,230 @@ +// file : build2/cxx/module.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include +#include + +#include +#include + +#include +#include + +#include + +#include +#include +#include +#include + +using namespace std; +using namespace butl; + +namespace build2 +{ + namespace cxx + { + extern "C" bool + cxx_init (scope& r, + scope& b, + const location& loc, + std::unique_ptr&, + bool first, + bool) + { + tracer trace ("cxx::init"); + level5 ([&]{trace << "for " << b.out_path ();}); + + // Initialize the bin module. Only do this if it hasn't already + // been loaded so that we don't overwrite user's bin.* settings. + // + { + auto l (b["bin.loaded"]); + + if (!l || !as (*l)) + load_module (false, "bin", r, b, loc); + } + + // Enter module variables. + // + // @@ Probably should only be done on load; make sure reset() unloads + // modules. + // + // @@ Should probably cache the variable pointers so we don't have + // to keep looking them up. + // + if (first) + { + auto& v (var_pool); + + v.find ("config.cxx", string_type); //@@ VAR type + + v.find ("config.cxx.poptions", strings_type); + v.find ("config.cxx.coptions", strings_type); + v.find ("config.cxx.loptions", strings_type); + v.find ("config.cxx.libs", strings_type); + + v.find ("cxx.poptions", strings_type); + v.find ("cxx.coptions", strings_type); + v.find ("cxx.loptions", strings_type); + v.find ("cxx.libs", strings_type); + + v.find ("cxx.export.poptions", strings_type); + v.find ("cxx.export.coptions", strings_type); + v.find ("cxx.export.loptions", strings_type); + v.find ("cxx.export.libs", strings_type); + + v.find ("cxx.std", string_type); + } + + // Register target types. + // + { + auto& t (b.target_types); + + t.insert (); + t.insert (); + + t.insert (); + t.insert (); + t.insert (); + t.insert (); + } + + // Register rules. + // + { + using namespace bin; + + auto& r (b.rules); + + r.insert (perform_update_id, "cxx.compile", compile::instance); + + r.insert (perform_update_id, "cxx.compile", compile::instance); + r.insert (perform_clean_id, "cxx.compile", compile::instance); + + r.insert (perform_update_id, "cxx.compile", compile::instance); + r.insert (perform_clean_id, "cxx.compile", compile::instance); + + r.insert (perform_update_id, "cxx.link", link::instance); + r.insert (perform_clean_id, "cxx.link", link::instance); + + r.insert (perform_update_id, "cxx.link", link::instance); + r.insert (perform_clean_id, "cxx.link", link::instance); + + r.insert (perform_update_id, "cxx.link", link::instance); + r.insert (perform_clean_id, "cxx.link", link::instance); + + // Register for configure so that we detect unresolved imports + // during configuration rather that later, e.g., during update. + // + r.insert (configure_update_id, "cxx.compile", compile::instance); + r.insert (configure_update_id, "cxx.compile", compile::instance); + + r.insert (configure_update_id, "cxx.link", link::instance); + r.insert (configure_update_id, "cxx.link", link::instance); + r.insert (configure_update_id, "cxx.link", link::instance); + + //@@ Should we check if install module was loaded (see bin)? + // + r.insert (perform_install_id, "cxx.install", install::instance); + r.insert (perform_install_id, "cxx.install", install::instance); + r.insert (perform_install_id, "cxx.install", install::instance); + } + + // Configure. + // + + // config.cxx + // + if (first) + { + auto p (config::required (r, "config.cxx", "g++")); + + // If we actually set a new value, test it by trying to execute. + // + if (p.second) + { + const string& cxx (as (p.first)); + const char* args[] = {cxx.c_str (), "-dumpversion", nullptr}; + + if (verb >= 2) + print_process (args); + else if (verb) + text << "test " << cxx; + + string ver; + try + { + process pr (args, 0, -1); // Open pipe to stdout. + ifdstream is (pr.in_ofd); + + bool r (getline (is, ver)); + + if (!r) + fail << "unexpected output from " << cxx; + + if (!pr.wait ()) + throw failed (); + } + catch (const process_error& e) + { + error << "unable to execute " << cxx << ": " << e.what (); + + if (e.child ()) + exit (1); + + throw failed (); + } + + if (verb >= 2) + text << cxx << " " << ver; + } + } + + // config.cxx.{p,c,l}options + // config.cxx.libs + // + // These are optional. We also merge them into the corresponding + // cxx.* variables. + // + // The merging part gets a bit tricky if this module has already + // been loaded in one of the outer scopes. By doing the straight + // append we would just be repeating the same options over and + // over. So what we are going to do is only append to a value if + // it came from this scope. Then the usage for merging becomes: + // + // cxx.coptions = # Note: '='. + // using cxx + // cxx.coptions += # Note: '+='. + // + if (const value& v = config::optional (r, "config.cxx.poptions")) + b.assign ("cxx.poptions") += as (v); + + if (const value& v = config::optional (r, "config.cxx.coptions")) + b.assign ("cxx.coptions") += as (v); + + if (const value& v = config::optional (r, "config.cxx.loptions")) + b.assign ("cxx.loptions") += as (v); + + if (const value& v = config::optional (r, "config.cxx.libs")) + b.assign ("cxx.libs") += as (v); + + // Configure "installability" of our target types. + // + { + using build2::install::path; + + path (b, dir_path ("include")); // Install into install.include. + path (b, dir_path ("include")); + path (b, dir_path ("include")); + path (b, dir_path ("include")); + } + + return true; + } + } +} diff --git a/build2/cxx/target b/build2/cxx/target new file mode 100644 index 0000000..c51c964 --- /dev/null +++ b/build2/cxx/target @@ -0,0 +1,78 @@ +// file : build2/cxx/target -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_CXX_TARGET +#define BUILD2_CXX_TARGET + +#include + +namespace build2 +{ + namespace cxx + { + class hxx: public file + { + public: + using file::file; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + + class ixx: public file + { + public: + using file::file; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + + class txx: public file + { + public: + using file::file; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + + class cxx: public file + { + public: + using file::file; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + + //@@ TMP + // + class h: public file + { + public: + using file::file; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + + class c: public file + { + public: + using file::file; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + } +} + +#endif // BUILD2_CXX_TARGET diff --git a/build2/cxx/target.cxx b/build2/cxx/target.cxx new file mode 100644 index 0000000..0990945 --- /dev/null +++ b/build2/cxx/target.cxx @@ -0,0 +1,81 @@ +// file : build2/cxx/target.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +using namespace std; + +namespace build2 +{ + namespace cxx + { + constexpr const char ext_var[] = "extension"; + + constexpr const char hxx_ext_def[] = "hxx"; + const target_type hxx::static_type + { + "hxx", + &file::static_type, + &target_factory, + &target_extension_var, + &search_file, + false + }; + + constexpr const char ixx_ext_def[] = "ixx"; + const target_type ixx::static_type + { + "ixx", + &file::static_type, + &target_factory, + &target_extension_var, + &search_file, + false + }; + + constexpr const char txx_ext_def[] = "txx"; + const target_type txx::static_type + { + "txx", + &file::static_type, + &target_factory, + &target_extension_var, + &search_file, + false + }; + + constexpr const char cxx_ext_def[] = "cxx"; + const target_type cxx::static_type + { + "cxx", + &file::static_type, + &target_factory, + &target_extension_var, + &search_file, + false + }; + + constexpr const char h_ext_def[] = "h"; + const target_type h::static_type + { + "h", + &file::static_type, + &target_factory, + &target_extension_var, + &search_file, + false + }; + + constexpr const char c_ext_def[] = "c"; + const target_type c::static_type + { + "c", + &file::static_type, + &target_factory, + &target_extension_var, + &search_file, + false + }; + } +} diff --git a/build2/cxx/utility b/build2/cxx/utility new file mode 100644 index 0000000..b0deb08 --- /dev/null +++ b/build2/cxx/utility @@ -0,0 +1,37 @@ +// file : build2/cxx/utility -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_CXX_UTILITY +#define BUILD2_CXX_UTILITY + +#include + +#include +#include + +#include + +namespace build2 +{ + namespace cxx + { + using config::append_options; + + // T is either target or scope. + // + template + void + append_std (cstrings& args, T&, std::string& storage); + + // Append library options from one of the cxx.export.* variables + // recursively, prerequisite libraries first. + // + void + append_lib_options (cstrings& args, target&, const char* variable); + } +} + +#include + +#endif // BUILD2_CXX_UTILITY diff --git a/build2/cxx/utility.cxx b/build2/cxx/utility.cxx new file mode 100644 index 0000000..fead1b4 --- /dev/null +++ b/build2/cxx/utility.cxx @@ -0,0 +1,29 @@ +// file : build2/cxx/utility.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include + +using namespace std; + +namespace build2 +{ + namespace cxx + { + void + append_lib_options (cstrings& args, target& l, const char* var) + { + using namespace bin; + + for (target* t: l.prerequisite_targets) + { + if (t->is_a () || t->is_a () || t->is_a ()) + append_lib_options (args, *t, var); + } + + append_options (args, l, var); + } + } +} diff --git a/build2/cxx/utility.txx b/build2/cxx/utility.txx new file mode 100644 index 0000000..b35649e --- /dev/null +++ b/build2/cxx/utility.txx @@ -0,0 +1,35 @@ +// file : build2/cxx/utility.txx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +using namespace std; + +namespace build2 +{ + namespace cxx + { + template + void + append_std (cstrings& args, T& t, std::string& s) + { + if (auto l = t["cxx.std"]) + { + const std::string& v (as (*l)); + + // Translate 11 to 0x and 14 to 1y for compatibility with + // older versions of the compiler. + // + s = "-std=c++"; + + if (v == "11") + s += "0x"; + else if (v == "14") + s += "1y"; + else + s += v; + + args.push_back (s.c_str ()); + } + } + } +} diff --git a/build2/diagnostics b/build2/diagnostics new file mode 100644 index 0000000..52f999a --- /dev/null +++ b/build2/diagnostics @@ -0,0 +1,402 @@ +// file : build2/diagnostics -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_DIAGNOSTICS +#define BUILD2_DIAGNOSTICS + +#include // size_t +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +namespace build2 +{ + struct diag_record; + + // Throw this exception to terminate the build. The handler should + // assume that the diagnostics has already been issued. + // + class failed: public std::exception {}; + + // Flag that indicates whether paths should be inserted relative + // into this stream. + // + extern const int relative_index; + + inline bool + relative (std::ostream& os) {return os.iword (relative_index);} + + inline void + relative (std::ostream& os, bool v) {os.iword (relative_index) = v ? 1 : 0;} + + // Print process commmand line. If the number of elements is specified + // (or the second version is used), then it will print the piped multi- + // process command line, if present. In this case, the expected format + // is as follows: + // + // name1 arg arg ... nullptr + // name2 arg arg ... nullptr + // ... + // nameN arg arg ... nullptr nullptr + // + void + print_process (diag_record&, const char* const* args, std::size_t n = 0); + + void + print_process (const char* const* args, std::size_t n = 0); + + inline void + print_process (diag_record& dr, const cstrings& args) + { + print_process (dr, args.data (), args.size ()); + } + + inline void + print_process (const cstrings& args) + { + print_process (args.data (), args.size ()); + } + + // Verbosity level. + // + // 0 - disabled + // 1 - high-level information messages + // 2 - essential underlying commands that are being executed + // 3 - all underlying commands that are being executed + // 4 - information helpful to the user (e.g., why a rule did not match) + // 5 - information helpful to the developer + // 6 - even more detailed information + // + // While uint8 is more than enough, use uint16 for the ease of printing. + // + extern std::uint16_t verb; + + template inline void level1 (const F& f) {if (verb >= 1) f ();} + template inline void level2 (const F& f) {if (verb >= 2) f ();} + template inline void level3 (const F& f) {if (verb >= 3) f ();} + template inline void level4 (const F& f) {if (verb >= 4) f ();} + template inline void level5 (const F& f) {if (verb >= 5) f ();} + template inline void level6 (const F& f) {if (verb >= 6) f ();} + + // Diagnostic facility, base infrastructure (potentially reusable). + // + extern std::ostream* diag_stream; + + template struct diag_prologue; + template struct diag_mark; + + typedef void (*diag_epilogue) (const diag_record&); + + struct diag_record + { + template + friend const diag_record& + operator<< (const diag_record& r, const T& x) + { + r.os_ << x; + return r; + } + + diag_record (): empty_ (true), epilogue_ (nullptr) {} + + template + explicit + diag_record (const diag_prologue& p) + : empty_ (true), epilogue_ (nullptr) { *this << p;} + + template + explicit + diag_record (const diag_mark& m) + : empty_ (true), epilogue_ (nullptr) { *this << m;} + + ~diag_record () noexcept(false); + + void + append (diag_epilogue e) const + { + if (e != nullptr) + { + assert (epilogue_ == nullptr); // No multiple epilogues support. + epilogue_ = e; + } + + if (empty_) + empty_ = false; + else + os_ << "\n "; + } + + // Move constructible-only type. + // + /* + @@ libstdc++ doesn't yet have the ostringstream move support. + + diag_record (diag_record&& r) + : os_ (std::move (r.os_)) + { + empty_ = r.empty_; + r.empty_ = true; + + epilogue_ = r.epilogue_; + r.epilogue_ = nullptr; + } + */ + + diag_record (diag_record&& r) + { + empty_ = r.empty_; + epilogue_ = r.epilogue_; + + if (!empty_) + { + assert (false); //@@ Relative flag will not be transferred. + os_ << r.os_.str (); + + r.empty_ = true; + r.epilogue_ = nullptr; + } + } + + diag_record& operator= (diag_record&&) = delete; + + diag_record (const diag_record&) = delete; + diag_record& operator= (const diag_record&) = delete; + + public: + mutable std::ostringstream os_; + + private: + mutable bool empty_; + mutable diag_epilogue epilogue_; + }; + + template + struct diag_prologue: B + { + diag_prologue (diag_epilogue e = nullptr): B (), epilogue_ (e) {} + + template + diag_prologue (A&&... a) + : B (std::forward (a)...), epilogue_ (nullptr) {} + + template + diag_prologue (diag_epilogue e, A&&... a) + : B (std::forward (a)...), epilogue_ (e) {} + + template + diag_record + operator<< (const T& x) const + { + diag_record r; + r.append (epilogue_); + B::operator() (r); + r << x; + return r; + } + + friend const diag_record& + operator<< (const diag_record& r, const diag_prologue& p) + { + r.append (p.epilogue_); + p (r); + return r; + } + + private: + diag_epilogue epilogue_; + }; + + template + struct diag_mark: B + { + diag_mark (): B () {} + + template + diag_mark (A&&... a): B (std::forward (a)...) {} + + template + diag_record + operator<< (const T& x) const + { + return B::operator() () << x; + } + + friend const diag_record& + operator<< (const diag_record& r, const diag_mark& m) + { + return r << m (); + } + }; + + // Diagnostic facility, project specifics. + // + struct simple_prologue_base + { + explicit + simple_prologue_base (const char* type, const char* name, bool rel) + : type_ (type), name_ (name), relative_ (rel) {} + + void + operator() (const diag_record& r) const; + + private: + const char* type_; + const char* name_; + const bool relative_; + }; + typedef diag_prologue simple_prologue; + + class location + { + public: + location () {} + location (const char* f, std::uint64_t l, std::uint64_t c) + : file (f), line (l), column (c) {} + + const char* file; + std::uint64_t line; + std::uint64_t column; + }; + + struct location_prologue_base + { + location_prologue_base (const char* type, + const char* name, + const location& l, + bool rel) + : type_ (type), name_ (name), loc_ (l), relative_ (rel) {} + + void + operator() (const diag_record& r) const; + + private: + const char* type_; + const char* name_; + const location loc_; + const bool relative_; + }; + typedef diag_prologue location_prologue; + + // Here is the absolute/relative path rationale: we want it absolute + // in the error/warning/info streams to give the user the complete + // picture. But in the text stream (e.g., command lines), we print + // relative unless verbosity is greater than 1. + // + struct basic_mark_base + { + explicit + basic_mark_base (const char* type, + const char* name = nullptr, + const void* data = nullptr) + : type_ (type), name_ (name), data_ (data) {} + + simple_prologue + operator() () const + { + return simple_prologue (type_, name_, false); + } + + location_prologue + operator() (const location& l) const + { + return location_prologue (type_, name_, l, false); + } + + template + location_prologue + operator() (const L& l) const + { + return location_prologue (type_, name_, get_location (l, data_), false); + } + + protected: + const char* type_; + const char* name_; + const void* data_; + }; + typedef diag_mark basic_mark; + + extern const basic_mark error; + extern const basic_mark warn; + extern const basic_mark info; + + // text + // + struct text_mark_base: basic_mark_base + { + text_mark_base (): basic_mark_base (nullptr) {} + + simple_prologue + operator() () const + { + return simple_prologue (type_, name_, verb <= 1); + } + }; + typedef diag_mark text_mark; + + extern const text_mark text; + + // trace + // + struct trace_mark_base: basic_mark_base + { + explicit + trace_mark_base (const char* name, const void* data = nullptr) + : basic_mark_base ("trace", name, data) {} + }; + typedef diag_mark trace_mark; + + typedef trace_mark tracer; + + // fail + // + template + struct fail_mark_base + { + explicit + fail_mark_base (const void* data = nullptr): data_ (data) {} + + simple_prologue + operator() () const + { + return simple_prologue (&epilogue, "error", nullptr, false); + } + + location_prologue + operator() (const location& l) const + { + return location_prologue (&epilogue, "error", nullptr, l, false); + } + + template + location_prologue + operator() (const L& l) const + { + return location_prologue ( + &epilogue, "error", nullptr, get_location (l, data_), false); + } + + static void + epilogue (const diag_record&) {throw E ();} + + private: + const void* data_; + }; + + template + using fail_mark = diag_mark>; + + extern const fail_mark fail; +} + +#endif // BUILD2_DIAGNOSTICS diff --git a/build2/diagnostics.cxx b/build2/diagnostics.cxx new file mode 100644 index 0000000..850c1d4 --- /dev/null +++ b/build2/diagnostics.cxx @@ -0,0 +1,125 @@ +// file : build2/diagnostics.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include // strchr() +#include + +#include + +using namespace std; + +namespace build2 +{ + // Relative stream. + // + const int relative_index = ostream::xalloc (); + + void + print_process (const char* const* args, size_t n) + { + diag_record r (text); + print_process (r, args, n); + } + + void + print_process (diag_record& r, const char* const* args, size_t n) + { + size_t m (0); + const char* const* p (args); + do + { + if (m != 0) + r << " |"; // Trailing space will be added inside the loop. + + for (m++; *p != nullptr; p++, m++) + { + if (p != args) + r << ' '; + + // Quote if empty or contains spaces. + // + bool q (**p == '\0' || strchr (*p, ' ') != nullptr); + + if (q) + r << '"'; + + r << *p; + + if (q) + r << '"'; + } + + if (m < n) // Can we examine the next element? + { + p++; + m++; + } + + } while (*p != nullptr); + } + + // Diagnostics verbosity level. + // + uint16_t verb; + + // Diagnostic facility, base infrastructure. + // + ostream* diag_stream = &cerr; + + diag_record:: + ~diag_record () noexcept(false) + { + // Don't flush the record if this destructor was called as part of + // the stack unwinding. Right now this means we cannot use this + // mechanism in destructors, which is not a big deal, except for + // one place: exception_guard. So for now we are going to have + // this ugly special check which we will be able to get rid of + // once C++17 uncaught_exceptions() becomes available. + // + if (!empty_ && (!std::uncaught_exception () || exception_unwinding_dtor)) + { + *diag_stream << os_.str () << std::endl; + + if (epilogue_ != nullptr) + epilogue_ (*this); // Can throw. + } + } + + // Diagnostic facility, project specifics. + // + + void simple_prologue_base:: + operator() (const diag_record& r) const + { + relative (r.os_, relative_); + + if (type_ != nullptr) + r << type_ << ": "; + + if (name_ != nullptr) + r << name_ << ": "; + } + + void location_prologue_base:: + operator() (const diag_record& r) const + { + relative (r.os_, relative_); + + r << loc_.file << ':' << loc_.line << ':' << loc_.column << ": "; + + if (type_ != nullptr) + r << type_ << ": "; + + if (name_ != nullptr) + r << name_ << ": "; + } + + const basic_mark error ("error"); + const basic_mark warn ("warning"); + const basic_mark info ("info"); + const text_mark text; + const fail_mark fail; +} diff --git a/build2/dist/module b/build2/dist/module new file mode 100644 index 0000000..29dca71 --- /dev/null +++ b/build2/dist/module @@ -0,0 +1,26 @@ +// file : build2/dist/module -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_DIST_MODULE +#define BUILD2_DIST_MODULE + +#include +#include + +#include + +namespace build2 +{ + namespace dist + { + extern "C" void + dist_boot (scope&, const location&, unique_ptr&); + + extern "C" bool + dist_init ( + scope&, scope&, const location&, unique_ptr&, bool, bool); + } +} + +#endif // BUILD2_DIST_MODULE diff --git a/build2/dist/module.cxx b/build2/dist/module.cxx new file mode 100644 index 0000000..00b709e --- /dev/null +++ b/build2/dist/module.cxx @@ -0,0 +1,142 @@ +// file : build2/dist/module.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include +#include +#include + +#include + +#include +#include + +using namespace std; +using namespace butl; + +namespace build2 +{ + namespace dist + { + static rule rule_; + + extern "C" void + dist_boot (scope& r, const location&, unique_ptr&) + { + tracer trace ("dist::boot"); + + level5 ([&]{trace << "for " << r.out_path ();}); + + // Register meta-operation. + // + r.meta_operations.insert (dist_id, dist); + } + + extern "C" bool + dist_init (scope& r, + scope&, + const location& l, + unique_ptr&, + bool first, + bool) + { + tracer trace ("dist::init"); + + if (!first) + { + warn (l) << "multiple dist module initializations"; + return true; + } + + const dir_path& out_root (r.out_path ()); + level5 ([&]{trace << "for " << out_root;}); + + // Enter module variables. + // + if (first) + { + auto& v (var_pool); + + v.find ("dist", bool_type); + + v.find ("dist.package", string_type); + + v.find ("dist.root", dir_path_type); + v.find ("config.dist.root", dir_path_type); + + //@@ VAR type + // + v.find ("dist.cmd", string_type); + v.find ("config.dist.cmd", string_type); + + v.find ("dist.archives", strings_type); + v.find ("config.dist.archives", strings_type); + } + + // Register our wildcard rule. Do it explicitly for the alias + // to prevent something like insert(dist_id, test_id) + // taking precedence. + // + r.rules.insert (dist_id, 0, "dist", rule_); + r.rules.insert (dist_id, 0, "dist.alias", rule_); + + // Configuration. + // + // Note that we don't use any defaults for root -- the location + // must be explicitly specified or we will complain if and when + // we try to dist. + // + using namespace config; + + bool s (specified (r, "config.dist")); + + // dist.root + // + { + value& v (r.assign ("dist.root")); + + if (s) + { + const value& cv (optional_absolute (r, "config.dist.root")); + + if (cv && !cv.empty ()) + v = cv; + } + } + + // dist.cmd + // + { + value& v (r.assign ("dist.cmd")); + + if (s) + { + const value& cv (required (r, "config.dist.cmd", "install").first); + + if (cv && !cv.empty ()) + v = cv; + } + else + v = "install"; + } + + // dist.archives + // + { + value& v (r.assign ("dist.archives")); + + if (s) + { + const value& cv (optional (r, "config.dist.archives")); + + if (cv && !cv.empty ()) + v = cv; + } + } + + return true; + } + } +} diff --git a/build2/dist/operation b/build2/dist/operation new file mode 100644 index 0000000..eb5da76 --- /dev/null +++ b/build2/dist/operation @@ -0,0 +1,18 @@ +// file : build2/dist/operation -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_DIST_OPERATION +#define BUILD2_DIST_OPERATION + +#include + +namespace build2 +{ + namespace dist + { + extern meta_operation_info dist; + } +} + +#endif // BUILD2_DIST_OPERATION diff --git a/build2/dist/operation.cxx b/build2/dist/operation.cxx new file mode 100644 index 0000000..24bdd31 --- /dev/null +++ b/build2/dist/operation.cxx @@ -0,0 +1,459 @@ +// file : build2/dist/operation.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace butl; + +namespace build2 +{ + namespace dist + { + static void + dist_meta_operation_pre () + { + // Reset the dependency state so that we don't end up with stray + // files from previous batches. + // + // @@ This is called too late, after we have bootstrapped the + // project. + // + //reset (); + } + + static operation_id + dist_operation_pre (operation_id o) + { + if (o != default_id) + fail << "explicit operation specified for dist meta-operation"; + + return o; + } + + static void + dist_match (action, action_targets&) + { + // Don't match anything -- see execute (). + } + + // install -d + // + static void + install (const string& cmd, const dir_path&); + + // install + // + static void + install (const string& cmd, file&, const dir_path&); + + // cd && tar|zip ... . + // + static void + archive (const dir_path& root, const string& pkg, const string& ext); + + static void + dist_execute (action, const action_targets& ts, bool) + { + tracer trace ("dist_execute"); + + // For now we assume all the targets are from the same project. + // + target& t (*static_cast (ts[0])); + scope* rs (t.base_scope ().root_scope ()); + + if (rs == nullptr) + fail << "out of project target " << t; + + const dir_path& out_root (rs->out_path ()); + const dir_path& src_root (rs->src_path ()); + + if (out_root == src_root) + fail << "in-tree distribution of target " << t << + info << "distribution requires out-of-tree build"; + + // Make sure we have the necessary configuration before + // we get down to business. + // + auto l (rs->vars["dist.root"]); + + if (!l || l->empty ()) + fail << "unknown root distribution directory" << + info << "did you forget to specify config.dist.root?"; + + const dir_path& dist_root (as (*l)); + + if (!dir_exists (dist_root)) + fail << "root distribution directory " << dist_root + << " does not exist"; + + l = rs->vars["dist.package"]; + + if (!l || l->empty ()) + fail << "unknown distribution package name" << + info << "did you forget to set dist.package?"; + + const string& dist_package (as (*l)); + const string& dist_cmd (as (*rs->vars["dist.cmd"])); + + // Get the list of operations supported by this project. Skip + // default_id. + // + for (operations::size_type id (default_id + 1); + id < rs->operations.size (); + ++id) + { + const operation_info* oi (rs->operations[id]); + if (oi == nullptr) + continue; + + // Note that we are not calling operation_pre/post() callbacks + // here since the meta operation is dist and we know what we + // are doing. + // + current_inner_oif = oi; + current_outer_oif = nullptr; + current_mode = oi->mode; + dependency_count = 0; + + action a (dist_id, id); + + if (verb >= 6) + dump (a); + + for (void* v: ts) + { + target& t (*static_cast (v)); + + if (rs != t.base_scope ().root_scope ()) + fail << "out of project target " << t; + + level5 ([&]{trace << diag_doing (a, t);}); + + match (a, t); + } + + if (verb >= 6) + dump (a); + } + + // Add buildfiles that are not normally loaded as part of the + // project, for example, the export stub. They will still be + // ignored on the next step if the user explicitly marked them + // nodist. + // + auto add_adhoc = [&src_root, &trace] (const dir_path& d, const char* f) + { + path p (d / path (f)); + if (file_exists (p)) + { + const char* e (p.extension ()); + targets.insert ( + p.directory (), + p.leaf ().base ().string (), + &extension_pool.find (e == nullptr ? "" : e), // Specified. + trace); + } + }; + + add_adhoc (src_root, "build/export.build"); + + // The same for subprojects that have been loaded. + // + if (auto l = rs->vars["subprojects"]) + { + for (auto p: as (*l)) + { + const dir_path& pd (p.second); + dir_path out_nroot (out_root / pd); + scope& nrs (scopes.find (out_nroot)); + + if (nrs.out_path () != out_nroot) // This subproject not loaded. + continue; + + const dir_path& src_nroot (nrs.src_path ()); + + if (!src_nroot.sub (src_root)) // Not a strong amalgamation. + continue; + + add_adhoc (src_nroot, "build/export.build"); + } + } + + // Collect the files. We want to take the snapshot of targets + // since updating some of them may result in more targets being + // entered. + // + action_targets files; + const variable& dist_var (var_pool.find ("dist")); + + for (const auto& pt: targets) + { + file* ft (pt->is_a ()); + + if (ft == nullptr) // Not a file. + continue; + + if (ft->dir.sub (src_root)) + { + // Include unless explicitly excluded. + // + auto l ((*ft)[dist_var]); + + if (l && !as (*l)) + level5 ([&]{trace << "excluding " << *ft;}); + else + files.push_back (ft); + + continue; + } + + if (ft->dir.sub (out_root)) + { + // Exclude unless explicitly included. + // + auto l ((*ft)[dist_var]); + + if (l && as (*l)) + { + level5 ([&]{trace << "including " << *ft;}); + files.push_back (ft); + } + + continue; + } + } + + // Make sure what we need to distribute is up to date. + // + { + if (perform.meta_operation_pre != nullptr) + perform.meta_operation_pre (); + + current_mif = &perform; + + if (perform.operation_pre != nullptr) + perform.operation_pre (update_id); + + current_inner_oif = &update; + current_outer_oif = nullptr; + current_mode = update.mode; + dependency_count = 0; + + action a (perform_id, update_id); + + perform.match (a, files); + perform.execute (a, files, true); // Run quiet. + + if (perform.operation_post != nullptr) + perform.operation_post (update_id); + + if (perform.meta_operation_post != nullptr) + perform.meta_operation_post (); + } + + dir_path td (dist_root / dir_path (dist_package)); + + // Clean up the target directory. + // + // @@ Not for incremental dist? + // + if (build2::rmdir_r (td) == rmdir_status::not_empty) + fail << "unable to clean target directory " << td; + + install (dist_cmd, td); + + // Copy over all the files. + // + for (void* v: files) + { + file& t (*static_cast (v)); + + // Figure out where this file is inside the target directory. + // + dir_path d (td); + d /= t.dir.sub (src_root) + ? t.dir.leaf (src_root) + : t.dir.leaf (out_root); + + if (!dir_exists (d)) + install (dist_cmd, d); + + install (dist_cmd, t, d); + } + + // Archive if requested. + // + if (auto l = rs->vars["dist.archives"]) + { + for (const string& e: as (*l)) + archive (dist_root, dist_package, e); + } + } + + // install -d + // + static void + install (const string& cmd, const dir_path& d) + { + path reld (relative (d)); + + cstrings args {cmd.c_str (), "-d"}; + + args.push_back ("-m"); + args.push_back ("755"); + args.push_back (reld.string ().c_str ()); + args.push_back (nullptr); + + if (verb >= 2) + print_process (args); + else if (verb) + text << "dist -d " << d; + + try + { + process pr (args.data ()); + + if (!pr.wait ()) + throw failed (); + } + catch (const process_error& e) + { + error << "unable to execute " << args[0] << ": " << e.what (); + + if (e.child ()) + exit (1); + + throw failed (); + } + } + + // install + // + static void + install (const string& cmd, file& t, const dir_path& d) + { + path reld (relative (d)); + path relf (relative (t.path ())); + + cstrings args {cmd.c_str ()}; + + // Preserve timestamps. This could becomes important if, for + // example, we have pre-generated sources. Note that the + // install-sh script doesn't support this option, while both + // Linux and BSD install's do. + // + args.push_back ("-p"); + + // Assume the file is executable if the owner has execute + // permission, in which case we make it executable for + // everyone. + // + args.push_back ("-m"); + args.push_back ( + (path_permissions (t.path ()) & permissions::xu) == permissions::xu + ? "755" + : "644"); + + args.push_back (relf.string ().c_str ()); + args.push_back (reld.string ().c_str ()); + args.push_back (nullptr); + + if (verb >= 2) + print_process (args); + else if (verb) + text << "dist " << t; + + try + { + process pr (args.data ()); + + if (!pr.wait ()) + throw failed (); + } + catch (const process_error& e) + { + error << "unable to execute " << args[0] << ": " << e.what (); + + if (e.child ()) + exit (1); + + throw failed (); + } + } + + static void + archive (const dir_path& root, const string& pkg, const string& e) + { + string a (pkg + '.' + e); + + // Delete old archive for good measure. + // + path ap (root / path (a)); + if (file_exists (ap)) + rmfile (ap); + + // Use zip for .zip archives. Everything else goes to tar in the + // auto-compress mode (-a). + // + cstrings args; + if (e == "zip") + args = {"zip", "-rq", a.c_str (), pkg.c_str (), nullptr}; + else + args = {"tar", "-a", "-cf", a.c_str (), pkg.c_str (), nullptr}; + + if (verb >= 2) + print_process (args); + else if (verb) + text << args[0] << " " << ap; + + try + { + // Change child's working directory to dist_root. + // + process pr (root.string ().c_str (), args.data ()); + + if (!pr.wait ()) + throw failed (); + } + catch (const process_error& e) + { + error << "unable to execute " << args[0] << ": " << e.what (); + + if (e.child ()) + exit (1); + + throw failed (); + } + } + + meta_operation_info dist { + "dist", + "distribute", + "distributing", + "has nothing to distribute", // We cannot "be distributed". + &dist_meta_operation_pre, + &dist_operation_pre, + &load, // normal load + &search, // normal search + &dist_match, + &dist_execute, + nullptr, // operation post + nullptr // meta-operation post + }; + } +} diff --git a/build2/dist/rule b/build2/dist/rule new file mode 100644 index 0000000..2fbf33b --- /dev/null +++ b/build2/dist/rule @@ -0,0 +1,29 @@ +// file : build2/dist/rule -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_DIST_RULE +#define BUILD2_DIST_RULE + +#include +#include +#include +#include + +namespace build2 +{ + namespace dist + { + class rule: public build2::rule + { + public: + virtual match_result + match (action, target&, const std::string&) const; + + virtual recipe + apply (action, target&, const match_result&) const; + }; + } +} + +#endif // BUILD2_DIST_RULE diff --git a/build2/dist/rule.cxx b/build2/dist/rule.cxx new file mode 100644 index 0000000..10f35f5 --- /dev/null +++ b/build2/dist/rule.cxx @@ -0,0 +1,55 @@ +// file : build2/dist/rule.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include +#include +#include +#include + +using namespace std; + +namespace build2 +{ + namespace dist + { + match_result rule:: + match (action, target& t, const std::string&) const + { + return t; // We always match. + } + + recipe rule:: + apply (action a, target& t, const match_result&) const + { + const dir_path& out_root (t.root_scope ().out_path ()); + + auto r (group_prerequisite_members (a, t, false)); + for (auto i (r.begin ()); i != r.end (); ++i) + { + prerequisite_member p (*i); + + // Skip prerequisites imported from other projects. + // + if (p.proj () != nullptr) + continue; + + // If we can, go inside see-through groups. + // + if (p.type ().see_through && i.enter_group ()) + continue; + + target& pt (p.search ()); + + // Don't match targets that are outside of our project. + // + if (pt.dir.sub (out_root)) + build2::match (a, pt); + } + + return noop_recipe; // We will never be executed. + } + } +} diff --git a/build2/dump b/build2/dump new file mode 100644 index 0000000..8ac600e --- /dev/null +++ b/build2/dump @@ -0,0 +1,18 @@ +// file : build2/dump -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_DUMP +#define BUILD2_DUMP + +#include + +namespace build2 +{ + // Dump the state pertaining to the specified action. + // + void + dump (action); +} + +#endif // BUILD2_DUMP diff --git a/build2/dump.cxx b/build2/dump.cxx new file mode 100644 index 0000000..19172cf --- /dev/null +++ b/build2/dump.cxx @@ -0,0 +1,253 @@ +// file : build2/dump.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include +#include + +#include +#include +#include +#include +#include + +using namespace std; + +namespace build2 +{ + static void + dump_variable (ostream& os, const variable& var, const value& val) + { + os << var.name << " = "; + + if (val.null ()) + os << "[null]"; + else + os << val.data_; + } + + static void + dump_variables (ostream& os, string& ind, const variable_map& vars) + { + for (const auto& e: vars) + { + os << endl + << ind; + + dump_variable (os, e.first, e.second); + } + } + + static void + dump_variables (ostream& os, string& ind, const variable_type_map& vtm) + { + for (const auto& vt: vtm) + { + const target_type& t (vt.first); + const variable_pattern_map& vpm (vt.second); + + for (const auto& vp: vpm) + { + const string p (vp.first); + const variable_map& vars (vp.second); + + os << endl + << ind; + + if (t != target::static_type) + os << t.name << '{'; + + os << p; + + if (t != target::static_type) + os << '}'; + + os << ':'; + + if (vars.size () == 1) + { + os << ' '; + dump_variable (os, vars.begin ()->first, vars.begin ()->second); + } + else + { + os << endl + << ind << '{'; + ind += " "; + dump_variables (os, ind, vars); + ind.resize (ind.size () - 2); + os << endl + << ind << '}'; + } + } + } + } + + static void + dump_target (ostream& os, string& ind, action a, const target& t) + { + os << ind << t; + + if (t.group != nullptr) + os << "->" << *t.group; + + os << ':'; + + for (const prerequisite& p: t.prerequisites) + { + os << ' '; + + // Print it as target if one has been cached. + // + if (p.target != nullptr) + os << *p.target; + else + os << p; + } + + // If the target has been matched to a rule, also print resolved + // prerequisite targets. + // + if (t.recipe (a)) + { + bool first (true); + for (const target* pt: t.prerequisite_targets) + { + if (pt == nullptr) // Skipped. + continue; + + os << (first ? " | " : " ") << *pt; + first = false; + } + } + + // Print target-specific variables. + // + if (!t.vars.empty ()) + { + os << endl + << ind << '{'; + ind += " "; + dump_variables (os, ind, t.vars); + ind.resize (ind.size () - 2); + os << endl + << ind << '}'; + } + } + + static void + dump_scope (ostream& os, + string& ind, + action a, + scope_map::const_iterator& i) + { + scope& p (*i->second); + const dir_path& d (i->first); + ++i; + + // We don't want the extra notations (e.g., ~/) provided by + // diag_relative() since we want the path to be relative to + // the outer scope. + // + os << ind << relative (d) << ":" << endl + << ind << '{'; + + const dir_path* orb (relative_base); + relative_base = &d; + + ind += " "; + + bool vb (false), sb (false); // Variable/scope block. + + // Target type/pattern-sepcific variables. + // + if (!p.target_vars.empty ()) + { + dump_variables (os, ind, p.target_vars); + vb = true; + } + + // Scope variables. + // + if (!p.vars.empty ()) + { + if (vb) + os << endl; + + dump_variables (os, ind, p.vars); + vb = true; + } + + // Nested scopes of which we are an immediate parent. + // + for (auto e (scopes.end ()); i != e && i->second->parent_scope () == &p;) + { + // See what kind of scope entry this is. It can be: + // + // 1. Out-of-project scope. + // 2. In-project out entry. + // 3. In-project src entry. + // + // We want to print #2 and #3 as a single, unified scope. + // + scope& s (*i->second); + if (s.src_path_ != s.out_path_ && s.src_path_ == &i->first) + { + ++i; + continue; + } + + if (vb) + { + os << endl; + vb = false; + } + + if (sb) + os << endl; // Extra newline between scope blocks. + + os << endl; + dump_scope (os, ind, a, i); + sb = true; + } + + // Targets. + // + for (const auto& pt: targets) + { + const target& t (*pt); + + if (&p != &t.base_scope ()) + continue; + + if (vb || sb) + { + os << endl; + vb = sb = false; + } + + os << endl; + dump_target (os, ind, a, t); + } + + ind.resize (ind.size () - 2); + relative_base = orb; + + os << endl + << ind << '}'; + } + + void + dump (action a) + { + auto i (scopes.begin ()); + assert (i->second == global_scope); + + string ind; + ostream& os (*diag_stream); + dump_scope (os, ind, a, i); + os << endl; + } +} diff --git a/build2/file b/build2/file new file mode 100644 index 0000000..21da0c0 --- /dev/null +++ b/build2/file @@ -0,0 +1,144 @@ +// file : build2/file -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_FILE +#define BUILD2_FILE + +#include +#include + +#include +#include +#include // list_value + +namespace build2 +{ + class target; + class location; + class prerequisite_key; + + using subprojects = std::map; + + extern const dir_path build_dir; // build + extern const dir_path bootstrap_dir; // build/bootstrap + + extern const path root_file; // build/root.build + extern const path bootstrap_file; // build/bootstrap.build + extern const path src_root_file; // build/bootstrap/src-root.build + + bool + is_src_root (const dir_path&); + + bool + is_out_root (const dir_path&); + + // Given an src_base directory, look for a project's src_root + // based on the presence of known special files. Return empty + // path if not found. + // + dir_path + find_src_root (const dir_path&); + + // The same as above but for project's out. Note that we also + // check whether a directory happens to be src_root, in case + // this is an in-tree build. The second argument is the out + // flag that is set to true if this is src_root. + // + dir_path + find_out_root (const dir_path&, bool* src = nullptr); + + void + source (const path& buildfile, scope& root, scope& base); + + // As above but first check if this buildfile has already been + // sourced for the base scope. + // + void + source_once (const path& buildfile, scope& root, scope& base); + + // As above but checks against the specified scope rather than base. + // + void + source_once (const path& buildfile, scope& root, scope& base, scope& once); + + // Create project's root scope. Only set the src_root variable if the + // passed src_root value is not empty. + // + scope& + create_root (const dir_path& out_root, const dir_path& src_root); + + // Setup root scope. Note that it assume the src_root variable + // has already been set. + // + void + setup_root (scope&); + + // Setup the base scope (set *_base variables, etc). + // + scope& + setup_base (scope_map::iterator, + const dir_path& out_base, + const dir_path& src_base); + + // Bootstrap the project's root scope, the out part. + // + void + bootstrap_out (scope& root); + + // Bootstrap the project's root scope, the src part. Return true if + // we loaded anything (which confirms the src_root is not bogus). + // + bool + bootstrap_src (scope& root); + + // Create and bootstrap outer root scopes, if any. Loading is + // done by load_root_pre() below. + // + void + create_bootstrap_outer (scope& root); + + // Create and bootstrap inner root scopes between root and base, + // if any. Return the innermost created root scope or root if + // none were created. Loading is done by load_root_pre() below. + // + scope& + create_bootstrap_inner (scope& root, const dir_path& out_base); + + // Load project's root[-pre].build unless already loaded. Also + // make sure all outer root scopes are loaded prior to loading + // this root scope. + // + void + load_root_pre (scope& root); + + // Import has two phases: the first is triggered by the import + // directive in the buildfile. It will try to find and load the + // project. Failed that, it will return the project-qualified + // name of the target which will be used to create a project- + // qualified prerequisite. This gives the rule that will be + // searching this prerequisite a chance to do some target-type + // specific search. For example, a C++ link rule can search + // for lib{} prerequisites in the C++ compiler default library + // search paths (so that we end up with functionality identical + // to -lfoo). If, however, the rule didn't do any of that (or + // failed to find anything usable), it calls the standard + // prerequisite search() function which sees this is a project- + // qualified prerequisite and goes straight to the second phase + // of import. Here, currently, we simply fail but in the future + // this will be the place where we can call custom "last resort" + // import hooks. For example, we can hook a package manager that + // will say, "Hey, I see you are trying to import foo and I see + // there is a package foo available in repository bar. Wanna + // download and use it?" + // + names + import (scope& base, name, const location&); + + target& + import (const prerequisite_key&); +} + +#include + +#endif // BUILD2_FILE diff --git a/build2/file.cxx b/build2/file.cxx new file mode 100644 index 0000000..cdaa79a --- /dev/null +++ b/build2/file.cxx @@ -0,0 +1,980 @@ +// file : build2/file.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include +#include // move() +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include + +using namespace std; +using namespace butl; + +namespace build2 +{ + const dir_path build_dir ("build"); + const dir_path bootstrap_dir ("build/bootstrap"); + + const path root_file ("build/root.build"); + const path bootstrap_file ("build/bootstrap.build"); + const path src_root_file ("build/bootstrap/src-root.build"); + + bool + is_src_root (const dir_path& d) + { + // @@ Can we have root without bootstrap? I don't think so. + // + return file_exists (d / bootstrap_file) || file_exists (d / root_file); + } + + bool + is_out_root (const dir_path& d) + { + return file_exists (d / src_root_file); + } + + dir_path + find_src_root (const dir_path& b) + { + for (dir_path d (b); !d.root () && d != home; d = d.directory ()) + { + if (is_src_root (d)) + return d; + } + + return dir_path (); + } + + dir_path + find_out_root (const dir_path& b, bool* src) + { + for (dir_path d (b); !d.root () && d != home; d = d.directory ()) + { + bool s (false); + if ((s = is_src_root (d)) || is_out_root (d)) // Order is important! + { + if (src != nullptr) + *src = s; + + return d; + } + } + + return dir_path (); + } + + static void + source (const path& bf, scope& root, scope& base, bool boot) + { + tracer trace ("source"); + + try + { + ifstream ifs (bf.string ()); + if (!ifs.is_open ()) + fail << "unable to open " << bf; + + ifs.exceptions (ifstream::failbit | ifstream::badbit); + + level5 ([&]{trace << "sourcing " << bf;}); + + parser p (boot); + p.parse_buildfile (ifs, bf, root, base); + } + catch (const ifstream::failure&) + { + fail << "unable to read buildfile " << bf; + } + } + + void + source (const path& bf, scope& root, scope& base) + { + return source (bf, root, base, false); + } + + void + source_once (const path& bf, scope& root, scope& base, scope& once) + { + tracer trace ("source_once"); + + if (!once.buildfiles.insert (bf).second) + { + level5 ([&]{trace << "skipping already sourced " << bf;}); + return; + } + + source (bf, root, base); + } + + scope& + create_root (const dir_path& out_root, const dir_path& src_root) + { + auto i (scopes.insert (out_root, nullptr, true, true)); + scope& rs (*i->second); + + // Set out_path. src_path is set in setup_root() below. + // + if (rs.out_path_ != &i->first) + { + assert (rs.out_path_ == nullptr); + rs.out_path_ = &i->first; + } + + // Enter built-in meta-operation and operation names. Loading of + // modules (via the src bootstrap; see below) can result in + // additional meta/operations being added. + // + if (rs.meta_operations.empty ()) + { + rs.meta_operations.insert (perform_id, perform); + + rs.operations.insert (default_id, default_); + rs.operations.insert (update_id, update); + rs.operations.insert (clean_id, clean); + } + + // If this is already a root scope, verify that things are + // consistent. + // + { + value& v (rs.assign ("out_root")); + + if (!v) + v = out_root; + else + { + const dir_path& p (as (v)); + + if (p != out_root) + fail << "new out_root " << out_root << " does not match " + << "existing " << p; + } + } + + if (!src_root.empty ()) + { + value& v (rs.assign ("src_root")); + + if (!v) + v = src_root; + else + { + const dir_path& p (as (v)); + + if (p != src_root) + fail << "new src_root " << src_root << " does not match " + << "existing " << p; + } + } + + return rs; + } + + void + setup_root (scope& s) + { + value& v (s.assign ("src_root")); + assert (v); + + // Register and set src_path. + // + if (s.src_path_ == nullptr) + s.src_path_ = &scopes.insert (as (v), &s, false, true)->first; + } + + scope& + setup_base (scope_map::iterator i, + const dir_path& out_base, + const dir_path& src_base) + { + scope& s (*i->second); + + // Set src/out_path. The key (i->first) can be either out_base + // or src_base. + // + if (s.out_path_ == nullptr) + { + s.out_path_ = + i->first == out_base + ? &i->first + : &scopes.insert (out_base, &s, true, false)->first; + } + + if (s.src_path_ == nullptr) + { + s.src_path_ = + i->first == src_base + ? &i->first + : &scopes.insert (src_base, &s, false, false)->first; + } + + // Set src/out_base variables. + // + { + value& v (s.assign ("out_base")); + + if (!v) + v = out_base; + else + assert (as (v) == out_base); + } + + { + value& v (s.assign ("src_base")); + + if (!v) + v = src_base; + else + assert (as (v) == src_base); + } + + return s; + } + + void + bootstrap_out (scope& root) + { + path bf (root.out_path () / path ("build/bootstrap/src-root.build")); + + if (!file_exists (bf)) + return; + + //@@ TODO: if bootstrap files can source other bootstrap files + // (the way to express dependecies), then we need a way to + // prevent multiple sourcing. We handle it here but we still + // need something like source_once (once [scope] source). + // + source_once (bf, root, root); + } + + // Extract the specified variable value from a buildfile. It is + // expected to be the first non-comment line and not to rely on + // any variable expansion other than those from the global scope. + // + static value + extract_variable (const path& bf, const char* var) + { + try + { + ifstream ifs (bf.string ()); + if (!ifs.is_open ()) + fail << "unable to open " << bf; + + ifs.exceptions (ifstream::failbit | ifstream::badbit); + + path rbf (diag_relative (bf)); + + lexer lex (ifs, rbf.string ()); + token t (lex.next ()); + token_type tt; + + if (t.type != token_type::name || t.value != var || + ((tt = lex.next ().type) != token_type::equal && + tt != token_type::equal_plus && + tt != token_type::plus_equal)) + { + error << "variable '" << var << "' expected as first line in " << rbf; + throw failed (); // Suppress "used uninitialized" warning. + } + + parser p; + temp_scope tmp (*global_scope); + p.parse_variable (lex, tmp, t.value, tt); + + auto l (tmp.vars[var]); + assert (l.defined ()); + value& v (*l); + return move (v); // Steal the value, the scope is going away. + } + catch (const ifstream::failure&) + { + fail << "unable to read buildfile " << bf; + } + + return value (); // Never reaches. + } + + // Extract the project name from bootstrap.build. + // + static string + find_project_name (const dir_path& out_root, + const dir_path& fallback_src_root, + bool* src_hint = nullptr) + { + tracer trace ("find_project_name"); + + // Load the project name. If this subdirectory is the subproject's + // src_root, then we can get directly to that. Otherwise, we first + // have to discover its src_root. + // + const dir_path* src_root; + value src_root_v; // Need it to live until the end. + + if (src_hint != nullptr ? *src_hint : is_src_root (out_root)) + src_root = &out_root; + else + { + path f (out_root / src_root_file); + + if (!fallback_src_root.empty () && !file_exists (f)) + src_root = &fallback_src_root; + else + { + src_root_v = extract_variable (f, "src_root"); + src_root = &as (src_root_v); + level5 ([&]{trace << "extracted src_root " << *src_root << " for " + << out_root;}); + } + } + + string name; + { + value v (extract_variable (*src_root / bootstrap_file, "project")); + name = move (as (v)); + } + + level5 ([&]{trace << "extracted project name '" << name << "' for " + << *src_root;}); + return name; + } + + // Scan the specified directory for any subprojects. If a subdirectory + // is a subproject, then enter it into the map, handling the duplicates. + // Otherwise, scan the subdirectory recursively. + // + static void + find_subprojects (subprojects& sps, + const dir_path& d, + const dir_path& root, + bool out) + { + tracer trace ("find_subprojects"); + + for (const dir_entry& de: dir_iterator (d)) + { + // If this is a link, then type() will try to stat() it. And if + // the link is dangling or points to something inaccessible, it + // will fail. + // + try + { + if (de.type () != entry_type::directory) + continue; + } + catch (const system_error& e) + { + continue; + } + + dir_path sd (d / path_cast (de.path ())); + + bool src (false); + if (!((out && is_out_root (sd)) || (src = is_src_root (sd)))) + { + find_subprojects (sps, sd, root, out); + continue; + } + + // Calculate relative subdirectory for this subproject. + // + dir_path dir (sd.leaf (root)); + level5 ([&]{trace << "subproject " << sd << " as " << dir;}); + + // Load its name. Note that here we don't use fallback src_root + // since this function is used to scan both out_root and src_root. + // + string name (find_project_name (sd, dir_path (), &src)); + + // If the name is empty, then is is an unnamed project. While the + // 'project' variable stays empty, here we come up with a surrogate + // name for a key. The idea is that such a key should never conflict + // with a real project name. We ensure this by using the project's + // sub-directory and appending trailing '/' to it. + // + if (name.empty ()) + name = dir.posix_string () + '/'; + + // @@ Can't use move() because we may need the values in diagnostics + // below. Looks like C++17 try_emplace() is what we need. + // + auto rp (sps.emplace (name, dir)); + + // Handle duplicates. + // + if (!rp.second) + { + const dir_path& dir1 (rp.first->second); + + if (dir != dir1) + fail << "inconsistent subproject directories for " << name << + info << "first alternative: " << dir1 << + info << "second alternative: " << dir; + + level6 ([&]{trace << "skipping duplicate";}); + } + } + } + + bool + bootstrap_src (scope& root) + { + tracer trace ("bootstrap_src"); + + bool r (false); + + const dir_path& out_root (root.out_path ()); + const dir_path& src_root (root.src_path ()); + + path bf (src_root / path ("build/bootstrap.build")); + + if (file_exists (bf)) + { + // We assume that bootstrap out cannot load this file explicitly. It + // feels wrong to allow this since that makes the whole bootstrap + // process hard to reason about. But we may try to bootstrap the + // same root scope multiple time. + // + if (root.buildfiles.insert (bf).second) + source (bf, root, root, true); + else + level5 ([&]{trace << "skipping already sourced " << bf;}); + + r = true; + } + + // See if we are a part of an amalgamation. There are two key + // players: the outer root scope which may already be present + // (i.e., we were loaded as part of an amalgamation) and the + // amalgamation variable that may or may not be set by the + // user (in bootstrap.build) or by an earlier call to this + // function for the same scope. When set by the user, the + // empty special value means that the project shall not be + // amalgamated (and which we convert to NULL below). When + // calculated, the NULL value indicates that we are not + // amalgamated. + // + { + auto rp (root.vars.assign ("amalgamation")); // Set NULL by default. + value& v (rp.first); + + if (v && v.empty ()) // Convert empty to NULL. + v = nullptr; + + if (scope* aroot = root.parent_scope ()->root_scope ()) + { + const dir_path& ad (aroot->out_path ()); + dir_path rd (ad.relative (out_root)); + + // If we already have the amalgamation variable set, verify + // that aroot matches its value. + // + if (!rp.second) + { + if (!v) + { + fail << out_root << " cannot be amalgamated" << + info << "amalgamated by " << ad; + } + else + { + const dir_path& vd (as (v)); + + if (vd != rd) + { + fail << "inconsistent amalgamation of " << out_root << + info << "specified: " << vd << + info << "actual: " << rd << " by " << ad; + } + } + } + else + { + // Otherwise, use the outer root as our amalgamation. + // + level5 ([&]{trace << out_root << " amalgamated as " << rd;}); + v = move (rd); + } + } + else if (rp.second) + { + // If there is no outer root and the amalgamation variable + // hasn't been set, then we need to check if any of the + // outer directories is a project's out_root. If so, then + // that's our amalgamation. + // + const dir_path& ad (find_out_root (out_root.directory ())); + + if (!ad.empty ()) + { + dir_path rd (ad.relative (out_root)); + level5 ([&]{trace << out_root << " amalgamated as " << rd;}); + v = move (rd); + } + } + } + + // See if we have any subprojects. In a sense, this is the other + // side/direction of the amalgamation logic above. Here, the + // subprojects variable may or may not be set by the user (in + // bootstrap.build) or by an earlier call to this function for + // the same scope. When set by the user, the empty special value + // means that there are no subproject and none should be searched + // for (and which we convert to NULL below). Otherwise, it is a + // list of directory[=project] pairs. The directory must be + // relative to our out_root. If the project name is not specified, + // then we have to figure it out. When subprojects are calculated, + // the NULL value indicates that we found no subprojects. + // + { + const variable& var (var_pool.find ("subprojects")); + auto rp (root.vars.assign(var)); // Set NULL by default. + value& v (rp.first); + + if (rp.second) + { + // No subprojects set so we need to figure out if there are any. + // + // First we are going to scan our out_root and find all the + // pre-configured subprojects. Then, if out_root != src_root, + // we are going to do the same for src_root. Here, however, + // we need to watch out for duplicates. + // + subprojects sps; + + if (dir_exists (out_root)) + find_subprojects (sps, out_root, out_root, true); + + if (out_root != src_root) + find_subprojects (sps, src_root, src_root, false); + + if (!sps.empty ()) // Keep it NULL if no subprojects. + v = move (sps); + } + else if (v) + { + // Convert empty to NULL. + // + if (v.empty ()) + v = nullptr; + else + { + // Pre-scan the value and convert it to the "canonical" form, + // that is, a list of simple=dir pairs. + // + for (auto i (v.data_.begin ()); i != v.data_.end (); ++i) + { + bool p (i->pair != '\0'); + + if (p) + { + // Project name. + // + if (!assign (*i) || as (*i).empty ()) + fail << "expected project name instead of '" << *i << "' in " + << "the subprojects variable"; + + ++i; // Got to have the second half of the pair. + } + + if (!assign (*i)) + fail << "expected directory instead of '" << *i << "' in the " + << "subprojects variable"; + + auto& d (as (*i)); + + // Figure out the project name if the user didn't specify one. + // + if (!p) + { + // Pass fallback src_root since this is a subproject that + // was specified by the user so it is most likely in our + // src. + // + string n (find_project_name (out_root / d, src_root / d)); + + // See find_subprojects() for details on unnamed projects. + // + if (n.empty ()) + n = d.posix_string () + '/'; + + i = v.data_.emplace (i, move (n)); + + i->pair = '='; + ++i; + } + } + + // Make it of the map type. + // + assign (v, var); + } + } + } + + return r; + } + + void + create_bootstrap_outer (scope& root) + { + auto l (root.vars["amalgamation"]); + + if (!l) + return; + + const dir_path& d (as (*l)); + dir_path out_root (root.out_path () / d); + out_root.normalize (); + + // src_root is a bit more complicated. Here we have three cases: + // + // 1. Amalgamation's src_root is "parallel" to the sub-project's. + // 2. Amalgamation's src_root is the same as its out_root. + // 3. Some other pre-configured (via src-root.build) src_root. + // + // So we need to try all these cases in some sensible order. + // #3 should probably be tried first since that src_root was + // explicitly configured by the user. After that, #2 followed + // by #1 seems reasonable. + // + scope& rs (create_root (out_root, dir_path ())); + bootstrap_out (rs); // #3 happens here, if at all. + + value& v (rs.assign ("src_root")); + + if (!v) + { + if (is_src_root (out_root)) // #2 + v = out_root; + else // #1 + { + dir_path src_root (root.src_path () / d); + src_root.normalize (); + v = move (src_root); + } + } + + setup_root (rs); + + bootstrap_src (rs); + create_bootstrap_outer (rs); + + // Check if we are strongly amalgamated by this outer root scope. + // + if (root.src_path ().sub (rs.src_path ())) + root.strong_ = rs.strong_scope (); // Itself or some outer scope. + } + + scope& + create_bootstrap_inner (scope& root, const dir_path& out_base) + { + if (auto l = root.vars["subprojects"]) + { + for (const name& n: *l) + { + if (n.pair != '\0') + continue; // Skip project names. + + dir_path out_root (root.out_path () / n.dir); + + if (!out_base.sub (out_root)) + continue; + + // The same logic to src_root as in create_bootstrap_outer(). + // + scope& rs (create_root (out_root, dir_path ())); + bootstrap_out (rs); + + value& v (rs.assign ("src_root")); + + if (!v) + v = is_src_root (out_root) + ? out_root + : (root.src_path () / n.dir); + + setup_root (rs); + + bootstrap_src (rs); + + // Check if we strongly amalgamated this inner root scope. + // + if (rs.src_path ().sub (root.src_path ())) + rs.strong_ = root.strong_scope (); // Itself or some outer scope. + + // See if there are more inner roots. + // + return create_bootstrap_inner (rs, out_base); + } + } + + return root; + } + + void + load_root_pre (scope& root) + { + tracer trace ("root_pre"); + + // First load outer roots, if any. + // + if (scope* rs = root.parent_scope ()->root_scope ()) + load_root_pre (*rs); + + // Finish off loading bootstrapped modules. + // + for (auto& p: root.modules) + { + const string& n (p.first); + module_state& s (p.second); + + if (s.boot) + { + load_module (false, n, root, root, s.loc); + assert (!s.boot); + } + } + + // Load root.build. + // + path bf (root.src_path () / path ("build/root.build")); + + if (file_exists (bf)) + source_once (bf, root, root); + } + + names + import (scope& ibase, name target, const location& loc) + { + tracer trace ("import"); + + // If there is no project specified for this target, then our + // run will be short and sweet: we simply return it as empty- + // project-qualified and let someone else (e.g., a rule) take + // a stab at it. + // + if (target.unqualified ()) + { + target.proj = &project_name_pool.find (""); + return names {move (target)}; + } + + // Otherwise, get the project name and convert the target to + // unqualified. + // + const string& project (*target.proj); + target.proj = nullptr; + + scope& iroot (*ibase.root_scope ()); + + // Figure out this project's out_root. + // + dir_path out_root; + dir_path fallback_src_root; // We have seen this already, haven't we..? + + // First search subprojects, starting with our root and then trying + // outer roots for as long as we are inside an amalgamation. + // + for (scope* r (&iroot);; r = r->parent_scope ()->root_scope ()) + { + // First check the amalgamation itself. + // + if (r != &iroot && as (*r->vars["project"]) == project) + { + out_root = r->out_path (); + fallback_src_root = r->src_path (); + break; + } + + if (auto l = r->vars["subprojects"]) + { + const auto& m (as (*l)); + auto i (m.find (project)); + + if (i != m.end ()) + { + const dir_path& d ((*i).second); + out_root = r->out_path () / d; + fallback_src_root = r->src_path () / d; + break; + } + } + + if (!r->vars["amalgamation"]) + break; + } + + // Then try the config.import.* mechanism. + // + if (out_root.empty ()) + { + const variable& var ( + var_pool.find ("config.import." + project, dir_path_type)); + + if (auto l = iroot[var]) + { + out_root = as (*l); + + if (l.belongs (*global_scope)) // A value from command line. + { + // Process the path by making it absolute and normalized. + // + if (out_root.relative ()) + out_root = work / out_root; + + out_root.normalize (); + + // Set on our root scope (part of our configuration). + // + iroot.assign (var) = out_root; + + // Also update the command-line value. This is necessary to avoid + // a warning issued by the config module about global/root scope + // value mismatch. Not very clean. + // + dir_path& d (as (const_cast (*l))); + if (d != out_root) + d = out_root; + } + } + else + { + // If we can't find the project, convert it back into qualified + // target and return to let someone else (e.g., a rule) to take + // a stab at it. + // + target.proj = &project; + level5 ([&]{trace << "postponing " << target;}); + return names {move (target)}; + } + } + + // Bootstrap the imported root scope. This is pretty similar to + // what we do in main() except that here we don't try to guess + // src_root. + // + dir_path src_root (is_src_root (out_root) ? out_root : dir_path ()); + scope& root (create_root (out_root, src_root)); + + bootstrap_out (root); + + // Check that the bootstrap process set src_root. + // + if (auto l = root.vars["src_root"]) + { + const dir_path& p (as (*l)); + + if (!src_root.empty () && p != src_root) + fail (loc) << "bootstrapped src_root " << p << " does not match " + << "discovered " << src_root; + } + // Otherwise, use fallback if available. + // + else if (!fallback_src_root.empty ()) + { + value& v (root.assign ("src_root")); + v = move (fallback_src_root); + } + else + fail (loc) << "unable to determine src_root for imported " << project << + info << "consider configuring " << out_root; + + setup_root (root); + + bootstrap_src (root); + + // Bootstrap outer roots if any. Loading will be done by + // load_root_pre() below. + // + create_bootstrap_outer (root); + + // Load the imported root scope. + // + load_root_pre (root); + + // Create a temporary scope so that the export stub does not mess + // up any of our variables. + // + temp_scope ts (ibase); + + // "Pass" the imported project's roots to the stub. + // + ts.assign ("out_root") = move (out_root); + ts.assign ("src_root") = move (src_root); + + // Also pass the target being imported. + // + { + value& v (ts.assign ("target")); + + if (!target.empty ()) // Otherwise leave NULL. + v = move (target); + } + + // Load the export stub. Note that it is loaded in the context + // of the importing project, not the imported one. The export + // stub will normally switch to the imported root scope at some + // point. + // + path es (root.src_path () / path ("build/export.build")); + + try + { + ifstream ifs (es.string ()); + if (!ifs.is_open ()) + fail (loc) << "unable to open " << es; + + ifs.exceptions (ifstream::failbit | ifstream::badbit); + + level5 ([&]{trace << "importing " << es;}); + + // @@ Should we verify these are all unqualified names? Or maybe + // there is a use-case for the export stub to return a qualified + // name? + // + parser p; + return p.parse_export_stub (ifs, es, iroot, ts); + } + catch (const ifstream::failure&) + { + fail (loc) << "unable to read buildfile " << es; + } + + return names (); // Never reached. + } + + target& + import (const prerequisite_key& pk) + { + assert (pk.proj != nullptr); + const string& p (*pk.proj); + + // @@ We no longer have location. This is especially bad for the + // empty case, i.e., where do I need to specify the project + // name)? Looks like the only way to do this is to keep location + // in name and then in prerequisite. Perhaps one day... + // + if (!p.empty ()) + fail << "unable to import target " << pk << + info << "consider explicitly specifying its project out_root via the " + << "config.import." << p << " command line variable"; + else + fail << "unable to import target " << pk << + info << "consider adding its installation location" << + info << "or explicitly specifying its project name"; + + throw failed (); // No return. + } +} diff --git a/build2/file.ixx b/build2/file.ixx new file mode 100644 index 0000000..b446ef4 --- /dev/null +++ b/build2/file.ixx @@ -0,0 +1,12 @@ +// file : build2/file.ixx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +namespace build2 +{ + inline void + source_once (const path& bf, scope& root, scope& base) + { + return source_once (bf, root, base, base); + } +} diff --git a/build2/install/module b/build2/install/module new file mode 100644 index 0000000..91b5ecb --- /dev/null +++ b/build2/install/module @@ -0,0 +1,26 @@ +// file : build2/install/module -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_INSTALL_MODULE +#define BUILD2_INSTALL_MODULE + +#include +#include + +#include + +namespace build2 +{ + namespace install + { + extern "C" void + install_boot (scope&, const location&, unique_ptr&); + + extern "C" bool + install_init ( + scope&, scope&, const location&, unique_ptr&, bool, bool); + } +} + +#endif // BUILD2_INSTALL_MODULE diff --git a/build2/install/module.cxx b/build2/install/module.cxx new file mode 100644 index 0000000..c6ad717 --- /dev/null +++ b/build2/install/module.cxx @@ -0,0 +1,188 @@ +// file : build2/install/module.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +using namespace std; +using namespace butl; + +namespace build2 +{ + namespace install + { + // Set install..* values based on config.install..* ones + // or the defaults. If none of config.install.* values were specified, + // then we do omitted/delayed configuration. Note that we still need + // to set all the install.* values to defaults, as if we had the + // default configuration. + // + // If override is true, then override values that came from outer + // configurations. We have to do this for paths that contain the + // package name. + // + template + static void + set_var (bool spec, + scope& r, + const char* name, + const char* var, + const T* dv, + bool override = false) + { + string vn; + const value* cv (nullptr); + + if (spec) + { + vn = "config.install."; + vn += name; + vn += var; + const variable& vr ( + var_pool.find (move (vn), &value_traits::value_type)); + + cv = dv != nullptr + ? &config::required (r, vr, *dv, override).first.get () + : &config::optional (r, vr); + } + + vn = "install."; + vn += name; + vn += var; + const variable& vr ( + var_pool.find (move (vn), &value_traits::value_type)); + + value& v (r.assign (vr)); + + if (spec) + { + if (*cv && !cv->empty ()) + v = *cv; + } + else + { + if (dv != nullptr) + v = *dv; + } + } + + static void + set_dir (bool s, // specified + scope& r, // root scope + const char* n, // var name + const string& ps, // path (as string) + const string& fm = string (), // file mode + const string& dm = string (), // dir mode + const string& c = string (), // command + bool o = false) // override + { + dir_path p (ps); + set_var (s, r, n, "", p.empty () ? nullptr : &p, o); + set_var (s, r, n, ".mode", fm.empty () ? nullptr : &fm); + set_var (s, r, n, ".dir_mode", dm.empty () ? nullptr : &dm); + set_var (s, r, n, ".sudo", nullptr); + set_var (s, r, n, ".cmd", c.empty () ? nullptr : &c); + set_var (s, r, n, ".options", nullptr); + } + + static alias_rule alias_; + static file_rule file_; + + extern "C" void + install_boot (scope& r, const location&, unique_ptr&) + { + tracer trace ("install::boot"); + + level5 ([&]{trace << "for " << r.out_path ();}); + + // Register the install operation. + // + r.operations.insert (install_id, install); + } + + extern "C" bool + install_init (scope& r, + scope& b, + const location& l, + unique_ptr&, + bool first, + bool) + { + tracer trace ("install::init"); + + if (!first) + { + warn (l) << "multiple install module initializations"; + return true; + } + + const dir_path& out_root (r.out_path ()); + level5 ([&]{trace << "for " << out_root;}); + + // Enter module variables. + // + // Note that the set_dir() calls below enter some more. + // + if (first) + { + auto& v (var_pool); + + v.find ("install", dir_path_type); + } + + // Register our alias and file installer rule. + // + b.rules.insert (perform_install_id, "install.alias", alias_); + b.rules.insert (perform_install_id, "install.file", file_); + + // Configuration. + // + // Note that we don't use any defaults for root -- the location + // must be explicitly specified or the installer will complain + // if and when we try to install. + // + if (first) + { + bool s (config::specified (r, "config.install")); + const string& n (as (*r["project"])); + + set_dir (s, r, "root", "", "", "755", "install"); + set_dir (s, r, "data_root", "root", "644"); + set_dir (s, r, "exec_root", "root", "755"); + + set_dir (s, r, "sbin", "exec_root/sbin"); + set_dir (s, r, "bin", "exec_root/bin"); + set_dir (s, r, "lib", "exec_root/lib"); + set_dir (s, r, "libexec", "exec_root/libexec/" + n, "", "", "", true); + + set_dir (s, r, "data", "data_root/share/" + n, "", "", "", true); + set_dir (s, r, "include", "data_root/include"); + + set_dir (s, r, "doc", "data_root/share/doc/" + n, "", "", "", true); + set_dir (s, r, "man", "data_root/share/man"); + + set_dir (s, r, "man1", "man/man1"); + } + + // Configure "installability" for built-in target types. + // + path (b, dir_path ("doc")); // Install into install.doc. + path (b, dir_path ("man")); // Install into install.man. + path (b, dir_path ("man1")); // Install into install.man1. + + return true; + } + } +} diff --git a/build2/install/operation b/build2/install/operation new file mode 100644 index 0000000..20630d5 --- /dev/null +++ b/build2/install/operation @@ -0,0 +1,18 @@ +// file : build2/install/operation -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_INSTALL_OPERATION +#define BUILD2_INSTALL_OPERATION + +#include + +namespace build2 +{ + namespace install + { + extern operation_info install; + } +} + +#endif // BUILD2_INSTALL_OPERATION diff --git a/build2/install/operation.cxx b/build2/install/operation.cxx new file mode 100644 index 0000000..eb7cdb1 --- /dev/null +++ b/build2/install/operation.cxx @@ -0,0 +1,32 @@ +// file : build2/install/operation.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +using namespace std; +using namespace butl; + +namespace build2 +{ + namespace install + { + static operation_id + install_pre (meta_operation_id mo) + { + // Run update as a pre-operation, unless we are disfiguring. + // + return mo != disfigure_id ? update_id : 0; + } + + operation_info install { + "install", + "install", + "installing", + "has nothing to install", // We cannot "be installed". + execution_mode::first, + &install_pre, + nullptr + }; + } +} diff --git a/build2/install/rule b/build2/install/rule new file mode 100644 index 0000000..aeb7f13 --- /dev/null +++ b/build2/install/rule @@ -0,0 +1,49 @@ +// file : build2/install/rule -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_INSTALL_RULE +#define BUILD2_INSTALL_RULE + +#include +#include +#include +#include + +namespace build2 +{ + namespace install + { + class alias_rule: public rule + { + public: + virtual match_result + match (action, target&, const std::string&) const; + + virtual recipe + apply (action, target&, const match_result&) const; + }; + + class file_rule: public rule + { + public: + virtual match_result + match (action, target&, const std::string&) const; + + // Return NULL if this prerequisite should be ignored and pointer to its + // target otherwise. The default implementation ignores prerequsites that + // are outside of this target's project. + // + virtual target* + filter (action, target&, prerequisite_member) const; + + virtual recipe + apply (action, target&, const match_result&) const; + + static target_state + perform_install (action, target&); + }; + } +} + +#endif // BUILD2_INSTALL_RULE diff --git a/build2/install/rule.cxx b/build2/install/rule.cxx new file mode 100644 index 0000000..16feca8 --- /dev/null +++ b/build2/install/rule.cxx @@ -0,0 +1,410 @@ +// file : build2/install/rule.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include +#include + +#include +#include +#include +#include + +#include + +using namespace std; +using namespace butl; + +namespace build2 +{ + namespace install + { + // Lookup the install or install.* variable. Return NULL if + // not found or if the value is the special 'false' name (which + // means do not install). T is either scope or target. + // + template + static const dir_path* + lookup (T& t, const string& var) + { + auto l (t[var]); + + if (!l) + return nullptr; + + const dir_path& r (as (*l)); + return r.simple () && r.string () == "false" ? nullptr : &r; + } + + // alias_rule + // + match_result alias_rule:: + match (action, target& t, const std::string&) const + { + return t; + } + + recipe alias_rule:: + apply (action a, target& t, const match_result&) const + { + tracer trace ("install::alias_rule::apply"); + + for (prerequisite p: group_prerequisites (t)) + { + target& pt (search (p)); + + // Check if this prerequisite is explicitly "not installable", + // that is, there is the 'install' variable and its value is + // false. + // + // At first, this might seem redundand since we could have let + // the file_rule below take care of it. The nuance is this: this + // prerequsite can be in a different subproject that hasn't loaded + // the install module (and therefore has no file_rule registered). + // The typical example would be the 'tests' subproject. + // + auto l (pt["install"]); + + if (l && as (*l).string () == "false") + { + level5 ([&]{trace << "ignoring " << pt;}); + continue; + } + + build2::match (a, pt); + t.prerequisite_targets.push_back (&pt); + } + + return default_recipe; + } + + // file_rule + // + + match_result file_rule:: + match (action a, target& t, const std::string&) const + { + // First determine if this target should be installed (called + // "installable" for short). + // + if (lookup (t, "install") == nullptr) + // If this is the update pre-operation, signal that we don't match so + // that some other rule can take care of it. + // + return a.operation () == update_id ? nullptr : match_result (t, false); + + match_result mr (t, true); + + // If this is the update pre-operation, change the recipe action + // to (update, 0) (i.e., "unconditional update"). + // + if (a.operation () == update_id) + mr.recipe_action = action (a.meta_operation (), update_id); + + return mr; + } + + target* file_rule:: + filter (action, target& t, prerequisite_member p) const + { + target& pt (p.search ()); + return pt.in (t.root_scope ()) ? &pt : nullptr; + } + + recipe file_rule:: + apply (action a, target& t, const match_result& mr) const + { + if (!mr.bvalue) // Not installable. + return noop_recipe; + + // Ok, if we are here, then this means: + // + // 1. This target is installable. + // 2. The action is either + // a. (perform, install, 0) or + // b. (*, update, install) + // + // In both cases, the next step is to search, match, and collect + // all the installable prerequisites. + // + // @@ Perhaps if [noinstall] will be handled by the + // group_prerequisite_members machinery, then we can just + // run standard search_and_match()? Will need an indicator + // that it was forced (e.g., [install]) for filter() below. + // + for (prerequisite_member p: group_prerequisite_members (a, t)) + { + // Ignore unresolved targets that are imported from other projects. + // We are definitely not installing those. + // + if (p.proj () != nullptr) + continue; + + // Let a customized rule have its say. + // + target* pt (filter (a, t, p)); + if (pt == nullptr) + continue; + + // See if the user instructed us not to install it. + // + auto l ((*pt)["install"]); + if (l && as (*l).string () == "false") + continue; + + build2::match (a, *pt); + + // If the matched rule returned noop_recipe, then the target + // state will be set to unchanged as an optimization. Use this + // knowledge to optimize things on our side as well since this + // will help a lot in case of any static installable content + // (headers, documentation, etc). + // + if (pt->state () != target_state::unchanged) + t.prerequisite_targets.push_back (pt); + else + unmatch (a, *pt); // No intent to execute. + } + + // This is where we diverge depending on the operation. In the + // update pre-operation, we need to make sure that this target + // as well as all its installable prerequisites are up to date. + // + if (a.operation () == update_id) + { + // Save the prerequisite targets that we found since the + // call to match_delegate() below will wipe them out. + // + target::prerequisite_targets_type p; + + if (!t.prerequisite_targets.empty ()) + p.swap (t.prerequisite_targets); + + // Find the "real" update rule, that is, the rule that would + // have been found if we signalled that we do not match from + // match() above. + // + recipe d (match_delegate (a, t).first); + + // If we have no installable prerequisites, then simply redirect + // to it. + // + if (p.empty ()) + return d; + + // Ok, the worst case scenario: we need to cause update of + // prerequisite targets and also delegate to the real update. + // + return [pt = move (p), dr = move (d)] + (action a, target& t) mutable -> target_state + { + // Do the target update first. + // + target_state r (execute_delegate (dr, a, t)); + + // Swap our prerequisite targets back in and execute. + // + t.prerequisite_targets.swap (pt); + r |= execute_prerequisites (a, t); + pt.swap (t.prerequisite_targets); // In case we get re-executed. + + return r; + }; + } + else + return &perform_install; + } + + struct install_dir + { + dir_path dir; + string sudo; + string cmd; //@@ VAR type + const_strings_value options {nullptr}; + string mode; + string dir_mode; + }; + + // install -d + // + static void + install (const install_dir& base, const dir_path& d) + { + path reld (relative (d)); + + cstrings args; + + if (!base.sudo.empty ()) + args.push_back (base.sudo.c_str ()); + + args.push_back (base.cmd.c_str ()); + args.push_back ("-d"); + + if (base.options.d != nullptr) //@@ VAR + config::append_options (args, base.options); + + args.push_back ("-m"); + args.push_back (base.dir_mode.c_str ()); + args.push_back (reld.string ().c_str ()); + args.push_back (nullptr); + + if (verb >= 2) + print_process (args); + else if (verb) + text << "install " << d; + + try + { + process pr (args.data ()); + + if (!pr.wait ()) + throw failed (); + } + catch (const process_error& e) + { + error << "unable to execute " << args[0] << ": " << e.what (); + + if (e.child ()) + exit (1); + + throw failed (); + } + } + + // install + // + static void + install (const install_dir& base, file& t) + { + path reld (relative (base.dir)); + path relf (relative (t.path ())); + + cstrings args; + + if (!base.sudo.empty ()) + args.push_back (base.sudo.c_str ()); + + args.push_back (base.cmd.c_str ()); + + if (base.options.d != nullptr) //@@ VAR + config::append_options (args, base.options); + + args.push_back ("-m"); + args.push_back (base.mode.c_str ()); + args.push_back (relf.string ().c_str ()); + args.push_back (reld.string ().c_str ()); + args.push_back (nullptr); + + if (verb >= 2) + print_process (args); + else if (verb) + text << "install " << t; + + try + { + process pr (args.data ()); + + if (!pr.wait ()) + throw failed (); + } + catch (const process_error& e) + { + error << "unable to execute " << args[0] << ": " << e.what (); + + if (e.child ()) + exit (1); + + throw failed (); + } + } + + // Resolve installation directory name to absolute directory path, + // creating leading directories as necessary. + // + static install_dir + resolve (scope& s, dir_path d, const string* var = nullptr) + { + install_dir r; + + if (d.absolute ()) + { + d.normalize (); + + // Make sure it already exists (this will normally be + // install.root with everything else defined in term of it). + // + if (!dir_exists (d)) + fail << "installation directory " << d << " does not exist"; + } + else + { + // If it is relative, then the first component is treated + // as the installation directory name, e.g., bin, sbin, lib, + // etc. Look it up and recurse. + // + const string& sn (*d.begin ()); + const string var ("install." + sn); + if (const dir_path* dn = lookup (s, var)) + { + r = resolve (s, *dn, &var); + d = r.dir / dir_path (++d.begin (), d.end ()); + d.normalize (); + + if (!dir_exists (d)) + install (r, d); // install -d + } + else + fail << "unknown installation directory name " << sn << + info << "did you forget to specify config." << var << "?"; + } + + r.dir = move (d); + + // Override components in install_dir if we have our own. + // + if (var != nullptr) + { + if (auto l = s[*var + ".sudo"]) r.sudo = as (*l); + if (auto l = s[*var + ".cmd"]) r.cmd = as (*l); + if (auto l = s[*var + ".mode"]) r.mode = as (*l); + if (auto l = s[*var + ".dir_mode"]) r.dir_mode = as (*l); + if (auto l = s[*var + ".options"]) r.options = as (*l); + } + + // Set defaults for unspecified components. + // + if (r.cmd.empty ()) r.cmd = "install"; + if (r.mode.empty ()) r.mode = "644"; + if (r.dir_mode.empty ()) r.dir_mode = "755"; + + return r; + } + + target_state file_rule:: + perform_install (action a, target& t) + { + file& ft (static_cast (t)); + assert (!ft.path ().empty ()); // Should have been assigned by update. + + // First handle installable prerequisites. + // + target_state r (execute_prerequisites (a, t)); + + // Resolve and, if necessary, create target directory. + // + install_dir d ( + resolve (t.base_scope (), + as (*t["install"]))); // We know it's there. + + // Override mode if one was specified. + // + if (auto l = t["install.mode"]) + d.mode = as (*l); + + install (d, ft); + return (r |= target_state::changed); + } + } +} diff --git a/build2/install/utility b/build2/install/utility new file mode 100644 index 0000000..3cfc3e2 --- /dev/null +++ b/build2/install/utility @@ -0,0 +1,40 @@ +// file : build2/install/utility -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_INSTALL_UTILITY +#define BUILD2_INSTALL_UTILITY + +#include +#include + +#include +#include + +namespace build2 +{ + namespace install + { + // Set install path, mode for a target type. + // + template + inline void + path (scope& s, dir_path d) + { + auto r (s.target_vars[T::static_type]["*"].assign ("install")); + if (r.second) // Already set by the user? + r.first.get () = std::move (d); + } + + template + inline void + mode (scope& s, std::string m) + { + auto r (s.target_vars[T::static_type]["*"].assign ("install.mode")); + if (r.second) // Already set by the user? + r.first.get () = std::move (m); + } + } +} + +#endif // BUILD2_INSTALL_UTILITY diff --git a/build2/lexer b/build2/lexer new file mode 100644 index 0000000..51f3e56 --- /dev/null +++ b/build2/lexer @@ -0,0 +1,138 @@ +// file : build2/lexer -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_LEXER +#define BUILD2_LEXER + +#include +#include +#include +#include // size_t +#include // uint64_t +#include +#include + +#include + +#include +#include + +#include +#include + +namespace build2 +{ + // Context-dependent lexing mode. In the value mode we don't treat + // certain characters (e.g., +, =) as special so that we can use + // them in the variable values, e.g., 'foo = g++'. In contrast, in + // the variable mode, we restrict certain character (e.g., /) from + // appearing in the name. The pairs mode is just like value except + // that we split names separated by the pair character. The eval + // mode is used in the evaluation context. + // + // The alternnative modes must be set manually. The value and pairs + // modes are automatically reset after the end of the line. The + // variable mode is reset after the name token. And the eval mode + // is reset after the closing ')'. + // + // Quoted is an internal mode and should not be set explicitly. + // + enum class lexer_mode {normal, variable, value, pairs, eval, quoted}; + + class lexer: protected butl::char_scanner + { + public: + lexer (std::istream& is, + const std::string& name, + void (*processor) (token&, const lexer&) = nullptr) + : char_scanner (is), fail (name), processor_ (processor), sep_ (false) + { + mode_.push (lexer_mode::normal); + } + + const std::string& + name () const {return fail.name_;} + + // Note: sets mode for the next token. If mode is pairs, then + // the second argument specifies the separator character. + // + void + mode (lexer_mode m, char pair_separator = '=') + { + mode_.push (m); + pair_separator_ = pair_separator; + } + + // Expire the current mode early. + // + void + expire_mode () {mode_.pop ();} + + lexer_mode + mode () const {return mode_.top ();} + + char + pair_separator () const {return pair_separator_;} + + // Scanner. + // + token + next (); + + // Peek at the first character of the next token. Return the character + // or 0 if the next token will be eos. Also return an indicator of + // whether the next token will be separated. + // + pair + peek_char (); + + private: + token + next_impl (); + + token + next_eval (); + + token + next_quoted (); + + token + name (bool separated); + + // Return true if we have seen any spaces. Skipped empty lines + // don't count. In other words, we are only interested in spaces + // that are on the same line as the following non-space character. + // + bool + skip_spaces (); + + xchar + escape (); + + // Diagnostics. + // + private: + struct fail_mark_base: build2::fail_mark_base + { + fail_mark_base (const std::string& n): name_ (n) {} + + location_prologue + operator() (const xchar&) const; + + std::string name_; + }; + typedef diag_mark fail_mark; + + private: + fail_mark fail; + + void (*processor_) (token&, const lexer&); + + std::stack mode_; + char pair_separator_; + bool sep_; // True if we skipped spaces in peek(). + }; +} + +#endif // BUILD2_LEXER diff --git a/build2/lexer.cxx b/build2/lexer.cxx new file mode 100644 index 0000000..e4d03c4 --- /dev/null +++ b/build2/lexer.cxx @@ -0,0 +1,431 @@ +// file : build2/lexer.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +using namespace std; + +namespace build2 +{ + typedef token_type type; + + token lexer:: + next () + { + token t (next_impl ()); + if (processor_ != nullptr) + processor_ (t, *this); + return t; + } + + pair lexer:: + peek_char () + { + // In the quoted mode we don't skip spaces. + // + sep_ = mode_.top () != lexer_mode::quoted && skip_spaces (); + xchar c (peek ()); + return make_pair (eos (c) ? '\0' : char (c), sep_); + } + + token lexer:: + next_impl () + { + lexer_mode m (mode_.top ()); + + // For some modes we have dedicated imlementations of next(). + // + switch (m) + { + case lexer_mode::eval: return next_eval (); + case lexer_mode::quoted: return next_quoted (); + default: break; + } + + bool sep (skip_spaces ()); + + xchar c (get ()); + uint64_t ln (c.line), cn (c.column); + + if (eos (c)) + return token (type::eos, sep, ln, cn); + + switch (c) + { + // NOTE: remember to update name(), next_eval() if adding new + // special characters. + // + case '\n': + { + // Expire value/pairs mode at the end of the line. + // + if (m == lexer_mode::value || m == lexer_mode::pairs) + mode_.pop (); + + return token (type::newline, sep, ln, cn); + } + case '{': return token (type::lcbrace, sep, ln, cn); + case '}': return token (type::rcbrace, sep, ln, cn); + case '$': return token (type::dollar, sep, ln, cn); + case '(': return token (type::lparen, sep, ln, cn); + case ')': return token (type::rparen, sep, ln, cn); + } + + // Handle pair separator. + // + if (m == lexer_mode::pairs && c == pair_separator_) + return token (c, sep, ln, cn); + + // The following characters are not treated as special in the + // value or pairs mode. + // + if (m != lexer_mode::value && m != lexer_mode::pairs) + { + switch (c) + { + // NOTE: remember to update name(), next_eval() if adding new + // special characters. + // + case ':': return token (type::colon, sep, ln, cn); + case '=': + { + if (peek () == '+') + { + get (); + return token (type::equal_plus, sep, ln, cn); + } + else + return token (type::equal, sep, ln, cn); + } + case '+': + { + if (peek () == '=') + { + get (); + return token (type::plus_equal, sep, ln, cn); + } + } + } + } + + // Otherwise it is a name. + // + unget (c); + return name (sep); + } + + token lexer:: + next_eval () + { + bool sep (skip_spaces ()); + xchar c (get ()); + + if (eos (c)) + fail (c) << "unterminated evaluation context"; + + uint64_t ln (c.line), cn (c.column); + + // This mode is quite a bit like the value mode when it comes + // to special characters. + // + switch (c) + { + // NOTE: remember to update name() if adding new special characters. + // + case '\n': fail (c) << "newline in evaluation context"; + case '{': return token (type::lcbrace, sep, ln, cn); + case '}': return token (type::rcbrace, sep, ln, cn); + case '$': return token (type::dollar, sep, ln, cn); + case '(': return token (type::lparen, sep, ln, cn); + case ')': + { + mode_.pop (); // Expire eval mode. + return token (type::rparen, sep, ln, cn); + } + } + + // Otherwise it is a name. + // + unget (c); + return name (sep); + } + + token lexer:: + next_quoted () + { + xchar c (get ()); + + if (eos (c)) + fail (c) << "unterminated double-quoted sequence"; + + uint64_t ln (c.line), cn (c.column); + + switch (c) + { + case '$': return token (type::dollar, false, ln, cn); + case '(': return token (type::lparen, false, ln, cn); + } + + // Otherwise it is a name. + // + unget (c); + return name (false); + } + + token lexer:: + name (bool sep) + { + xchar c (peek ()); + assert (!eos (c)); + + uint64_t ln (c.line), cn (c.column); + string lexeme; + + lexer_mode m (mode_.top ()); + bool quoted (m == lexer_mode::quoted); + + for (; !eos (c); c = peek ()) + { + bool done (false); + + // Handle pair separator. + // + if (m == lexer_mode::pairs && c == pair_separator_) + break; + + // The following characters are not treated as special in the + // value/pairs, eval, and quoted modes. + // + if (m != lexer_mode::value && + m != lexer_mode::pairs && + m != lexer_mode::eval && + m != lexer_mode::quoted) + { + switch (c) + { + case ':': + case '+': + case '=': + { + done = true; + break; + } + } + + if (done) + break; + } + + // While these extra characters are treated as the name end in + // the variable mode. + // + if (m == lexer_mode::variable) + { + switch (c) + { + case '/': + case '-': + { + done = true; + break; + } + } + + if (done) + break; + } + + // If we are quoted, these are ordinary characters. + // + if (m != lexer_mode::quoted) + { + switch (c) + { + case ' ': + case '\t': + case '\n': + case '#': + case '{': + case '}': + case ')': + { + done = true; + break; + } + case '\\': + { + get (); + c = escape (); + if (c != '\n') // Ignore. + lexeme += c; + continue; + } + case '\'': + { + // If we are in the variable mode, then treat quote as just + // another separator. + // + if (m == lexer_mode::variable) + { + done = true; + break; + } + else + { + get (); + + for (c = get (); !eos (c) && c != '\''; c = get ()) + lexeme += c; + + if (eos (c)) + fail (c) << "unterminated single-quoted sequence"; + + quoted = true; + continue; + } + } + } + + if (done) + break; + } + + switch (c) + { + case '$': + case '(': + { + done = true; + break; + } + case '\"': + { + // If we are in the variable mode, then treat quote as just + // another separator. + // + if (m == lexer_mode::variable) + { + done = true; + break; + } + else + { + get (); + + if (m == lexer_mode::quoted) + mode_.pop (); + else + { + mode_.push (lexer_mode::quoted); + quoted = true; + } + + m = mode_.top (); + continue; + } + } + default: + { + get (); + lexeme += c; + continue; + } + } + + assert (done); + break; + } + + if (m == lexer_mode::quoted && eos (c)) + fail (c) << "unterminated double-quoted sequence"; + + // Expire variable mode at the end of the name. + // + if (m == lexer_mode::variable) + mode_.pop (); + + return token (lexeme, sep, quoted, ln, cn); + } + + bool lexer:: + skip_spaces () + { + bool r (sep_); + sep_ = false; + + xchar c (peek ()); + bool start (c.column == 1); + + for (; !eos (c); c = peek ()) + { + switch (c) + { + case ' ': + case '\t': + { + r = true; + break; + } + case '\n': + { + // Skip empty lines. + // + if (start) + { + r = false; + break; + } + + return r; + } + case '#': + { + get (); + + // Read until newline or eos. + // + for (c = peek (); !eos (c) && c != '\n'; c = peek ()) + get (); + + r = true; + continue; + } + case '\\': + { + get (); + + if (peek () == '\n') + break; // Ignore. + + unget (c); + // Fall through. + } + default: + return r; // Not a space. + } + + get (); + } + + return r; + } + + lexer::xchar lexer:: + escape () + { + xchar c (get ()); + + if (eos (c)) + fail (c) << "unterminated escape sequence"; + + return c; + } + + location_prologue lexer::fail_mark_base:: + operator() (const xchar& c) const + { + return build2::fail_mark_base::operator() ( + location (name_.c_str (), c.line, c.column)); + } +} diff --git a/build2/module b/build2/module new file mode 100644 index 0000000..4fac698 --- /dev/null +++ b/build2/module @@ -0,0 +1,86 @@ +// file : build2/module -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_MODULE +#define BUILD2_MODULE + +#include + +#include +#include + +#include + +namespace build2 +{ + class scope; + class location; + + class module + { + public: + virtual + ~module () = default; + }; + + extern "C" + using module_boot_function = + void (scope& root, const location&, unique_ptr&); + + // Return false if the module configuration (normally based on the default + // values) was unsuccessful but this is not (yet) an error. One example + // would be the optional use of a module. Or a module might remain + // unconfigured for as long as it is actually not used (e.g., install, + // dist). The return value is used to set the .configured variable. + // + extern "C" + using module_init_function = + bool (scope& root, + scope& base, + const location&, + unique_ptr&, + bool first, // First time for this project. + bool optional); // Loaded with 'using?' (optional module). + + + struct module_state + { + bool boot; // True if the module boot'ed but not yet init'ed. + module_init_function* init; + unique_ptr module; + const location loc; // Boot location. + }; + + using loaded_module_map = std::map; + + // Load and boot the specified module. + // + void + boot_module (const string& name, scope& root, const location&); + + // Load (if not already loaded) and initialize the specified module. Used + // by the parser but also by some modules to load prerequisite modules. + // Return true if the module was both successfully loaded and configured + // (false can only be returned if optional). + // + bool + load_module (bool optional, + const std::string& name, + scope& root, + scope& base, + const location&); + + // Builtin modules. + // + struct module_functions + { + module_boot_function* boot; + module_init_function* init; + }; + + using available_module_map = std::map; + extern available_module_map builtin_modules; +} + +#endif // BUILD2_MODULE diff --git a/build2/module.cxx b/build2/module.cxx new file mode 100644 index 0000000..288569f --- /dev/null +++ b/build2/module.cxx @@ -0,0 +1,114 @@ +// file : build2/module.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include // make_pair() + +#include +#include +#include + +using namespace std; + +namespace build2 +{ + available_module_map builtin_modules; + + void + boot_module (const string& name, scope& rs, const location& loc) + { + // First see if this modules has already been loaded for this project. + // + loaded_module_map& lm (rs.modules); + auto i (lm.find (name)); + + if (i != lm.end ()) + { + module_state& s (i->second); + + // The only valid situation here is if the module has already been + // bootstrapped. + // + assert (s.boot); + return; + } + + // Otherwise search for this module. + // + auto j (builtin_modules.find (name)); + + if (j == builtin_modules.end ()) + fail (loc) << "unknown module " << name; + + const module_functions& mf (j->second); + + if (mf.boot == nullptr) + fail (loc) << "module " << name << " shouldn't be loaded in bootstrap"; + + i = lm.emplace (name, module_state {true, mf.init, nullptr, loc}).first; + mf.boot (rs, loc, i->second.module); + } + + bool + load_module (bool opt, + const string& name, + scope& rs, + scope& bs, + const location& loc) + { + // First see if this modules has already been loaded for this project. + // + loaded_module_map& lm (rs.modules); + auto i (lm.find (name)); + bool f (i == lm.end ()); + + if (f) + { + // Otherwise search for this module. + // + auto j (builtin_modules.find (name)); + + if (j == builtin_modules.end ()) + { + if (!opt) + fail (loc) << "unknown module " << name; + } + else + { + const module_functions& mf (j->second); + + if (mf.boot != nullptr) + fail (loc) << "module " << name << " should be loaded in bootstrap"; + + i = lm.emplace ( + name, module_state {false, mf.init, nullptr, loc}).first; + } + } + else + { + module_state& s (i->second); + + if (s.boot) + { + s.boot = false; + f = true; // This is a first call to init. + } + } + + bool l (i != lm.end ()); + bool c (l && i->second.init (rs, bs, loc, i->second.module, f, opt)); + + const variable& lv (var_pool.find (name + ".loaded", + variable_visibility::project, + bool_type)); + const variable& cv (var_pool.find (name + ".configured", + variable_visibility::project, + bool_type)); + bs.assign (lv) = l; + bs.assign (cv) = c; + + return l && c; + } +} diff --git a/build2/name b/build2/name new file mode 100644 index 0000000..a0672c7 --- /dev/null +++ b/build2/name @@ -0,0 +1,113 @@ +// file : build2/name -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_NAME +#define BUILD2_NAME + +#include +#include +#include +#include // move() + +#include + +// Note: include instead of this file directly. +// +namespace build2 +{ + using butl::dir_path; + + // A name is what we operate on by default. Depending on the context, + // it can be interpreted as a target or prerequisite name. A name + // without a type and directory can be used to represent any text. + // A name with directory and empty value represents a directory. + // A name may also be project-qualified. If the project name is + // empty, then it means the name is in a project other than our + // own (e.g., it is installed). + // + // If pair is not '\0', then this name and the next in the list + // form a pair. + // + struct name + { + name () = default; + + explicit name (std::string v): value (std::move (v)) {} + name& operator= (std::string v) {return *this = name (std::move (v));} + + explicit name (dir_path d): dir (std::move (d)) {} + name& operator= (dir_path d) {return *this = name (std::move (d));} + + name (std::string t, std::string v) + : type (std::move (t)), value (std::move (v)) {} + + name (dir_path d, std::string t, std::string v) + : dir (std::move (d)), type (std::move (t)), value (std::move (v)) {} + + // The first argument should be from project_name_pool. + // + name (const std::string* p, dir_path d, std::string t, std::string v) + : proj (p), + dir (std::move (d)), + type (std::move (t)), + value (std::move (v)) {} + + bool + qualified () const {return proj != nullptr;} + + bool + unqualified () const {return proj == nullptr;} + + bool + typed () const {return !type.empty ();} + + bool + untyped () const {return type.empty ();} + + bool + empty () const {return dir.empty () && value.empty ();} + + // Note that strictly speaking the following tests should be + // orthogonal to qualification. However, the vast majority of + // cases where we expect a simple or directory name, we also + // expect it to be unqualified. + // + // Note also that empty name is simple but not a directory. + // + bool + simple () const {return unqualified () && untyped () && dir.empty ();} + + bool + directory () const + {return unqualified () && untyped () && !dir.empty () && value.empty ();} + + const std::string* proj = nullptr; // Points to project_name_pool. + dir_path dir; + std::string type; + std::string value; + char pair = '\0'; // Pair symbol, if any. + }; + + inline bool + operator== (const name& x, const name& y) + { + return x.proj == y.proj && // Pooled, so can just compare pointers. + x.type == y.type && + x.dir == y.dir && + x.value == y.value; + } + + inline bool + operator!= (const name& x, const name& y) {return !(x == y);} + + typedef std::vector names; + + std::ostream& + operator<< (std::ostream&, const name&); + + std::ostream& + operator<< (std::ostream&, const names&); +} + +#endif // BUILD2_NAME diff --git a/build2/name.cxx b/build2/name.cxx new file mode 100644 index 0000000..9ebfa1f --- /dev/null +++ b/build2/name.cxx @@ -0,0 +1,63 @@ +// file : build2/name.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include + +#include + +using namespace std; + +namespace build2 +{ + ostream& + operator<< (ostream& os, const name& n) + { + if (n.proj != nullptr) + os << *n.proj << '%'; + + // If the value is empty, then we want to print the directory + // inside {}, e.g., dir{bar/}, not bar/dir{}. We also want to + // print {} for an empty name. + // + bool d (!n.dir.empty ()); + bool v (!n.value.empty ()); + bool t (!n.type.empty () || (!d && !v)); + + if (v) + os << n.dir; + + if (t) + os << n.type << '{'; + + if (v) + os << n.value; + else + os << n.dir; + + if (t) + os << '}'; + + return os; + } + + ostream& + operator<< (ostream& os, const names& ns) + { + for (auto i (ns.begin ()), e (ns.end ()); i != e; ) + { + const name& n (*i); + ++i; + os << n; + + if (n.pair != '\0') + os << n.pair; + else if (i != e) + os << ' '; + } + + return os; + } +} diff --git a/build2/operation b/build2/operation new file mode 100644 index 0000000..a843c9d --- /dev/null +++ b/build2/operation @@ -0,0 +1,359 @@ +// file : build2/operation -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_OPERATION +#define BUILD2_OPERATION + +#include +#include +#include +#include +#include // reference_wrapper + +#include + +#include + +namespace build2 +{ + class location; + class scope; + class target_key; + + // While we are using uint8_t for the meta/operation ids, we assume + // that each is limited to 4 bits (max 128 entries) so that we can + // store the combined action id in uint8_t as well. This makes our + // life easier when it comes to defining switch labels for action + // ids (no need to mess with endian-ness). + // + // Note that 0 is not a valid meta/operation/action id. + // + using meta_operation_id = std::uint8_t; + using operation_id = std::uint8_t; + using action_id = std::uint8_t; + + // Meta-operations and operations are not the end of the story. We + // also have operation nesting (currently only one level deep) which + // is used to implement pre/post operations (currently, but may be + // useful for other things). Here is the idea: the test operation + // needs to make sure that the targets that it needs to test are + // up-to-date. So it runs update as its pre-operation. It is almost + // like an ordinary update except that it has test as its outer + // operation (the meta-operations are always the same). This way a + // rule can recognize that this is "update for test" and do something + // differently. For example, if an executable is not a test, then + // there is no use updating it. At the same time, most rules will + // ignore the fact that this is a nested update and for them it is + // "update as usual". + // + struct action + { + action (): inner_id (0), outer_id (0) {} // Invalid action. + + bool + valid () const {return inner_id != 0;} + + // If this is not a nested operation, then outer should be 0. + // + action (meta_operation_id m, operation_id inner, operation_id outer = 0) + : inner_id ((m << 4) | inner), + outer_id (outer == 0 ? 0 : (m << 4) | outer) {} + + meta_operation_id + meta_operation () const {return inner_id >> 4;} + + operation_id + operation () const {return inner_id & 0xF;} + + operation_id + outer_operation () const {return outer_id & 0xF;} + + // Implicit conversion operator to action_id for the switch() + // statement, etc. Most places will only care about the inner + // operation. + // + operator action_id () const {return inner_id;} + + action_id inner_id; + action_id outer_id; + }; + + // This is an "overrides" comparison, i.e., it returns true + // if the recipe for x overrides recipe for y. The idea is + // that for the same inner operation, action with an outer + // operation is "weaker" than the one without. + // + inline bool + operator> (action x, action y) + { + return x.inner_id != y.inner_id || + (x.outer_id != y.outer_id && y.outer_id != 0); + } + + // Note that these ignore the outer operation. + // + inline bool + operator== (action x, action y) {return x.inner_id == y.inner_id;} + + inline bool + operator!= (action x, action y) {return !(x == y);} + + std::ostream& + operator<< (std::ostream&, action); + + // Id constants for build-in and pre-defined meta/operations. + // + const meta_operation_id perform_id = 1; + const meta_operation_id configure_id = 2; + const meta_operation_id disfigure_id = 3; + const meta_operation_id dist_id = 4; + + // The default operation is a special marker that can be used to + // indicate that no operation was explicitly specified by the user. + // + const operation_id default_id = 1; // Shall be first. + const operation_id update_id = 2; + const operation_id clean_id = 3; + const operation_id test_id = 4; + const operation_id install_id = 5; + + const action_id perform_update_id = (perform_id << 4) | update_id; + const action_id perform_clean_id = (perform_id << 4) | clean_id; + const action_id perform_test_id = (perform_id << 4) | test_id; + const action_id perform_install_id = (perform_id << 4) | install_id; + + const action_id configure_update_id = (configure_id << 4) | update_id; + + // Recipe execution mode. + // + // When a target is a prerequisite of another target, its recipe can be + // executed before the dependent's recipe (the normal case) or after. + // We will call these "front" and "back" execution modes, respectively + // (think "the prerequisite is 'front-running' the dependent"). + // + // There could also be several dependent targets and the prerequisite's + // recipe can be execute as part of the first dependent (the normal + // case) or last (or for all/some of them; see the recipe execution + // protocol in ). We will call these "first" and "last" + // execution modes, respectively. + // + // Now you may be having a hard time imagining where a mode other than + // the normal one (first/front) could be useful. An the answer is, + // compensating or inverse operations such as clean, uninstall, etc. + // If we use the last/back mode for, say, clean, then we will remove + // targets in the order inverse to the way they were updated. While + // this sounds like an elegant idea, are there any practical benefits + // of doing it this way. As it turns out there is (at least) one: when + // we are removing a directory (see fsdir{}), we want to do it after + // all the targets that depend on it (such as files, sub-directories) + // were removed. If we do it before, then the directory won't be empty + // yet. + // + // It appears that this execution mode is dictated by the essence of + // the operation. Constructive operations (those that "do") seem to + // naturally use the first/front mode. That is, we need to "do" the + // prerequisite first before we can "do" the dependent. While the + // destructive ones (those that "undo") seem to need last/back. That + // is, we need to "undo" all the dependents before we can "undo" the + // prerequisite (say, we need to remove all the files before we can + // remove their directory). + // + // If you noticed the parallel with the way C++ construction and + // destruction works for base/derived object then you earned a gold + // star! + // + // Note that the front/back mode is realized in the dependen's recipe + // (which is another indication that it is a property of the operation). + // + enum class execution_mode {first, last}; + + // Meta-operation info. + // + + // Normally a list of resolved and matched targets to execute. But + // can be something else, depending on the meta-operation. + // + typedef std::vector action_targets; + + struct meta_operation_info + { + const std::string name; + + // Name derivatives for diagnostics. If empty, then the meta- + // operation need not be mentioned. + // + const std::string name_do; // E.g., [to] 'configure'. + const std::string name_doing; // E.g., [while] 'configuring'. + const std::string name_done; // E.g., 'is configured'. + + // If operation_pre() is not NULL, then it may translate default_id + // (and only default_id) to some other operation. If not translated, + // then default_id is used. If, however, operation_pre() is NULL, + // then default_id is translated to update_id. + // + void (*meta_operation_pre) (); // Start of meta-operation batch. + operation_id (*operation_pre) (operation_id); // Start of operation batch. + + // Meta-operation-specific logic to load the buildfile, search and match + // the targets, and execute the action on the targets. + // + void (*load) (const path& buildfile, + scope& root, + const dir_path& out_base, + const dir_path& src_base, + const location&); + + void (*search) (scope& root, + const target_key&, + const location&, + action_targets&); + + void (*match) (action, action_targets&); + + void (*execute) (action, const action_targets&, bool quiet); + + void (*operation_post) (operation_id); // End of operation batch. + void (*meta_operation_post) (); // End of meta-operation batch. + }; + + // Built-in meta-operations. + // + + // perform + // + + // Load the buildfile. This is the default implementation that first + // calls root_pre(), then creates the scope for out_base, and, finally, + // loads the buildfile unless it has already been loaded for the root + // scope. + // + void + load (const path& buildfile, + scope& root, + const dir_path& out_base, + const dir_path& src_base, + const location&); + + // Search and match the target. This is the default implementation + // that does just that and adds a pointer to the target to the list. + // + void + search (scope&, const target_key&, const location&, action_targets&); + + void + match (action, action_targets&); + + // Execute the action on the list of targets. This is the default + // implementation that does just that while issuing appropriate + // diagnostics (unless quiet). + // + void + execute (action, const action_targets&, bool quiet); + + extern meta_operation_info perform; + + // Operation info. + // + struct operation_info + { + const std::string name; + + // Name derivatives for diagnostics. Note that unlike meta-operations, + // these can only be empty for the default operation (id 1), And + // meta-operations that make use of the default operation shall not + // have empty derivatives (failed which only target name will be + // printed). + // + const std::string name_do; // E.g., [to] 'update'. + const std::string name_doing; // E.g., [while] 'updating'. + const std::string name_done; // E.g., 'is up to date'. + + const execution_mode mode; + + // If the returned operation_id's are not 0, then they are injected + // as pre/post operations for this operation. Can be NULL if unused. + // The returned operation_id shall not be default_id. + // + operation_id (*pre) (meta_operation_id); + operation_id (*post) (meta_operation_id); + }; + + // Built-in operations. + // + extern operation_info default_; + extern operation_info update; + extern operation_info clean; + + // Global meta/operation tables. Each registered meta/operation + // is assigned an id which is used as an index in the per-project + // registered meta/operation lists. + // + // We have three types of meta/operations: built-in (e.g., perform, + // update), pre-defined (e.g., configure, test), and dynamically- + // defined. For built-in ones, both the id and implementation are + // part of the build2 core. For pre-defined, the id is registered + // as part of the core but the implementation is loaded as part of + // a module. The idea with pre-defined operations is that they have + // common, well-established semantics but could still be optional. + // Another aspect of pre-defined operations is that often rules + // across multiple modules need to know their ids. Finally, + // dynamically-defined meta/operations have their ids registered + // as part of a module load. In this case, the meta/operation is + // normally (but not necessarily) fully implemented by this module. + // + // Note also that the name of a meta/operation in a sense defines + // its semantics. It would be strange to have an operation called + // test that does two very different things in different projects. + // + extern butl::string_table meta_operation_table; + extern butl::string_table operation_table; + + // These are "sparse" in the sense that we may have "holes" that + // are represented as NULL pointers. Also, lookup out of bounds + // is treated as a hole. + // + template + struct sparse_vector + { + using base_type = std::vector; + using size_type = typename base_type::size_type; + + void + insert (size_type i, T& x) + { + size_type n (v_.size ()); + + if (i < n) + v_[i] = &x; + else + { + if (n != i) + v_.resize (i, nullptr); // Add holes. + v_.push_back (&x); + } + } + + T* + operator[] (size_type i) const + { + return i < v_.size () ? v_[i] : nullptr; + } + + bool + empty () const {return v_.empty ();} + + // Note that this is more of a "max index" rather than size. + // + size_type + size () const {return v_.size ();} + + private: + base_type v_; + }; + + using meta_operations = sparse_vector; + using operations = sparse_vector; +} + +#endif // BUILD2_OPERATION diff --git a/build2/operation.cxx b/build2/operation.cxx new file mode 100644 index 0000000..f4eae64 --- /dev/null +++ b/build2/operation.cxx @@ -0,0 +1,232 @@ +// file : build2/operation.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include +#include +#include +#include // reference_wrapper + +#include // reverse_iterate + +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace butl; + +namespace build2 +{ + // action + // + ostream& + operator<< (ostream& os, action a) + { + uint16_t + m (a.meta_operation ()), + i (a.operation ()), + o (a.outer_operation ()); + + os << '(' << m << ','; + + if (o != 0) + os << o << '('; + + os << i; + + if (o != 0) + os << ')'; + + os << ')'; + + return os; + } + + // perform + // + void + load (const path& bf, + scope& root, + const dir_path& out_base, + const dir_path& src_base, + const location&) + { + // Load project's root[-pre].build. + // + load_root_pre (root); + + // Create the base scope. Note that its existence doesn't + // mean it was already setup as a base scope; it can be the + // same as root. + // + auto i (scopes.insert (out_base, nullptr, true, false)); + scope& base (setup_base (i, out_base, src_base)); + + // Load the buildfile unless it has already been loaded. + // + source_once (bf, root, base, root); + } + + void + search (scope&, + const target_key& tk, + const location& l, + action_targets& ts) + { + tracer trace ("search"); + + auto i (targets.find (tk, trace)); + if (i == targets.end ()) + fail (l) << "unknown target " << tk; + + ts.push_back (i->get ()); + } + + void + match (action a, action_targets& ts) + { + tracer trace ("match"); + + if (verb >= 6) + dump (a); + + for (void* vt: ts) + { + target& t (*static_cast (vt)); + level5 ([&]{trace << "matching " << t;}); + match (a, t); + } + + if (verb >= 6) + dump (a); + } + + void + execute (action a, const action_targets& ts, bool quiet) + { + tracer trace ("execute"); + + // Execute collecting postponed targets (to be re-examined later). + // Do it in reverse order if the execution mode is 'last'. + // + vector> psp; + + auto body ( + [a, quiet, &psp, &trace] (void* v) + { + target& t (*static_cast (v)); + + level5 ([&]{trace << diag_doing (a, t);}); + + switch (execute (a, t)) + { + case target_state::unchanged: + { + if (!quiet) + info << diag_done (a, t); + break; + } + case target_state::postponed: + psp.push_back (t); + break; + case target_state::changed: + break; + case target_state::failed: + //@@ This could probably happen in a parallel build. + default: + assert (false); + } + }); + + if (current_mode == execution_mode::first) + for (void* v: ts) body (v); + else + for (void* v: reverse_iterate (ts)) body (v); + + // We should have executed every target that we matched. + // + assert (dependency_count == 0); + + // Re-examine postponed targets. This is the only reliable way to + // find out whether the target has changed. + // + for (target& t: psp) + { + switch (execute (a, t)) + { + case target_state::unchanged: + { + if (!quiet) + info << diag_done (a, t); + break; + } + case target_state::changed: + break; + case target_state::postponed: + assert (false); + case target_state::failed: + //@@ This could probably happen in a parallel build. + default: + assert (false); + } + } + } + + meta_operation_info perform { + "perform", + "", + "", + "", + nullptr, // meta-operation pre + nullptr, // operation pre + &load, + &search, + &match, + &execute, + nullptr, // operation post + nullptr // meta-operation post + }; + + // operations + // + operation_info default_ { + "", + "", + "", + "", + execution_mode::first, + nullptr, + nullptr + }; + + operation_info update { + "update", + "update", + "updating", + "is up to date", + execution_mode::first, + nullptr, + nullptr + }; + + operation_info clean { + "clean", + "clean", + "cleaning", + "is clean", + execution_mode::last, + nullptr, + nullptr + }; + + // Tables. + // + string_table meta_operation_table; + string_table operation_table; +} diff --git a/build2/options b/build2/options new file mode 100644 index 0000000..596980e --- /dev/null +++ b/build2/options @@ -0,0 +1,328 @@ +// -*- C++ -*- +// +// This file was generated by CLI, a command line interface +// compiler for C++. +// + +#ifndef BUILD2_OPTIONS +#define BUILD2_OPTIONS + +// Begin prologue. +// +// +// End prologue. + +#include +#include +#include +#include + +#ifndef CLI_POTENTIALLY_UNUSED +# if defined(_MSC_VER) || defined(__xlC__) +# define CLI_POTENTIALLY_UNUSED(x) (void*)&x +# else +# define CLI_POTENTIALLY_UNUSED(x) (void)x +# endif +#endif + +namespace cl +{ + class usage_para + { + public: + enum value + { + none, + text, + option + }; + + usage_para (value); + + operator value () const + { + return v_; + } + + private: + value v_; + }; + + class unknown_mode + { + public: + enum value + { + skip, + stop, + fail + }; + + unknown_mode (value); + + operator value () const + { + return v_; + } + + private: + value v_; + }; + + // Exceptions. + // + + class exception: public std::exception + { + public: + virtual void + print (::std::ostream&) const = 0; + }; + + ::std::ostream& + operator<< (::std::ostream&, const exception&); + + class unknown_option: public exception + { + public: + virtual + ~unknown_option () throw (); + + unknown_option (const std::string& option); + + const std::string& + option () const; + + virtual void + print (::std::ostream&) const; + + virtual const char* + what () const throw (); + + private: + std::string option_; + }; + + class unknown_argument: public exception + { + public: + virtual + ~unknown_argument () throw (); + + unknown_argument (const std::string& argument); + + const std::string& + argument () const; + + virtual void + print (::std::ostream&) const; + + virtual const char* + what () const throw (); + + private: + std::string argument_; + }; + + class missing_value: public exception + { + public: + virtual + ~missing_value () throw (); + + missing_value (const std::string& option); + + const std::string& + option () const; + + virtual void + print (::std::ostream&) const; + + virtual const char* + what () const throw (); + + private: + std::string option_; + }; + + class invalid_value: public exception + { + public: + virtual + ~invalid_value () throw (); + + invalid_value (const std::string& option, + const std::string& value); + + const std::string& + option () const; + + const std::string& + value () const; + + virtual void + print (::std::ostream&) const; + + virtual const char* + what () const throw (); + + private: + std::string option_; + std::string value_; + }; + + class eos_reached: public exception + { + public: + virtual void + print (::std::ostream&) const; + + virtual const char* + what () const throw (); + }; + + class scanner + { + public: + virtual + ~scanner (); + + virtual bool + more () = 0; + + virtual const char* + peek () = 0; + + virtual const char* + next () = 0; + + virtual void + skip () = 0; + }; + + class argv_scanner: public scanner + { + public: + argv_scanner (int& argc, char** argv, bool erase = false); + argv_scanner (int start, int& argc, char** argv, bool erase = false); + + int + end () const; + + virtual bool + more (); + + virtual const char* + peek (); + + virtual const char* + next (); + + virtual void + skip (); + + private: + int i_; + int& argc_; + char** argv_; + bool erase_; + }; + + template + struct parser; +} + +#include + +class options +{ + public: + options (); + + options (int& argc, + char** argv, + bool erase = false, + ::cl::unknown_mode option = ::cl::unknown_mode::fail, + ::cl::unknown_mode argument = ::cl::unknown_mode::stop); + + options (int start, + int& argc, + char** argv, + bool erase = false, + ::cl::unknown_mode option = ::cl::unknown_mode::fail, + ::cl::unknown_mode argument = ::cl::unknown_mode::stop); + + options (int& argc, + char** argv, + int& end, + bool erase = false, + ::cl::unknown_mode option = ::cl::unknown_mode::fail, + ::cl::unknown_mode argument = ::cl::unknown_mode::stop); + + options (int start, + int& argc, + char** argv, + int& end, + bool erase = false, + ::cl::unknown_mode option = ::cl::unknown_mode::fail, + ::cl::unknown_mode argument = ::cl::unknown_mode::stop); + + options (::cl::scanner&, + ::cl::unknown_mode option = ::cl::unknown_mode::fail, + ::cl::unknown_mode argument = ::cl::unknown_mode::stop); + + // Option accessors. + // + const bool& + help () const; + + const bool& + version () const; + + const bool& + v () const; + + const bool& + q () const; + + const std::uint16_t& + verbose () const; + + bool + verbose_specified () const; + + // Print usage information. + // + static ::cl::usage_para + print_usage (::std::ostream&, + ::cl::usage_para = ::cl::usage_para::none); + + // Implementation details. + // + protected: + bool + _parse (const char*, ::cl::scanner&); + + private: + void + _parse (::cl::scanner&, + ::cl::unknown_mode option, + ::cl::unknown_mode argument); + + public: + bool help_; + bool version_; + bool v_; + bool q_; + std::uint16_t verbose_; + bool verbose_specified_; +}; + +#include + +// Begin epilogue. +// +// +// End epilogue. + +#endif // BUILD2_OPTIONS diff --git a/build2/options.cli b/build2/options.cli new file mode 100644 index 0000000..8e30985 --- /dev/null +++ b/build2/options.cli @@ -0,0 +1,31 @@ +// file : build2/options.cli +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +include ; + +class options +{ + bool --help {"Print usage information and exit."}; + bool --version {"Print version and exit."}; + + bool -v + { + "Print actual commands being executed. This is equivalent to + \cb{--verbose 2}." + }; + + bool -q + { + "Run quietly, only printing error messages. This is equivalent + to \cb{--verbose 0}." + }; + + std::uint16_t --verbose = 1 + { + "", + "Set the diagnostics verbosity to between 0 (disabled) and + 6 (lots of information). The default is 1. @@ Need to document + further." + }; +}; diff --git a/build2/options.cxx b/build2/options.cxx new file mode 100644 index 0000000..aa72be1 --- /dev/null +++ b/build2/options.cxx @@ -0,0 +1,549 @@ +// -*- C++ -*- +// +// This file was generated by CLI, a command line interface +// compiler for C++. +// + +// Begin prologue. +// +// +// End prologue. + +#include + +#include +#include +#include +#include +#include +#include + +namespace cl +{ + // unknown_option + // + unknown_option:: + ~unknown_option () throw () + { + } + + void unknown_option:: + print (::std::ostream& os) const + { + os << "unknown option '" << option ().c_str () << "'"; + } + + const char* unknown_option:: + what () const throw () + { + return "unknown option"; + } + + // unknown_argument + // + unknown_argument:: + ~unknown_argument () throw () + { + } + + void unknown_argument:: + print (::std::ostream& os) const + { + os << "unknown argument '" << argument ().c_str () << "'"; + } + + const char* unknown_argument:: + what () const throw () + { + return "unknown argument"; + } + + // missing_value + // + missing_value:: + ~missing_value () throw () + { + } + + void missing_value:: + print (::std::ostream& os) const + { + os << "missing value for option '" << option ().c_str () << "'"; + } + + const char* missing_value:: + what () const throw () + { + return "missing option value"; + } + + // invalid_value + // + invalid_value:: + ~invalid_value () throw () + { + } + + void invalid_value:: + print (::std::ostream& os) const + { + os << "invalid value '" << value ().c_str () << "' for option '" + << option ().c_str () << "'"; + } + + const char* invalid_value:: + what () const throw () + { + return "invalid option value"; + } + + // eos_reached + // + void eos_reached:: + print (::std::ostream& os) const + { + os << what (); + } + + const char* eos_reached:: + what () const throw () + { + return "end of argument stream reached"; + } + + // scanner + // + scanner:: + ~scanner () + { + } + + // argv_scanner + // + bool argv_scanner:: + more () + { + return i_ < argc_; + } + + const char* argv_scanner:: + peek () + { + if (i_ < argc_) + return argv_[i_]; + else + throw eos_reached (); + } + + const char* argv_scanner:: + next () + { + if (i_ < argc_) + { + const char* r (argv_[i_]); + + if (erase_) + { + for (int i (i_ + 1); i < argc_; ++i) + argv_[i - 1] = argv_[i]; + + --argc_; + argv_[argc_] = 0; + } + else + ++i_; + + return r; + } + else + throw eos_reached (); + } + + void argv_scanner:: + skip () + { + if (i_ < argc_) + ++i_; + else + throw eos_reached (); + } + + template + struct parser + { + static void + parse (X& x, bool& xs, scanner& s) + { + std::string o (s.next ()); + + if (s.more ()) + { + std::string v (s.next ()); + std::istringstream is (v); + if (!(is >> x && is.eof ())) + throw invalid_value (o, v); + } + else + throw missing_value (o); + + xs = true; + } + }; + + template <> + struct parser + { + static void + parse (bool& x, scanner& s) + { + s.next (); + x = true; + } + }; + + template <> + struct parser + { + static void + parse (std::string& x, bool& xs, scanner& s) + { + const char* o (s.next ()); + + if (s.more ()) + x = s.next (); + else + throw missing_value (o); + + xs = true; + } + }; + + template + struct parser > + { + static void + parse (std::vector& c, bool& xs, scanner& s) + { + X x; + bool dummy; + parser::parse (x, dummy, s); + c.push_back (x); + xs = true; + } + }; + + template + struct parser > + { + static void + parse (std::set& c, bool& xs, scanner& s) + { + X x; + bool dummy; + parser::parse (x, dummy, s); + c.insert (x); + xs = true; + } + }; + + template + struct parser > + { + static void + parse (std::map& m, bool& xs, scanner& s) + { + const char* o (s.next ()); + + if (s.more ()) + { + std::string ov (s.next ()); + std::string::size_type p = ov.find ('='); + + K k = K (); + V v = V (); + std::string kstr (ov, 0, p); + std::string vstr (ov, (p != std::string::npos ? p + 1 : ov.size ())); + + int ac (2); + char* av[] = + { + const_cast (o), 0 + }; + + bool dummy; + if (!kstr.empty ()) + { + av[1] = const_cast (kstr.c_str ()); + argv_scanner s (0, ac, av); + parser::parse (k, dummy, s); + } + + if (!vstr.empty ()) + { + av[1] = const_cast (vstr.c_str ()); + argv_scanner s (0, ac, av); + parser::parse (v, dummy, s); + } + + m[k] = v; + } + else + throw missing_value (o); + + xs = true; + } + }; + + template + void + thunk (X& x, scanner& s) + { + parser::parse (x.*M, s); + } + + template + void + thunk (X& x, scanner& s) + { + parser::parse (x.*M, x.*S, s); + } +} + +#include +#include + +// options +// + +options:: +options () +: help_ (), + version_ (), + v_ (), + q_ (), + verbose_ (1), + verbose_specified_ (false) +{ +} + +options:: +options (int& argc, + char** argv, + bool erase, + ::cl::unknown_mode opt, + ::cl::unknown_mode arg) +: help_ (), + version_ (), + v_ (), + q_ (), + verbose_ (1), + verbose_specified_ (false) +{ + ::cl::argv_scanner s (argc, argv, erase); + _parse (s, opt, arg); +} + +options:: +options (int start, + int& argc, + char** argv, + bool erase, + ::cl::unknown_mode opt, + ::cl::unknown_mode arg) +: help_ (), + version_ (), + v_ (), + q_ (), + verbose_ (1), + verbose_specified_ (false) +{ + ::cl::argv_scanner s (start, argc, argv, erase); + _parse (s, opt, arg); +} + +options:: +options (int& argc, + char** argv, + int& end, + bool erase, + ::cl::unknown_mode opt, + ::cl::unknown_mode arg) +: help_ (), + version_ (), + v_ (), + q_ (), + verbose_ (1), + verbose_specified_ (false) +{ + ::cl::argv_scanner s (argc, argv, erase); + _parse (s, opt, arg); + end = s.end (); +} + +options:: +options (int start, + int& argc, + char** argv, + int& end, + bool erase, + ::cl::unknown_mode opt, + ::cl::unknown_mode arg) +: help_ (), + version_ (), + v_ (), + q_ (), + verbose_ (1), + verbose_specified_ (false) +{ + ::cl::argv_scanner s (start, argc, argv, erase); + _parse (s, opt, arg); + end = s.end (); +} + +options:: +options (::cl::scanner& s, + ::cl::unknown_mode opt, + ::cl::unknown_mode arg) +: help_ (), + version_ (), + v_ (), + q_ (), + verbose_ (1), + verbose_specified_ (false) +{ + _parse (s, opt, arg); +} + +::cl::usage_para options:: +print_usage (::std::ostream& os, ::cl::usage_para p) +{ + CLI_POTENTIALLY_UNUSED (os); + + if (p == ::cl::usage_para::text) + os << ::std::endl; + + os << "--help Print usage information and exit." << ::std::endl; + + os << "--version Print version and exit." << ::std::endl; + + os << "-v Print actual commands being executed." << ::std::endl; + + os << "-q Run quietly, only printing error messages." << ::std::endl; + + os << "--verbose Set the diagnostics verbosity to between 0 (disabled)" << ::std::endl + << " and 6 (lots of information)." << ::std::endl; + + p = ::cl::usage_para::option; + + return p; +} + +typedef +std::map +_cli_options_map; + +static _cli_options_map _cli_options_map_; + +struct _cli_options_map_init +{ + _cli_options_map_init () + { + _cli_options_map_["--help"] = + &::cl::thunk< options, bool, &options::help_ >; + _cli_options_map_["--version"] = + &::cl::thunk< options, bool, &options::version_ >; + _cli_options_map_["-v"] = + &::cl::thunk< options, bool, &options::v_ >; + _cli_options_map_["-q"] = + &::cl::thunk< options, bool, &options::q_ >; + _cli_options_map_["--verbose"] = + &::cl::thunk< options, std::uint16_t, &options::verbose_, + &options::verbose_specified_ >; + } +}; + +static _cli_options_map_init _cli_options_map_init_; + +bool options:: +_parse (const char* o, ::cl::scanner& s) +{ + _cli_options_map::const_iterator i (_cli_options_map_.find (o)); + + if (i != _cli_options_map_.end ()) + { + (*(i->second)) (*this, s); + return true; + } + + return false; +} + +void options:: +_parse (::cl::scanner& s, + ::cl::unknown_mode opt_mode, + ::cl::unknown_mode arg_mode) +{ + bool opt = true; + + while (s.more ()) + { + const char* o = s.peek (); + + if (std::strcmp (o, "--") == 0) + { + s.skip (); + opt = false; + continue; + } + + if (opt && _parse (o, s)); + else if (opt && std::strncmp (o, "-", 1) == 0 && o[1] != '\0') + { + switch (opt_mode) + { + case ::cl::unknown_mode::skip: + { + s.skip (); + continue; + } + case ::cl::unknown_mode::stop: + { + break; + } + case ::cl::unknown_mode::fail: + { + throw ::cl::unknown_option (o); + } + } + + break; + } + else + { + switch (arg_mode) + { + case ::cl::unknown_mode::skip: + { + s.skip (); + continue; + } + case ::cl::unknown_mode::stop: + { + break; + } + case ::cl::unknown_mode::fail: + { + throw ::cl::unknown_argument (o); + } + } + + break; + } + } +} + +// Begin epilogue. +// +// +// End epilogue. + diff --git a/build2/options.ixx b/build2/options.ixx new file mode 100644 index 0000000..245e0c0 --- /dev/null +++ b/build2/options.ixx @@ -0,0 +1,165 @@ +// -*- C++ -*- +// +// This file was generated by CLI, a command line interface +// compiler for C++. +// + +// Begin prologue. +// +// +// End prologue. + +namespace cl +{ + // usage_para + // + inline usage_para:: + usage_para (value v) + : v_ (v) + { + } + + // unknown_mode + // + inline unknown_mode:: + unknown_mode (value v) + : v_ (v) + { + } + + // exception + // + inline ::std::ostream& + operator<< (::std::ostream& os, const exception& e) + { + e.print (os); + return os; + } + + // unknown_option + // + inline unknown_option:: + unknown_option (const std::string& option) + : option_ (option) + { + } + + inline const std::string& unknown_option:: + option () const + { + return option_; + } + + // unknown_argument + // + inline unknown_argument:: + unknown_argument (const std::string& argument) + : argument_ (argument) + { + } + + inline const std::string& unknown_argument:: + argument () const + { + return argument_; + } + + // missing_value + // + inline missing_value:: + missing_value (const std::string& option) + : option_ (option) + { + } + + inline const std::string& missing_value:: + option () const + { + return option_; + } + + // invalid_value + // + inline invalid_value:: + invalid_value (const std::string& option, + const std::string& value) + : option_ (option), value_ (value) + { + } + + inline const std::string& invalid_value:: + option () const + { + return option_; + } + + inline const std::string& invalid_value:: + value () const + { + return value_; + } + + // argv_scanner + // + inline argv_scanner:: + argv_scanner (int& argc, char** argv, bool erase) + : i_ (1), argc_ (argc), argv_ (argv), erase_ (erase) + { + } + + inline argv_scanner:: + argv_scanner (int start, int& argc, char** argv, bool erase) + : i_ (start), argc_ (argc), argv_ (argv), erase_ (erase) + { + } + + inline int argv_scanner:: + end () const + { + return i_; + } +} + +// options +// + +inline const bool& options:: +help () const +{ + return this->help_; +} + +inline const bool& options:: +version () const +{ + return this->version_; +} + +inline const bool& options:: +v () const +{ + return this->v_; +} + +inline const bool& options:: +q () const +{ + return this->q_; +} + +inline const std::uint16_t& options:: +verbose () const +{ + return this->verbose_; +} + +inline bool options:: +verbose_specified () const +{ + return this->verbose_specified_; +} + +// Begin epilogue. +// +// +// End epilogue. diff --git a/build2/parser b/build2/parser new file mode 100644 index 0000000..f8bd97a --- /dev/null +++ b/build2/parser @@ -0,0 +1,296 @@ +// file : build2/parser -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_PARSER +#define BUILD2_PARSER + +#include +#include + +#include +#include + +#include +#include +#include +#include // list_value +#include + +namespace build2 +{ + class scope; + class target; + + class parser + { + public: + typedef build2::names names_type; + typedef build2::variable variable_type; + + // If boot is true, then we are parsing bootstrap.build and modules + // should only be bootstrapped. + // + parser (bool boot = false): fail (&path_), boot_ (boot) {} + + // Issue diagnostics and throw failed in case of an error. + // + void + parse_buildfile (std::istream&, const path&, scope& root, scope& base); + + buildspec + parse_buildspec (std::istream&, const std::string& name); + + token + parse_variable (lexer&, scope&, std::string name, token_type kind); + + names_type + parse_export_stub (std::istream& is, const path& p, scope& r, scope& b) + { + parse_buildfile (is, p, r, b); + return std::move (export_value_); + } + + // Recursive descent parser. + // + protected: + void + clause (token&, token_type&); + + void + print (token&, token_type&); + + void + source (token&, token_type&); + + void + include (token&, token_type&); + + void + import (token&, token_type&); + + void + export_ (token&, token_type&); + + void + using_ (token&, token_type&); + + void + define (token&, token_type&); + + void + if_else (token&, token_type&); + + void + variable (token&, token_type&, std::string name, token_type kind); + + std::string + variable_name (names_type&&, const location&); + + names_type + variable_value (token&, token_type&, const variable_type&); + + names_type + eval (token&, token_type&); + + // If chunk is true, then parse the smallest but complete, name-wise, + // chunk of input. Note that in this case you may still end up with + // multiple names, for example, {foo bar}. + // + names_type + names (token& t, token_type& tt, bool chunk = false) + { + names_type ns; + names (t, tt, ns, chunk, 0, nullptr, nullptr, nullptr); + return ns; + } + + void + names (token&, token_type&, + names_type&, + bool chunk, + std::size_t pair, + const std::string* prj, + const dir_path* dir, + const std::string* type); + + size_t + names_trailer (token&, token_type&, + names_type&, + size_t pair, + const std::string* prj, + const dir_path* dir, + const std::string* type); + + // Skip until newline or eos. + // + void + skip_line (token&, token_type&); + + // Skip until block-closing } or eos, taking into account nested blocks. + // + void + skip_block (token&, token_type&); + + // Return true if the name token can be considered a directive keyword. + // + bool + keyword (token&); + + // Buildspec. + // + buildspec + buildspec_clause (token&, token_type&, token_type end); + + // Utilities. + // + protected: + + // Switch to a new current scope. Note that this function might + // also have to switch to a new root scope if the new current + // scope is in another project. So both must be saved and + // restored. + // + void + switch_scope (const dir_path&); + + void + process_default_target (token&); + + // Enter buildfile as a target. + // + void + enter_buildfile (const path&); + + // Lexer. + // + protected: + token_type + next (token&, token_type&); + + // Be careful with peeking and switching the lexer mode. See keyword() + // for more information. + // + token_type + peek (); + + const token& + peeked () const + { + assert (peeked_); + return peek_; + } + + void + mode (lexer_mode m, char ps = '=') + { + if (replay_ != replay::play) + lexer_->mode (m, ps); + } + + lexer_mode + mode () const + { + assert (replay_ != replay::play); + return lexer_->mode (); + } + + void + expire_mode () + { + if (replay_ != replay::play) + lexer_->expire_mode (); + } + + // Token saving and replaying. Note that is can only be used in certain + // contexts. Specifically, the lexer mode should be the same and the code + // that parses a replay must not interact with the lexer directly (e.g., + // the keyword() test). For now we don't enforce any of this. + // + // Note also that the peeked token is not part of the replay, until it + // is "got". + // + // + void + replay_save () + { + assert (replay_ == replay::stop); + replay_ = replay::save; + } + + void + replay_play () + { + assert ((replay_ == replay::save && !replay_data_.empty ()) || + (replay_ == replay::play && replay_i_ == replay_data_.size ())); + + replay_i_ = 0; + replay_ = replay::play; + } + + void + replay_stop () + { + replay_data_.clear (); + replay_ = replay::stop; + } + + const token& + replay_next () + { + assert (replay_i_ != replay_data_.size ()); + return replay_data_[replay_i_++]; + } + + struct replay_guard + { + replay_guard (parser& p, bool start = true) + : p_ (start ? &p : nullptr) + { + if (p_ != nullptr) + p_->replay_save (); + } + + void + play () + { + if (p_ != nullptr) + p_->replay_play (); + } + + ~replay_guard () + { + if (p_ != nullptr) + p_->replay_stop (); + } + + private: + parser* p_; + }; + + // Diagnostics. + // + protected: + const fail_mark fail; + + protected: + bool boot_; + + const std::string* path_; // Path processed by diag_relative() and pooled. + lexer* lexer_; + target* target_; // Current target, if any. + scope* scope_; // Current base scope (out_base). + scope* root_; // Current root scope (out_root). + target* default_target_; + names_type export_value_; + + token peek_ = token (token_type::eos, false, 0, 0); + bool peeked_ = false; + + enum class replay {stop, save, play} replay_ = replay::stop; + vector replay_data_; + size_t replay_i_; // Position of the next token during replay. + }; +} + +#endif // BUILD2_PARSER diff --git a/build2/parser.cxx b/build2/parser.cxx new file mode 100644 index 0000000..139d2a2 --- /dev/null +++ b/build2/parser.cxx @@ -0,0 +1,2206 @@ +// file : build2/parser.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include // is{alpha alnum}() + +#include // unique_ptr +#include +#include // move() +#include // make_move_iterator() +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +namespace build2 +{ + static location + get_location (const token&, const void*); + + typedef token_type type; + + void parser:: + parse_buildfile (istream& is, const path& p, scope& root, scope& base) + { + enter_buildfile (p); + + path_ = &path_pool.find (diag_relative (p)); // Relative to work. + + lexer l (is, *path_); + lexer_ = &l; + target_ = nullptr; + scope_ = &base; + root_ = &root; + default_target_ = nullptr; + + token t (type::eos, false, 0, 0); + type tt; + next (t, tt); + + clause (t, tt); + + if (tt != type::eos) + fail (t) << "unexpected " << t; + + process_default_target (t); + } + + token parser:: + parse_variable (lexer& l, scope& s, string name, type kind) + { + path_ = &l.name (); // Note: not pooled. + lexer_ = &l; + target_ = nullptr; + scope_ = &s; + + type tt; + token t (type::eos, false, 0, 0); + variable (t, tt, name, kind); + return t; + } + + void parser:: + clause (token& t, type& tt) + { + tracer trace ("parser::clause", &path_); + + // clause() should always stop at a token that is at the beginning of + // the line (except for eof). That is, if something is called to parse + // a line, it should parse it until newline (or fail). This is important + // for if-else blocks, directory scopes, etc., that assume the } token + // they see is on the new line. + // + while (tt != type::eos) + { + // We always start with one or more names. + // + if (tt != type::name && + tt != type::lcbrace && // Untyped name group: '{foo ...' + tt != type::dollar && // Variable expansion: '$foo ...' + tt != type::lparen && // Eval context: '(foo) ...' + tt != type::colon) // Empty name: ': ...' + break; // Something else. Let our caller handle that. + + // See if this is one of the directives. + // + if (tt == type::name && keyword (t)) + { + const string& n (t.value); + + if (n == "print") + { + // @@ Is this the only place where it is valid? Probably also + // in var namespace. + // + print (t, tt); + continue; + } + else if (n == "source") + { + source (t, tt); + continue; + } + else if (n == "include") + { + include (t, tt); + continue; + } + else if (n == "import") + { + import (t, tt); + continue; + } + else if (n == "export") + { + export_ (t, tt); + continue; + } + else if (n == "using" || + n == "using?") + { + using_ (t, tt); + continue; + } + else if (n == "define") + { + define (t, tt); + continue; + } + else if (n == "if" || + n == "if!") + { + if_else (t, tt); + continue; + } + else if (n == "else" || + n == "elif" || + n == "elif!") + { + // Valid ones are handled in if_else(). + // + fail (t) << n << " without if"; + } + } + + // ': foo' is equvalent to '{}: foo' and to 'dir{}: foo'. + // + const location nloc (get_location (t, &path_)); + names_type ns (tt != type::colon + ? names (t, tt) + : names_type ({name ("dir", string ())})); + + if (tt == type::colon) + { + // While '{}:' means empty name, '{$x}:' where x is empty list + // means empty list. + // + if (ns.empty ()) + fail (t) << "target expected before :"; + + next (t, tt); + + if (tt == type::newline) + { + // See if this is a directory/target scope. + // + if (peek () == type::lcbrace) + { + next (t, tt); + + // Should be on its own line. + // + if (next (t, tt) != type::newline) + fail (t) << "expected newline after {"; + + // See if this is a directory or target scope. Different + // things can appear inside depending on which one it is. + // + bool dir (false); + for (const auto& n: ns) + { + // A name represents directory as an empty value. + // + if (n.directory ()) + { + if (ns.size () != 1) + { + // @@ TODO: point to name (and above). + // + fail (nloc) << "multiple names in directory scope"; + } + + dir = true; + } + } + + next (t, tt); + + if (dir) + { + // Directory scope. + // + dir_path p (move (ns[0].dir)); // Steal. + + // Relative scopes are opened relative to out, not src. + // + if (p.relative ()) + p = scope_->out_path () / p; + + p.normalize (); + + scope* ors (root_); + scope* ocs (scope_); + switch_scope (p); + + // A directory scope can contain anything that a top level can. + // + clause (t, tt); + + scope_ = ocs; + root_ = ors; + } + else + { + // @@ TODO: target scope. + } + + if (tt != type::rcbrace) + fail (t) << "expected } instead of " << t; + + // Should be on its own line. + // + if (next (t, tt) == type::newline) + next (t, tt); + else if (tt != type::eos) + fail (t) << "expected newline after }"; + + continue; + } + + // If this is not a scope, then it is a target without any + // prerequisites. + // + } + + // Dependency declaration or scope/target-specific variable + // assignment. + // + if (tt == type::name || + tt == type::lcbrace || + tt == type::dollar || + tt == type::lparen || + tt == type::newline || + tt == type::eos) + { + const location ploc (get_location (t, &path_)); + names_type pns (tt != type::newline && tt != type::eos + ? names (t, tt) + : names_type ()); + + // Common target entering code used in both cases. + // + auto enter_target = [this, &nloc, &trace] (name&& tn) -> target& + { + const string* e; + const target_type* ti (scope_->find_target_type (tn, e)); + + if (ti == nullptr) + fail (nloc) << "unknown target type " << tn.type; + + path& d (tn.dir); + + if (d.empty ()) + d = scope_->out_path (); // Already normalized. + else + { + if (d.relative ()) + d = scope_->out_path () / d; + + d.normalize (); + } + + // Find or insert. + // + return targets.insert ( + *ti, move (tn.dir), move (tn.value), e, trace).first; + }; + + // Scope/target-specific variable assignment. + // + if (tt == type::equal || + tt == type::equal_plus || + tt == type::plus_equal) + { + token at (t); + type att (tt); + + string v (variable_name (move (pns), ploc)); + + // If we have multiple targets/scopes, then we save the value + // tokens when parsing the first one and then replay them for + // the subsequent. We have to do it this way because the value + // may contain variable expansions that would be sensitive to + // the target/scope context in which they are evaluated. + // + replay_guard rg (*this, ns.size () > 1); + + for (name& n: ns) + { + if (n.qualified ()) + fail (nloc) << "project name in scope/target " << n; + + if (n.directory ()) + { + // The same code as in directory scope handling code above. + // + dir_path p (move (n.dir)); + + if (p.relative ()) + p = scope_->out_path () / p; + + p.normalize (); + + scope* ors (root_); + scope* ocs (scope_); + switch_scope (p); + + variable (t, tt, v, att); + + scope_ = ocs; + root_ = ors; + } + else + { + // Figure out if this is a target or type/pattern specific + // variable. + // + size_t p (n.value.find ('*')); + + if (p == string::npos) + { + target* ot (target_); + target_ = &enter_target (move (n)); + variable (t, tt, v, att); + target_ = ot; + } + else + { + // See tests/variable/type-pattern. + // + if (!n.dir.empty ()) + fail (nloc) << "directory in target type/pattern " << n; + + if (n.value.find ('*', p + 1) != string::npos) + fail (nloc) << "multiple wildcards in target type/pattern " + << n; + + // Resolve target type. If none is specified, use the root + // of the hierarchy. + // + const target_type* ti ( + n.untyped () + ? &target::static_type + : scope_->find_target_type (n.type)); + + if (ti == nullptr) + fail (nloc) << "unknown target type " << n.type; + + if (att == type::equal_plus) + fail (at) << "prepend to target type/pattern-specific " + << "variable " << v; + + if (att == type::plus_equal) + fail (at) << "append to target type/pattern-specific " + << "variable " << v; + + const auto& var (var_pool.find (v)); + + // Note: expand variables in the value in the context of + // the scope. + // + names_type vns (variable_value (t, tt, var)); + value& val (scope_->target_vars[*ti][move (n.value)].assign ( + var).first); + val.assign (move (vns), var); + } + } + + rg.play (); // Replay. + } + } + // Dependency declaration. + // + else + { + // Prepare the prerequisite list. + // + target::prerequisites_type ps; + ps.reserve (pns.size ()); + + for (auto& pn: pns) + { + const string* e; + const target_type* ti (scope_->find_target_type (pn, e)); + + if (ti == nullptr) + fail (ploc) << "unknown target type " << pn.type; + + pn.dir.normalize (); + + // Find or insert. + // + prerequisite& p ( + scope_->prerequisites.insert ( + pn.proj, + *ti, + move (pn.dir), + move (pn.value), + e, + *scope_, + trace).first); + + ps.emplace_back (p); + } + + for (auto& tn: ns) + { + if (tn.qualified ()) + fail (nloc) << "project name in target " << tn; + + target& t (enter_target (move (tn))); + + //@@ OPT: move if last/single target (common cases). + // + t.prerequisites.insert (t.prerequisites.end (), + ps.begin (), + ps.end ()); + + if (default_target_ == nullptr) + default_target_ = &t; + } + } + + if (tt == type::newline) + next (t, tt); + else if (tt != type::eos) + fail (t) << "expected newline instead of " << t; + + continue; + } + + if (tt == type::eos) + continue; + + fail (t) << "expected newline instead of " << t; + } + + // Variable assignment. + // + if (tt == type::equal || + tt == type::equal_plus || + tt == type::plus_equal) + { + variable (t, tt, variable_name (move (ns), nloc), tt); + + if (tt == type::newline) + next (t, tt); + else if (tt != type::eos) + fail (t) << "expected newline instead of " << t; + + continue; + } + + // Allow things like function calls that don't result in anything. + // + if (tt == type::newline && ns.empty ()) + { + next (t, tt); + continue; + } + + fail (t) << "unexpected " << t; + } + } + + void parser:: + source (token& t, type& tt) + { + tracer trace ("parser::source", &path_); + + // The rest should be a list of buildfiles. Parse them as names + // to get variable expansion and directory prefixes. + // + mode (lexer_mode::value); + next (t, tt); + const location l (get_location (t, &path_)); + names_type ns (tt != type::newline && tt != type::eos + ? names (t, tt) + : names_type ()); + + for (name& n: ns) + { + if (n.qualified () || n.empty () || n.value.empty ()) + fail (l) << "expected buildfile instead of " << n; + + // Construct the buildfile path. + // + path p (move (n.dir)); + p /= path (move (n.value)); + + // If the path is relative then use the src directory corresponding + // to the current directory scope. + // + if (root_->src_path_ != nullptr && p.relative ()) + p = src_out (scope_->out_path (), *root_) / p; + + p.normalize (); + + try + { + ifstream ifs (p.string ()); + + if (!ifs.is_open ()) + fail (l) << "unable to open " << p; + + ifs.exceptions (ifstream::failbit | ifstream::badbit); + + level5 ([&]{trace (t) << "entering " << p;}); + + enter_buildfile (p); + + const string* op (path_); + path_ = &path_pool.find (diag_relative (p)); // Relative to work. + + lexer l (ifs, *path_); + lexer* ol (lexer_); + lexer_ = &l; + + token t (type::eos, false, 0, 0); + type tt; + next (t, tt); + clause (t, tt); + + if (tt != type::eos) + fail (t) << "unexpected " << t; + + level5 ([&]{trace (t) << "leaving " << p;}); + + lexer_ = ol; + path_ = op; + } + catch (const ifstream::failure&) + { + fail (l) << "unable to read buildfile " << p; + } + } + + if (tt == type::newline) + next (t, tt); + else if (tt != type::eos) + fail (t) << "expected newline instead of " << t; + } + + void parser:: + include (token& t, type& tt) + { + tracer trace ("parser::include", &path_); + + if (root_->src_path_ == nullptr) + fail (t) << "inclusion during bootstrap"; + + // The rest should be a list of buildfiles. Parse them as names + // to get variable expansion and directory prefixes. + // + mode (lexer_mode::value); + next (t, tt); + const location l (get_location (t, &path_)); + names_type ns (tt != type::newline && tt != type::eos + ? names (t, tt) + : names_type ()); + + for (name& n: ns) + { + if (n.qualified () || n.empty ()) + fail (l) << "expected buildfile instead of " << n; + + // Construct the buildfile path. If it is a directory, then append + // 'buildfile'. + // + path p (move (n.dir)); + if (n.value.empty ()) + p /= path ("buildfile"); + else + { + bool d (path::traits::is_separator (n.value.back ()) + || n.type == "dir"); + + p /= path (move (n.value)); + if (d) + p /= path ("buildfile"); + } + + level6 ([&]{trace (l) << "relative path " << p;}); + + // Determine new out_base. + // + dir_path out_base; + + if (p.relative ()) + { + out_base = scope_->out_path () / p.directory (); + out_base.normalize (); + } + else + { + p.normalize (); + + // Make sure the path is in this project. Include is only meant + // to be used for intra-project inclusion (plus amalgamation). + // + bool in_out (false); + if (!p.sub (root_->src_path ()) && + !(in_out = p.sub (root_->out_path ()))) + fail (l) << "out of project include " << p; + + out_base = in_out + ? p.directory () + : out_src (p.directory (), *root_); + } + + // Switch the scope. Note that we need to do this before figuring + // out the absolute buildfile path since we may switch the project + // root and src_root with it (i.e., include into a sub-project). + // + scope* ors (root_); + scope* ocs (scope_); + switch_scope (out_base); + + // Use the new scope's src_base to get absolute buildfile path + // if it is relative. + // + if (p.relative ()) + p = scope_->src_path () / p.leaf (); + + level6 ([&]{trace (l) << "absolute path " << p;}); + + if (!root_->buildfiles.insert (p).second) // Note: may be "new" root. + { + level5 ([&]{trace (l) << "skipping already included " << p;}); + scope_ = ocs; + root_ = ors; + continue; + } + + try + { + ifstream ifs (p.string ()); + + if (!ifs.is_open ()) + fail (l) << "unable to open " << p; + + ifs.exceptions (ifstream::failbit | ifstream::badbit); + + level5 ([&]{trace (t) << "entering " << p;}); + + enter_buildfile (p); + + const string* op (path_); + path_ = &path_pool.find (diag_relative (p)); // Relative to work. + + lexer l (ifs, *path_); + lexer* ol (lexer_); + lexer_ = &l; + + target* odt (default_target_); + default_target_ = nullptr; + + token t (type::eos, false, 0, 0); + type tt; + next (t, tt); + clause (t, tt); + + if (tt != type::eos) + fail (t) << "unexpected " << t; + + process_default_target (t); + + level5 ([&]{trace (t) << "leaving " << p;}); + + default_target_ = odt; + lexer_ = ol; + path_ = op; + } + catch (const ifstream::failure&) + { + fail (l) << "unable to read buildfile " << p; + } + + scope_ = ocs; + root_ = ors; + } + + if (tt == type::newline) + next (t, tt); + else if (tt != type::eos) + fail (t) << "expected newline instead of " << t; + } + + void parser:: + import (token& t, type& tt) + { + tracer trace ("parser::import", &path_); + + if (root_->src_path_ == nullptr) + fail (t) << "import during bootstrap"; + + next (t, tt); + + // General import format: + // + // import [=](|/])+ + // + value* val (nullptr); + const build2::variable* var (nullptr); + + type at; // Assignment type. + if (tt == type::name) + { + at = peek (); + + if (at == type::equal || + at == type::equal_plus || + at == type::plus_equal) + { + var = &var_pool.find (t.value); + val = at == type::equal + ? &scope_->assign (*var) + : &scope_->append (*var); + next (t, tt); // Consume =/=+/+=. + mode (lexer_mode::value); + next (t, tt); + } + } + + // The rest should be a list of projects and/or targets. Parse + // them as names to get variable expansion and directory prefixes. + // + const location l (get_location (t, &path_)); + names_type ns (tt != type::newline && tt != type::eos + ? names (t, tt) + : names_type ()); + + for (name& n: ns) + { + // build2::import() will check the name, if required. + // + names_type r (build2::import (*scope_, move (n), l)); + + if (val != nullptr) + { + if (at == type::equal) + val->assign (move (r), *var); + else if (at == type::equal_plus) + val->prepend (move (r), *var); + else + val->append (move (r), *var); + } + } + + if (tt == type::newline) + next (t, tt); + else if (tt != type::eos) + fail (t) << "expected newline instead of " << t; + } + + void parser:: + export_ (token& t, type& tt) + { + tracer trace ("parser::export", &path_); + + scope* ps (scope_->parent_scope ()); + + // This should be temp_scope. + // + if (ps == nullptr || ps->out_path () != scope_->out_path ()) + fail (t) << "export outside export stub"; + + // The rest is a value. Parse it as names to get variable expansion. + // build2::import() will check the names, if required. + // + mode (lexer_mode::value); + next (t, tt); + + if (tt != type::newline && tt != type::eos) + export_value_ = names (t, tt); + + if (tt == type::newline) + next (t, tt); + else if (tt != type::eos) + fail (t) << "expected newline instead of " << t; + } + + void parser:: + using_ (token& t, type& tt) + { + tracer trace ("parser::using", &path_); + + bool optional (t.value.back () == '?'); + + if (optional && boot_) + fail (t) << "optional module in bootstrap"; + + // The rest should be a list of module names. Parse them as names + // to get variable expansion, etc. + // + mode (lexer_mode::pairs, '@'); + next (t, tt); + const location l (get_location (t, &path_)); + names_type ns (tt != type::newline && tt != type::eos + ? names (t, tt) + : names_type ()); + + for (auto i (ns.begin ()); i != ns.end (); ++i) + { + string n, v; + + if (!i->simple ()) + fail (l) << "module name expected instead of " << *i; + + n = move (i->value); + + if (i->pair) + { + ++i; + if (!i->simple ()) + fail (l) << "module version expected instead of " << *i; + + v = move (i->value); + } + + // Handle the special 'build' module. + // + if (n == "build") + { + if (!v.empty ()) + { + unsigned int iv; + try {iv = to_version (v);} + catch (const invalid_argument& e) + { + fail (l) << "invalid version '" << v << "': " << e.what (); + } + + if (iv > BUILD2_VERSION) + fail (l) << "build2 " << v << " required" << + info << "running build2 " << BUILD2_VERSION_STR; + } + } + else + { + assert (v.empty ()); // Module versioning not yet implemented. + + if (boot_) + boot_module (n, *root_, l); + else + load_module (optional, n, *root_, *scope_, l); + } + } + + if (tt == type::newline) + next (t, tt); + else if (tt != type::eos) + fail (t) << "expected newline instead of " << t; + } + + static target* + derived_factory (const target_type& t, dir_path d, string n, const string* e) + { + // Pass our type to the base factory so that it can detect that it is + // being called to construct a derived target. This can be used, for + // example, to decide whether to "link up" to the group. + // + target* r (t.base->factory (t, move (d), move (n), e)); + r->derived_type = &t; + return r; + } + + constexpr const char derived_ext_var[] = "extension"; + + void parser:: + define (token& t, type& tt) + { + // define : + // + // See tests/define. + // + if (next (t, tt) != type::name) + fail (t) << "expected name instead of " << t << " in target type " + << "definition"; + + string dn (move (t.value)); + const location dnl (get_location (t, &path_)); + + if (next (t, tt) != type::colon) + fail (t) << "expected ':' instead of " << t << " in target type " + << "definition"; + + next (t, tt); + + if (tt == type::name) + { + // Target. + // + const string& bn (t.value); + const target_type* bt (scope_->find_target_type (bn)); + + if (bt == nullptr) + fail (t) << "unknown target type " << bn; + + unique_ptr dt (new target_type (*bt)); + dt->base = bt; + dt->factory = &derived_factory; + + // Override extension derivation function: we most likely don't want + // to use the same default as our base (think cli: file). + // + dt->extension = &target_extension_var; + + target_type& rdt (*dt); // Save a non-const reference to the object. + + auto pr (scope_->target_types.emplace (dn, target_type_ref (move (dt)))); + + if (!pr.second) + fail (dnl) << "target type " << dn << " already define in this scope"; + + // Patch the alias name to use the map's key storage. + // + rdt.name = pr.first->first.c_str (); + + next (t, tt); // Get newline. + } + else + fail (t) << "expected name instead of " << t << " in target type " + << "definition"; + + if (tt == type::newline) + next (t, tt); + else if (tt != type::eos) + fail (t) << "expected newline instead of " << t; + } + + void parser:: + if_else (token& t, type& tt) + { + // Handle the whole if-else chain. See tests/if-else. + // + bool taken (false); // One of the branches has been taken. + + for (;;) + { + string k (move (t.value)); + next (t, tt); + + bool take (false); // Take this branch? + + if (k != "else") + { + // Should we evaluate the expression if one of the branches has + // already been taken? On the one hand, evaluating it is a waste + // of time. On the other, it can be invalid and the only way for + // the user to know their buildfile is valid is to test every + // branch. There could also be side effects. We also have the same + // problem with ignored branch blocks except there evaluating it + // is not an option. So let's skip it. + // + if (taken) + skip_line (t, tt); + else + { + if (tt == type::newline || tt == type::eos) + fail (t) << "expected " << k << "-expression instead of " << t; + + // Parse as names to get variable expansion, evaluation, etc. + // + const location nsl (get_location (t, &path_)); + names_type ns (names (t, tt)); + + // Should evaluate to true or false. + // + if (ns.size () != 1 || !value_traits::assign (ns[0])) + fail (nsl) << "expected " << k << "-expression to evaluate to " + << "'true' or 'false' instead of '" << ns << "'"; + + bool e (ns[0].value == "true"); + take = (k.back () == '!' ? !e : e); + } + } + else + take = !taken; + + if (tt != type::newline) + fail (t) << "expected newline instead of " << t << " after " << k + << (k != "else" ? "-expression" : ""); + + if (next (t, tt) != type::lcbrace) + fail (t) << "expected { instead of " << t << " at the beginning of " + << k << "-block"; + + if (next (t, tt) != type::newline) + fail (t) << "expected newline after {"; + + next (t, tt); + + if (take) + { + clause (t, tt); + taken = true; + } + else + skip_block (t, tt); + + if (tt != type::rcbrace) + fail (t) << "expected } instead of " << t << " at the end of " << k + << "-block"; + + next (t, tt); + + if (tt == type::newline) + next (t, tt); + else if (tt != type::eos) + fail (t) << "expected newline after }"; + + // See if we have another el* keyword. + // + if (k != "else" && tt == type::name && keyword (t)) + { + const string& n (t.value); + + if (n == "else" || n == "elif" || n == "elif!") + continue; + } + + break; + } + } + + void parser:: + print (token& t, type& tt) + { + // Parse the rest as names to get variable expansion, etc. Switch + // to the variable value lexing mode so that we don't treat special + // characters (e.g., ':') as the end of the names. + // + mode (lexer_mode::value); + next (t, tt); + names_type ns (tt != type::newline && tt != type::eos + ? names (t, tt) + : names_type ()); + + cout << ns << endl; + + if (tt != type::eos) + next (t, tt); // Swallow newline. + } + + string parser:: + variable_name (names_type&& ns, const location& l) + { + // The list should contain a single, simple name. + // + if (ns.size () != 1 || !ns[0].simple () || ns[0].empty ()) + fail (l) << "variable name expected instead of " << ns; + + string& n (ns[0].value); + + if (n.front () == '.') // Fully qualified name. + return string (n, 1, string::npos); + else + //@@ TODO: append namespace if any. + return move (n); + } + + void parser:: + variable (token& t, type& tt, string name, type kind) + { + const auto& var (var_pool.find (move (name))); + names_type vns (variable_value (t, tt, var)); + + if (kind == type::equal) + { + value& v (target_ != nullptr + ? target_->assign (var) + : scope_->assign (var)); + v.assign (move (vns), var); + } + else + { + value& v (target_ != nullptr + ? target_->append (var) + : scope_->append (var)); + + if (kind == type::equal_plus) + v.prepend (move (vns), var); + else + v.append (move (vns), var); + } + } + + names parser:: + variable_value (token& t, type& tt, const variable_type& var) + { + if (var.pairs != '\0') + mode (lexer_mode::pairs, var.pairs); + else + mode (lexer_mode::value); + + next (t, tt); + return (tt != type::newline && tt != type::eos + ? names (t, tt) + : names_type ()); + } + + parser::names_type parser:: + eval (token& t, type& tt) + { + mode (lexer_mode::eval); + next (t, tt); + + names_type ns (tt != type::rparen ? names (t, tt) : names_type ()); + + if (tt != type::rparen) + fail (t) << "expected ')' instead of " << t; + + return ns; + } + + // Parse names inside {} and handle the following "crosses" (i.e., + // {a b}{x y}) if any. Return the number of names added to the list. + // + size_t parser:: + names_trailer (token& t, type& tt, + names_type& ns, + size_t pair, + const string* pp, + const dir_path* dp, + const string* tp) + { + next (t, tt); // Get what's after '{'. + + size_t count (ns.size ()); + names (t, tt, + ns, + false, + (pair != 0 + ? pair + : (ns.empty () || ns.back ().pair == '\0' ? 0 : ns.size ())), + pp, dp, tp); + count = ns.size () - count; + + if (tt != type::rcbrace) + fail (t) << "expected } instead of " << t; + + // See if we have a cross. See tests/names. + // + if (peek () == type::lcbrace && !peeked ().separated) + { + next (t, tt); // Get '{'. + const location loc (get_location (t, &path_)); + + names_type x; // Parse into a separate list of names. + names_trailer (t, tt, x, 0, nullptr, nullptr, nullptr); + + if (size_t n = x.size ()) + { + // Now cross the last 'count' names in 'ns' with 'x'. First we will + // allocate n - 1 additional sets of last 'count' names in 'ns'. + // + size_t b (ns.size () - count); // Start of 'count' names. + ns.reserve (ns.size () + count * (n - 1)); + for (size_t i (0); i != n - 1; ++i) + for (size_t j (0); j != count; ++j) + ns.push_back (ns[b + j]); + + // Now cross each name, this time including the first set. + // + for (size_t i (0); i != n; ++i) + { + for (size_t j (0); j != count; ++j) + { + name& l (ns[b + i * count + j]); + const name& r (x[i]); + + // Move the project names. + // + if (r.proj != nullptr) + { + if (l.proj != nullptr) + fail (loc) << "nested project name " << *r.proj; + + l.proj = r.proj; + } + + // Merge directories. + // + if (!r.dir.empty ()) + { + if (l.dir.empty ()) + l.dir = move (r.dir); + else + l.dir /= r.dir; + } + + // Figure out the type. As a first step, "promote" the lhs value + // to type. + // + if (!l.value.empty ()) + { + if (!l.type.empty ()) + fail (loc) << "nested type name " << l.value; + + l.type.swap (l.value); + } + + if (!r.type.empty ()) + { + if (!l.type.empty ()) + fail (loc) << "nested type name " << r.type; + + l.type = move (r.type); + } + + l.value = move (r.value); + + // @@ TODO: need to handle pairs on lhs. I think all that needs + // to be done is skip pair's first elements. Maybe also check + // that there are no pairs on the rhs. There is just no easy + // way to enable the pairs mode to test it, yet. + } + } + + count *= n; + } + } + + return count; + } + + void parser:: + names (token& t, type& tt, + names_type& ns, + bool chunk, + size_t pair, + const string* pp, + const dir_path* dp, + const string* tp) + { + // If pair is not 0, then it is an index + 1 of the first half of + // the pair for which we are parsing the second halves, e.g., + // a={b c d{e f} {}}. + // + + // Buffer that is used to collect the complete name in case of + // an unseparated variable expansion or eval context, e.g., + // 'foo$bar($baz)fox'. The idea is to concatenate all the + // individual parts in this buffer and then re-inject it into + // the loop as a single token. + // + string concat; + + // Number of names in the last group. This is used to detect when + // we need to add an empty first pair element (e.g., {=y}) or when + // we have a for now unsupported multi-name LHS (e.g., {x y}=z). + // + size_t count (0); + + for (bool first (true);; first = false) + { + // If the accumulating buffer is not empty, then we have two options: + // continue accumulating or inject. We inject if the next token is + // not a name, var expansion, or eval context or if it is separated. + // + if (!concat.empty () && + ((tt != type::name && + tt != type::dollar && + tt != type::lparen) || peeked ().separated)) + { + tt = type::name; + t = token (move (concat), true, false, t.line, t.column); + concat.clear (); + } + else if (!first) + { + // If we are chunking, stop at the next separated token. Unless + // current or next token is a pair separator, since we want the + // "x = y" pair to be parsed as a single chunk. + // + bool p (t.type == type::pair_separator); // Current token. + + next (t, tt); + + if (chunk && t.separated && (tt != type::pair_separator && !p)) + break; + } + + // Name. + // + if (tt == type::name) + { + string name (t.value); //@@ move? + tt = peek (); + + // Should we accumulate? If the buffer is not empty, then + // we continue accumulating (the case where we are separated + // should have been handled by the injection code above). If + // the next token is a var expansion or eval context and it + // is not separated, then we need to start accumulating. + // + if (!concat.empty () || // Continue. + ((tt == type::dollar || + tt == type::lparen) && !peeked ().separated)) // Start. + { + concat += name; + continue; + } + + string::size_type p (name.find_last_of ("/%")); + + // First take care of project. A project-qualified name is + // not very common, so we can afford some copying for the + // sake of simplicity. + // + const string* pp1 (pp); + + if (p != string::npos) + { + bool last (name[p] == '%'); + string::size_type p1 (last ? p : name.rfind ('%', p - 1)); + + if (p1 != string::npos) + { + string proj; + proj.swap (name); + + // First fix the rest of the name. + // + name.assign (proj, p1 + 1, string::npos); + p = last ? string::npos : p - (p1 + 1); + + // Now process the project name. + // @@ Validate it. + // + proj.resize (p1); + + if (pp != nullptr) + fail (t) << "nested project name " << proj; + + pp1 = &project_name_pool.find (proj); + } + } + + string::size_type n (p != string::npos ? name.size () - 1 : 0); + + // See if this is a type name, directory prefix, or both. That + // is, it is followed by an un-separated '{'. + // + if (tt == type::lcbrace && !peeked ().separated) + { + next (t, tt); + + if (p != n && tp != nullptr) + fail (t) << "nested type name " << name; + + dir_path d1; + const dir_path* dp1 (dp); + + string t1; + const string* tp1 (tp); + + if (p == string::npos) // type + tp1 = &name; + else if (p == n) // directory + { + if (dp == nullptr) + d1 = dir_path (name); + else + d1 = *dp / dir_path (name); + + dp1 = &d1; + } + else // both + { + t1.assign (name, p + 1, n - p); + + if (dp == nullptr) + d1 = dir_path (name, 0, p + 1); + else + d1 = *dp / dir_path (name, 0, p + 1); + + dp1 = &d1; + tp1 = &t1; + } + + count = names_trailer (t, tt, ns, pair, pp1, dp1, tp1); + tt = peek (); + continue; + } + + // If we are a second half of a pair, add another first half + // unless this is the first instance. + // + if (pair != 0 && pair != ns.size ()) + ns.push_back (ns[pair - 1]); + + count = 1; + + // If it ends with a directory separator, then it is a directory. + // Note that at this stage we don't treat '.' and '..' as special + // (unless they are specified with a directory separator) because + // then we would have ended up treating '.: ...' as a directory + // scope. Instead, this is handled higher up the processing chain, + // in target_types::find(). This would also mess up reversibility + // to simple name. + // + // @@ TODO: and not quoted + // + if (p == n) + { + // For reversibility to simple name, only treat it as a directory + // if the string is an exact representation. + // + if (p != 0 && name[p - 1] != '/') // Take care of the "//" case. + name.resize (p); // Strip trailing '/'. + + dir_path dir (move (name), dir_path::exact); + + if (!dir.empty ()) + { + if (dp != nullptr) + dir = *dp / dir; + + ns.emplace_back (pp1, + move (dir), + (tp != nullptr ? *tp : string ()), + string ()); + continue; + } + + // Add the trailing slash back and treat it as a simple name. + // + if (p != 0 && name[p - 1] != '/') + name.push_back ('/'); + } + + ns.emplace_back (pp1, + (dp != nullptr ? *dp : dir_path ()), + (tp != nullptr ? *tp : string ()), + move (name)); + continue; + } + + // Variable expansion/function call or eval context. + // + if (tt == type::dollar || tt == type::lparen) + { + // These two cases are pretty similar in that in both we + // pretty quickly end up with a list of names that we need + // to splice into the result. + // + names_type lv_data; + const names_type* plv; + + location loc; + const char* what; // Variable or evaluation context. + + if (tt == type::dollar) + { + // Switch to the variable name mode. We want to use this + // mode for $foo but not for $(foo). Since we don't know + // whether the next token is a paren or a name, we turn + // it on and switch to the eval mode if what we get next + // is a paren. + // + mode (lexer_mode::variable); + next (t, tt); + loc = get_location (t, &path_); + + string n; + if (tt == type::name) + n = t.value; + else if (tt == type::lparen) + { + expire_mode (); + names_type ns (eval (t, tt)); + + // Make sure the result of evaluation is a single, simple name. + // + if (ns.size () != 1 || !ns.front ().simple ()) + fail (loc) << "variable/function name expected instead of '" + << ns << "'"; + + n = move (ns.front ().value); + } + else + fail (t) << "variable/function name expected instead of " << t; + + if (n.empty ()) + fail (loc) << "empty variable/function name"; + + // Figure out whether this is a variable expansion or a function + // call. + // + tt = peek (); + + if (tt == type::lparen) + { + next (t, tt); // Get '('. + names_type ns (eval (t, tt)); + + // Just a stub for now. + // + cout << n << "(" << ns << ")" << endl; + + tt = peek (); + + if (lv_data.empty ()) + continue; + + plv = &lv_data; + what = "function call"; + } + else + { + // Process variable name. + // + if (n.front () == '.') // Fully qualified name. + n.erase (0, 1); + else + { + //@@ TODO: append namespace if any. + } + + // Lookup. + // + const auto& var (var_pool.find (move (n))); + auto l (target_ != nullptr ? (*target_)[var] : (*scope_)[var]); + + // Undefined/NULL namespace variables are not allowed. + // + if (!l && var.name.find ('.') != string::npos) + fail (loc) << "undefined/null namespace variable " << var.name; + + if (!l || l->empty ()) + continue; + + plv = &l->data_; + what = "variable expansion"; + } + } + else + { + loc = get_location (t, &path_); + lv_data = eval (t, tt); + + tt = peek (); + + if (lv_data.empty ()) + continue; + + plv = &lv_data; + what = "context evaluation"; + } + + // @@ Could move if (lv == &lv_data). + // + const names_type& lv (*plv); + + // Should we accumulate? If the buffer is not empty, then + // we continue accumulating (the case where we are separated + // should have been handled by the injection code above). If + // the next token is a name or var expansion and it is not + // separated, then we need to start accumulating. + // + if (!concat.empty () || // Continue. + ((tt == type::name || // Start. + tt == type::dollar || + tt == type::lparen) && !peeked ().separated)) + { + // This should be a simple value or a simple directory. The + // token still points to the name (or closing paren). + // + if (lv.size () > 1) + fail (loc) << "concatenating " << what << " contains multiple " + << "values"; + + const name& n (lv[0]); + + if (n.qualified ()) + fail (loc) << "concatenating " << what << " contains project name"; + + if (n.typed ()) + fail (loc) << "concatenating " << what << " contains type"; + + if (!n.dir.empty ()) + { + if (!n.value.empty ()) + fail (loc) << "concatenating " << what << " contains directory"; + + concat += n.dir.string (); + } + else + concat += n.value; + } + else + { + // Copy the names from the variable into the resulting name list + // while doing sensible things with the types and directories. + // + for (const name& n: lv) + { + const string* pp1 (pp); + const dir_path* dp1 (dp); + const string* tp1 (tp); + + if (n.proj != 0) + { + if (pp == nullptr) + pp1 = n.proj; + else + fail (loc) << "nested project name " << *n.proj << " in " + << what; + } + + dir_path d1; + if (!n.dir.empty ()) + { + if (dp != nullptr) + { + if (n.dir.absolute ()) + fail (loc) << "nested absolute directory " << n.dir + << " in " << what; + + d1 = *dp / n.dir; + dp1 = &d1; + } + else + dp1 = &n.dir; + } + + if (!n.type.empty ()) + { + if (tp == nullptr) + tp1 = &n.type; + else + fail (loc) << "nested type name " << n.type << " in " << what; + } + + // If we are a second half of a pair. + // + if (pair != 0) + { + // Check that there are no nested pairs. + // + if (n.pair != '\0') + fail (loc) << "nested pair in " << what; + + // And add another first half unless this is the first instance. + // + if (pair != ns.size ()) + ns.push_back (ns[pair - 1]); + } + + ns.emplace_back (pp1, + (dp1 != nullptr ? *dp1 : dir_path ()), + (tp1 != nullptr ? *tp1 : string ()), + n.value); + + ns.back ().pair = n.pair; + } + + count = lv.size (); + } + + continue; + } + + // Untyped name group without a directory prefix, e.g., '{foo bar}'. + // + if (tt == type::lcbrace) + { + count = names_trailer (t, tt, ns, pair, pp, dp, tp); + tt = peek (); + continue; + } + + // A pair separator (only in the pairs mode). + // + if (tt == type::pair_separator) + { + if (pair != 0) + fail (t) << "nested pair on the right hand side of a pair"; + + if (count > 1) + fail (t) << "multiple names on the left hand side of a pair"; + + if (count == 0) + { + // Empty LHS, (e.g., {=y}), create an empty name. + // + ns.emplace_back (pp, + (dp != nullptr ? *dp : dir_path ()), + (tp != nullptr ? *tp : string ()), + string ()); + count = 1; + } + + ns.back ().pair = t.pair; + tt = peek (); + continue; + } + + if (!first) + break; + + if (tt == type::rcbrace) // Empty name, e.g., dir{}. + { + // If we are a second half of a pair, add another first half + // unless this is the first instance. + // + if (pair != 0 && pair != ns.size ()) + ns.push_back (ns[pair - 1]); + + ns.emplace_back (pp, + (dp != nullptr ? *dp : dir_path ()), + (tp != nullptr ? *tp : string ()), + string ()); + break; + } + else + // Our caller expected this to be a name. + // + fail (t) << "expected name instead of " << t; + } + + // Handle the empty RHS in a pair, (e.g., {y=}). + // + if (!ns.empty () && ns.back ().pair != '\0') + { + ns.emplace_back (pp, + (dp != nullptr ? *dp : dir_path ()), + (tp != nullptr ? *tp : string ()), + string ()); + } + } + + void parser:: + skip_line (token& t, type& tt) + { + for (; tt != type::newline && tt != type::eos; next (t, tt)) ; + } + + void parser:: + skip_block (token& t, type& tt) + { + // Skip until } or eos, keeping track of the {}-balance. + // + for (size_t b (0); tt != type::eos; ) + { + if (tt == type::lcbrace || tt == type::rcbrace) + { + type ptt (peek ()); + if (ptt == type::newline || ptt == type::eos) // Block { or }. + { + if (tt == type::lcbrace) + ++b; + else + { + if (b == 0) + break; + + --b; + } + } + } + + skip_line (t, tt); + + if (tt != type::eos) + next (t, tt); + } + } + + bool parser:: + keyword (token& t) + { + assert (replay_ == replay::stop); // Can't be used in a replay. + assert (t.type == type::name); + + // The goal here is to allow using keywords as variable names and + // target types without imposing ugly restrictions/decorators on + // keywords (e.g., '.using' or 'USING'). A name is considered a + // potential keyword if: + // + // - it is not quoted [so a keyword can always be escaped] and + // - next token is '\n' (or eos) or '(' [so if(...) will work] or + // - next token is separated and is not '=', '=+', or '+=' [which + // means a "directive trailer" can never start with one of them]. + // + // See tests/keyword. + // + if (!t.quoted) + { + // We cannot peek at the whole token here since it might have to be + // lexed in a different mode. So peek at its first character. + // + pair p (lexer_->peek_char ()); + char c (p.first); + + return c == '\n' || c == '\0' || c == '(' || + (p.second && c != '=' && c != '+'); + } + + return false; + } + + // Buildspec parsing. + // + + // Here is the problem: we "overload" '(' and ')' to mean operation + // application rather than the eval context. At the same time we want + // to use names() to parse names, get variable expansion/function calls, + // quoting, etc. We just need to disable the eval context. The way this + // is done has two parts: Firstly, we parse names in chunks and detect + // and handle the opening paren. In other words, a buildspec like + // 'clean (./)' is "chunked" as 'clean', '(', etc. While this is fairly + // straightforward, there is one snag: concatenating eval contexts, as + // in 'clean(./)'. Normally, this will be treated as a single chunk and + // we don't want that. So here comes the trick (or hack, if you like): + // we will make every opening paren token "separated" (i.e., as if it + // was proceeded by a space). This will disable concatenating eval. In + // fact, we will even go a step further and only do this if we are in + // the original pairs mode. This will allow us to still use eval + // contexts in buildspec, provided that we quote it: '"cle(an)"'. Note + // also that function calls still work as usual: '$filter (clean test)'. + // To disable a function call and make it instead a var that is expanded + // into operation name(s), we can use quoting: '"$ops"(./)'. + // + static void + paren_processor (token& t, const lexer& l) + { + if (t.type == type::lparen && l.mode () == lexer_mode::pairs) + t.separated = true; + } + + buildspec parser:: + parse_buildspec (istream& is, const std::string& name) + { + path_ = &name; // Note: caller pools. + + lexer l (is, name, &paren_processor); + lexer_ = &l; + target_ = nullptr; + scope_ = root_ = global_scope; + + // Turn on pairs recognition with '@' as the pair separator (e.g., + // src_root/@out_root/exe{foo bar}). + // + mode (lexer_mode::pairs, '@'); + + token t (type::eos, false, 0, 0); + type tt; + next (t, tt); + + return buildspec_clause (t, tt, type::eos); + } + + static bool + opname (const name& n) + { + // First it has to be a non-empty simple name. + // + if (n.pair != '\0' || !n.simple () || n.empty ()) + return false; + + // C identifier. + // + for (size_t i (0); i != n.value.size (); ++i) + { + char c (n.value[i]); + if (c != '_' && !(i != 0 ? isalnum (c) : isalpha (c))) + return false; + } + + return true; + } + + buildspec parser:: + buildspec_clause (token& t, type& tt, type tt_end) + { + buildspec bs; + + while (tt != tt_end) + { + // We always start with one or more names. Eval context + // (lparen) only allowed if quoted. + // + if (tt != type::name && + tt != type::lcbrace && // Untyped name group: '{foo ...' + tt != type::dollar && // Variable expansion: '$foo ...' + !(tt == type::lparen && mode () == lexer_mode::quoted) && + tt != type::pair_separator) // Empty pair LHS: '@foo ...' + fail (t) << "operation or target expected instead of " << t; + + const location l (get_location (t, &path_)); // Start of names. + + // This call will parse the next chunk of output and produce + // zero or more names. + // + names_type ns (names (t, tt, true)); + + // What these names mean depends on what's next. If it is an + // opening paren, then they are operation/meta-operation names. + // Otherwise they are targets. + // + if (tt == type::lparen) // Peeked into by names(). + { + if (ns.empty ()) + fail (t) << "operation name expected before '('"; + + for (const name& n: ns) + if (!opname (n)) + fail (l) << "operation name expected instead of '" << n << "'"; + + // Inside '(' and ')' we have another, nested, buildspec. + // + next (t, tt); + const location l (get_location (t, &path_)); // Start of nested names. + buildspec nbs (buildspec_clause (t, tt, type::rparen)); + + // Merge the nested buildspec into ours. But first determine + // if we are an operation or meta-operation and do some sanity + // checks. + // + bool meta (false); + for (const metaopspec& nms: nbs) + { + // We definitely shouldn't have any meta-operations. + // + if (!nms.name.empty ()) + fail (l) << "nested meta-operation " << nms.name; + + if (!meta) + { + // If we have any operations in the nested spec, then this + // mean that our names are meta-operation names. + // + for (const opspec& nos: nms) + { + if (!nos.name.empty ()) + { + meta = true; + break; + } + } + } + } + + // No nested meta-operations means we should have a single + // metaopspec object with empty meta-operation name. + // + assert (nbs.size () == 1); + const metaopspec& nmo (nbs.back ()); + + if (meta) + { + for (name& n: ns) + { + bs.push_back (nmo); + bs.back ().name = move (n.value); + } + } + else + { + // Since we are not a meta-operation, the nested buildspec + // should be just a bunch of targets. + // + assert (nmo.size () == 1); + const opspec& nos (nmo.back ()); + + if (bs.empty () || !bs.back ().name.empty ()) + bs.push_back (metaopspec ()); // Empty (default) meta operation. + + for (name& n: ns) + { + bs.back ().push_back (nos); + bs.back ().back ().name = move (n.value); + } + } + + next (t, tt); // Done with '('. + } + else if (!ns.empty ()) + { + // Group all the targets into a single operation. In other + // words, 'foo bar' is equivalent to 'update(foo bar)'. + // + if (bs.empty () || !bs.back ().name.empty ()) + bs.push_back (metaopspec ()); // Empty (default) meta operation. + + metaopspec& ms (bs.back ()); + + for (auto i (ns.begin ()), e (ns.end ()); i != e; ++i) + { + // @@ We may actually want to support this at some point. + // + if (i->qualified ()) + fail (l) << "target name expected instead of " << *i; + + if (opname (*i)) + ms.push_back (opspec (move (i->value))); + else + { + // Do we have the src_base? + // + dir_path src_base; + if (i->pair != '\0') + { + if (i->typed ()) + fail (l) << "expected target src_base instead of " << *i; + + src_base = move (i->dir); + + if (!i->value.empty ()) + src_base /= dir_path (move (i->value)); + + ++i; + assert (i != e); // Got to have the second half of the pair. + } + + if (ms.empty () || !ms.back ().name.empty ()) + ms.push_back (opspec ()); // Empty (default) operation. + + opspec& os (ms.back ()); + os.emplace_back (move (src_base), move (*i)); + } + } + } + } + + return bs; + } + + void parser:: + switch_scope (const dir_path& p) + { + tracer trace ("parser::switch_scope", &path_); + + // First, enter the scope into the map and see if it is in any + // project. If it is not, then there is nothing else to do. + // + auto i (scopes.insert (p, nullptr, true, false)); + scope_ = i->second; + scope* rs (scope_->root_scope ()); + + if (rs == nullptr) + return; + + // Path p can be src_base or out_base. Figure out which one it is. + // + dir_path out_base (p.sub (rs->out_path ()) ? p : src_out (p, *rs)); + + // Create and bootstrap root scope(s) of subproject(s) that this + // scope may belong to. If any were created, load them. Note that + // we need to do this before figuring out src_base since we may + // switch the root project (and src_root with it). + // + { + scope* nrs (&create_bootstrap_inner (*rs, out_base)); + + if (rs != nrs) + { + load_root_pre (*nrs); // Load outer roots recursively. + rs = nrs; + } + } + + // Switch to the new root scope. + // + if (rs != root_) + { + level5 ([&]{trace << "switching to root scope " << rs->out_path ();}); + root_ = rs; + } + + // Now we can figure out src_base and finish setting the scope. + // + dir_path src_base (src_out (out_base, *rs)); + setup_base (i, move (out_base), move (src_base)); + } + + void parser:: + process_default_target (token& t) + { + tracer trace ("parser::process_default_target", &path_); + + // The logic is as follows: if we have an explicit current directory + // target, then that's the default target. Otherwise, we take the + // first target and use it as a prerequisite to create an implicit + // current directory target, effectively making it the default + // target via an alias. If there are no targets in this buildfile, + // then we don't do anything. + // + if (default_target_ == nullptr || // No targets in this buildfile. + targets.find (dir::static_type, // Explicit current dir target. + scope_->out_path (), + "", + nullptr, + trace) != targets.end ()) + return; + + target& dt (*default_target_); + + level5 ([&]{trace (t) << "creating current directory alias for " << dt;}); + + target& ct ( + targets.insert ( + dir::static_type, scope_->out_path (), "", nullptr, trace).first); + + prerequisite& p ( + scope_->prerequisites.insert ( + nullptr, + dt.type (), + dt.dir, + dt.name, + dt.ext, + *scope_, // Doesn't matter which scope since dir is absolute. + trace).first); + + p.target = &dt; + ct.prerequisites.emplace_back (p); + } + + void parser:: + enter_buildfile (const path& p) + { + tracer trace ("parser::enter_buildfile", &path_); + + const char* e (p.extension ()); + targets.insert ( + p.directory (), + p.leaf ().base ().string (), + &extension_pool.find (e == nullptr ? "" : e), // Always specified. + trace); + } + + type parser:: + next (token& t, type& tt) + { + if (peeked_) + { + t = move (peek_); + peeked_ = false; + } + else + t = (replay_ == replay::play ? replay_next () : lexer_->next ()); + + if (replay_ == replay::save) + replay_data_.push_back (t); + + tt = t.type; + return tt; + } + + type parser:: + peek () + { + if (!peeked_) + { + peek_ = (replay_ == replay::play ? replay_next () : lexer_->next ()); + peeked_ = true; + } + + return peek_.type; + } + + static location + get_location (const token& t, const void* data) + { + assert (data != nullptr); + const string& p (**static_cast (data)); + return location (p.c_str (), t.line, t.column); + } +} diff --git a/build2/path-io b/build2/path-io new file mode 100644 index 0000000..c41e1b9 --- /dev/null +++ b/build2/path-io @@ -0,0 +1,26 @@ +// file : build2/path-io -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_PATH_IO +#define BUILD2_PATH_IO + +#include + +#include + +// Custom path IO. +// +namespace build2 +{ + using butl::path; + using butl::dir_path; + + std::ostream& + operator<< (std::ostream&, const path&); + + std::ostream& + operator<< (std::ostream&, const dir_path&); +} + +#endif // BUILD2_PATH_IO diff --git a/build2/path-io.cxx b/build2/path-io.cxx new file mode 100644 index 0000000..0ee50b4 --- /dev/null +++ b/build2/path-io.cxx @@ -0,0 +1,39 @@ +// file : build2/path-io.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include + +#include +#include + +using namespace std; + +namespace build2 +{ + ostream& + operator<< (ostream& os, const path& p) + { + return os << (relative (os) ? diag_relative (p) : p.string ()); + } + + ostream& + operator<< (ostream& os, const dir_path& d) + { + if (relative (os)) + os << diag_relative (d); + else + { + const string& s (d.string ()); + + // Print the directory with trailing '/'. + // + if (!s.empty ()) + os << s << (dir_path::traits::is_separator (s.back ()) ? "" : "/"); + } + + return os; + } +} diff --git a/build2/prerequisite b/build2/prerequisite new file mode 100644 index 0000000..0a5f51c --- /dev/null +++ b/build2/prerequisite @@ -0,0 +1,129 @@ +// file : build2/prerequisite -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_PREREQUISITE +#define BUILD2_PREREQUISITE + +#include +#include +#include +#include // move +#include +#include // reference_wrapper + +#include +#include +#include // extension_pool +#include + +namespace build2 +{ + class scope; + class target; + + // Light-weight (by being shallow-pointing) prerequisite key, similar + // to (and based on) target key. + // + class prerequisite_key + { + public: + typedef build2::scope scope_type; + + mutable const std::string* proj; // Can be NULL, from project_name_pool. + target_key tk; + mutable scope_type* scope; // Can be NULL if tk.dir is absolute. + }; + + inline bool + operator< (const prerequisite_key& x, const prerequisite_key& y) + { + assert (x.scope == y.scope); + + // Can compare project name pointers since they are from project_name_pool. + // + return x.proj < y.proj || (x.proj == y.proj && x.tk < y.tk); + } + + std::ostream& + operator<< (std::ostream&, const prerequisite_key&); + + class prerequisite + { + public: + typedef build2::target target_type; + typedef build2::target_type target_type_type; + typedef build2::scope scope_type; + + prerequisite (const std::string* p, + const target_type_type& t, + dir_path d, + std::string n, + const std::string* e, + scope_type& s) + : proj (p), + type (t), + dir (std::move (d)), + name (std::move (n)), + ext (e), + scope (s), + target (nullptr) {} + + public: + const std::string* proj; // NULL if not project-qualified. + const target_type_type& type; + const dir_path dir; // Normalized absolute or relative (to scope). + const std::string name; + const std::string* ext; // NULL if unspecified. + scope_type& scope; + target_type* target; // NULL if not yet resolved. Note that this should + // always be the "primary target", not a member of + // a target group. + prerequisite_key + key () const + { + return prerequisite_key {proj, {&type, &dir, &name, &ext}, &scope}; + } + + public: + // Prerequisite (target) type. + // + template + bool + is_a () const {return type.is_a ();} + }; + + inline bool + operator< (const prerequisite& x, const prerequisite& y) + { + return x.key () < y.key (); + } + + inline std::ostream& + operator<< (std::ostream& os, const prerequisite& p) + { + return os << p.key (); + } + + // Set of prerequisites in a scope. + // + struct prerequisite_set: std::set + { + std::pair + insert (const std::string* proj, + const target_type&, + dir_path dir, + std::string name, + const std::string* ext, + scope&, + tracer&); + + std::pair + insert (const std::string* proj, const target_key& tk, scope& s, tracer& t) + { + return insert (proj, *tk.type, *tk.dir, *tk.name, *tk.ext, s, t); + } + }; +} + +#endif // BUILD2_PREREQUISITE diff --git a/build2/prerequisite.cxx b/build2/prerequisite.cxx new file mode 100644 index 0000000..6a31071 --- /dev/null +++ b/build2/prerequisite.cxx @@ -0,0 +1,82 @@ +// file : build2/prerequisite.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include + +#include +#include // target_type +#include +#include + +using namespace std; + +namespace build2 +{ + // prerequisite_key + // + ostream& + operator<< (ostream& os, const prerequisite_key& pk) + { + if (pk.proj != nullptr) + os << *pk.proj << '%'; + + // Don't print scope if we are project-qualified or the + // prerequisite's directory is absolute. In both these + // cases the scope is not used to resolve it to target. + // + else if (!pk.tk.dir->absolute ()) + { + string s (diag_relative (pk.scope->out_path (), false)); + + if (!s.empty ()) + os << s << ':'; + } + + return os << pk.tk; + } + + // prerequisite_set + // + auto prerequisite_set:: + insert (const std::string* proj, + const target_type& tt, + dir_path dir, + std::string name, + const std::string* ext, + scope& s, + tracer& trace) -> pair + { + //@@ OPT: would be nice to somehow first check if this prerequisite is + // already in the set before allocating a new instance. + + // Find or insert. + // + auto r (emplace (proj, tt, move (dir), move (name), ext, s)); + prerequisite& p (const_cast (*r.first)); + + // Update extension if the existing prerequisite has it unspecified. + // + if (p.ext != ext) + { + level5 ([&]{ + diag_record r (trace); + r << "assuming prerequisite " << p << " is the same as the " + << "one with "; + if (ext == nullptr) + r << "unspecified extension"; + else if (ext->empty ()) + r << "no extension"; + else + r << "extension " << *ext; + }); + + if (ext != nullptr) + p.ext = ext; + } + + return pair (p, r.second); + } +} diff --git a/build2/rule b/build2/rule new file mode 100644 index 0000000..3d0782f --- /dev/null +++ b/build2/rule @@ -0,0 +1,135 @@ +// file : build2/rule -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_RULE +#define BUILD2_RULE + +#include +#include // nullptr_t + +#include +#include +#include + +namespace build2 +{ + class match_result + { + public: + typedef build2::target target_type; + typedef build2::prerequisite prerequisite_type; + + // Can contain neither (both are NULL), one of, or both. If both + // are NULL, then it is a "no match" indicator. + // + // Note that if the "payload" is stored in *value instead of + // prerequisite, then target must not be NULL. + // + union + { + prerequisite_type* prerequisite; + + bool bvalue; + void* pvalue; + const void* cpvalue; + }; + + target_type* target; + + action recipe_action = action (); // Used as recipe's action if set. + + match_result (std::nullptr_t v = nullptr): prerequisite (v), target (v) {} + match_result (prerequisite_type& p): prerequisite (&p), target (nullptr) {} + match_result (prerequisite_type* p): prerequisite (p), target (nullptr) {} + match_result (target_type& t): prerequisite (nullptr), target (&t) {} + match_result (target_type* t): prerequisite (nullptr), target (t) {} + match_result (const prerequisite_member& pm) + : prerequisite (&pm.prerequisite.get ()), target (pm.target) {} + + match_result (target_type& t, bool v): bvalue (v), target (&t) {} + match_result (target_type& t, void* v): pvalue (v), target (&t) {} + match_result (target_type& t, const void* v): cpvalue (v), target (&t) {} + match_result (target_type& t, std::nullptr_t v): pvalue (v), target (&t) {} + + explicit + operator bool () const + { + return target != nullptr || prerequisite != nullptr; + } + }; + + class rule + { + public: + virtual match_result + match (action, target&, const std::string& hint) const = 0; + + virtual recipe + apply (action, target&, const match_result&) const = 0; + }; + + // Fallback rule that on update verifies that the file exists and is + // not older than any of its prerequisites. + // + class file_rule: public rule + { + public: + virtual match_result + match (action, target&, const std::string& hint) const; + + virtual recipe + apply (action, target&, const match_result&) const; + + static target_state + perform_update (action, target&); + + static file_rule instance; + }; + + class alias_rule: public rule + { + public: + virtual match_result + match (action, target&, const std::string& hint) const; + + virtual recipe + apply (action, target&, const match_result&) const; + + static alias_rule instance; + }; + + class fsdir_rule: public rule + { + public: + virtual match_result + match (action, target&, const std::string& hint) const; + + virtual recipe + apply (action, target&, const match_result&) const; + + static target_state + perform_update (action, target&); + + static target_state + perform_clean (action, target&); + + static fsdir_rule instance; + }; + + // Fallback rule that always matches and does nothing. + // + class fallback_rule: public build2::rule + { + public: + virtual match_result + match (action, target& t, const std::string&) const {return t;} + + virtual recipe + apply (action, target&, const match_result&) const {return noop_recipe;} + + static fallback_rule instance; + }; +} + +#endif // BUILD2_RULE diff --git a/build2/rule-map b/build2/rule-map new file mode 100644 index 0000000..f86ef16 --- /dev/null +++ b/build2/rule-map @@ -0,0 +1,115 @@ +// file : build2/rule-map -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_RULE_MAP +#define BUILD2_RULE_MAP + +#include +#include +#include +#include // unique_ptr +#include // reference_wrapper + +#include + +#include +#include + +namespace build2 +{ + class rule; + + using target_type_rule_map = std::map< + const target_type*, + butl::prefix_map, '.'>>; + + // This is an "indexed map" with operation_id being the index. Entry + // with id 0 is a wildcard. + // + class operation_rule_map + { + public: + template + void + insert (operation_id oid, const char* hint, rule& r) + { + // 3 is the number of builtin operations. + // + if (oid >= map_.size ()) + map_.resize ((oid < 3 ? 3 : oid) + 1); + + map_[oid][&T::static_type].emplace (hint, r); + } + + // Return NULL if not found. + // + const target_type_rule_map* + operator[] (operation_id oid) const + { + return map_.size () > oid ? &map_[oid] : nullptr; + } + + bool + empty () const {return map_.empty ();} + + private: + std::vector map_; + }; + + // This is another indexed map but this time meta_operation_id is the + // index. The implementation is different, however: here we use a linked + // list with the first, statically-allocated node corresponding to the + // perform meta-operation. The idea is to try and get away with a dynamic + // allocation for the common cases since most rules will be registered + // for perform, at least on non-root scopes. + // + class rule_map + { + public: + + template + void + insert (action_id a, const char* hint, rule& r) + { + insert (a >> 4, a & 0x0F, hint, r); + } + + template + void + insert (meta_operation_id mid, operation_id oid, const char* hint, rule& r) + { + if (mid_ == mid) + map_.insert (oid, hint, r); + else + { + if (next_ == nullptr) + next_.reset (new rule_map (mid)); + + next_->insert (mid, oid, hint, r); + } + } + + // Return NULL if not found. + // + const operation_rule_map* + operator[] (meta_operation_id mid) const + { + return mid == mid_ ? &map_ : next_ == nullptr ? nullptr : (*next_)[mid]; + } + + explicit + rule_map (meta_operation_id mid = perform_id): mid_ (mid) {} + + bool + empty () const {return map_.empty () && next_ == nullptr;} + + private: + meta_operation_id mid_; + operation_rule_map map_; + std::unique_ptr next_; + }; +} + +#endif // BUILD2_RULE_MAP diff --git a/build2/rule.cxx b/build2/rule.cxx new file mode 100644 index 0000000..34fb3b4 --- /dev/null +++ b/build2/rule.cxx @@ -0,0 +1,249 @@ +// file : build2/rule.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include // move() +#include + +#include + +#include +#include +#include +#include +#include + +using namespace std; +using namespace butl; + +namespace build2 +{ + // file_rule + // + // Note that this rule is special. It is the last, fallback rule. If + // it doesn't match, then no other rule can possibly match and we have + // an error. It also cannot be ambigious with any other rule. As a + // result the below implementation bends or ignores quite a few rules + // that normal implementations should follow. So you probably shouldn't + // use it as a guide to implement your own, normal, rules. + // + match_result file_rule:: + match (action a, target& t, const string&) const + { + tracer trace ("file_rule::match"); + + // While strictly speaking we should check for the file's existence + // for every action (because that's the condition for us matching), + // for some actions this is clearly a waste. Say, perform_clean: we + // are not doing anything for this action so not checking if the file + // exists seems harmless. So the overall guideline seems to be this: + // if we don't do anything for the action (other than performing it + // on the prerequisites), then we match. + // + switch (a) + { + case perform_update_id: + { + path_target& pt (dynamic_cast (t)); + + // Assign the path. While normally we shouldn't do this in match(), + // no other rule should ever be ambiguous with the fallback one. + // + if (pt.path ().empty ()) + pt.derive_path (); + + // We cannot just call pt.mtime() since we haven't matched yet. + // + timestamp ts (file_mtime (pt.path ())); + pt.mtime (ts); + + if (ts != timestamp_nonexistent) + return t; + + level4 ([&]{trace << "no existing file for target " << t;}); + return nullptr; + } + default: + return t; + } + } + + recipe file_rule:: + apply (action a, target& t, const match_result&) const + { + // Update triggers the update of this target's prerequisites + // so it would seem natural that we should also trigger their + // cleanup. However, this possibility is rather theoretical + // since such an update would render this target out of date + // which in turn would lead to an error. So until we see a + // real use-case for this functionality, we simply ignore + // the clean operation. + // + if (a.operation () == clean_id) + return noop_recipe; + + // If we have no prerequisites, then this means this file + // is up to date. Return noop_recipe which will also cause + // the target's state to be set to unchanged. This is an + // important optimization on which quite a few places that + // deal with predominantly static content rely. + // + if (!t.has_prerequisites ()) + return noop_recipe; + + // Search and match all the prerequisites. + // + search_and_match_prerequisites (a, t); + return a == perform_update_id ? &perform_update : default_recipe; + } + + target_state file_rule:: + perform_update (action a, target& t) + { + // Make sure the target is not older than any of its prerequisites. + // + timestamp mt (dynamic_cast (t).mtime ()); + + for (target* pt: t.prerequisite_targets) + { + target_state ts (execute (a, *pt)); + + // If this is an mtime-based target, then compare timestamps. + // + if (auto mpt = dynamic_cast (pt)) + { + timestamp mp (mpt->mtime ()); + + if (mt < mp) + fail << "no recipe to " << diag_do (a, t) << + info << "prerequisite " << *pt << " is ahead of " << t + << " by " << (mp - mt); + } + else + { + // Otherwise we assume the prerequisite is newer if it was changed. + // + if (ts == target_state::changed) + fail << "no recipe to " << diag_do (a, t) << + info << "prerequisite " << *pt << " is ahead of " << t + << " because it was updated"; + } + } + + return target_state::unchanged; + } + + file_rule file_rule::instance; + + // alias_rule + // + match_result alias_rule:: + match (action, target& t, const string&) const + { + return t; + } + + recipe alias_rule:: + apply (action a, target& t, const match_result&) const + { + search_and_match_prerequisites (a, t); + return default_recipe; + } + + alias_rule alias_rule::instance; + + // fsdir_rule + // + match_result fsdir_rule:: + match (action, target& t, const string&) const + { + return t; + } + + recipe fsdir_rule:: + apply (action a, target& t, const match_result&) const + { + // Inject dependency on the parent directory. Note that we + // don't do it for clean since we shouldn't be removing it. + // + if (a.operation () != clean_id) + inject_parent_fsdir (a, t); + + search_and_match_prerequisites (a, t); + + switch (a) + { + case perform_update_id: return &perform_update; + case perform_clean_id: return &perform_clean; + default: assert (false); return default_recipe; + } + } + + target_state fsdir_rule:: + perform_update (action a, target& t) + { + target_state ts (target_state::unchanged); + + // First update prerequisites (e.g. create parent directories) + // then create this directory. + // + if (!t.prerequisite_targets.empty ()) + ts = execute_prerequisites (a, t); + + const dir_path& d (t.dir); // Everything is in t.dir. + + // Generally, it is probably correct to assume that in the majority + // of cases the directory will already exist. If so, then we are + // going to get better performance by first checking if it indeed + // exists. See try_mkdir() for details. + // + if (!dir_exists (d)) + { + if (verb >= 2) + text << "mkdir " << d; + else if (verb) + text << "mkdir " << t; + + try + { + try_mkdir (d); + } + catch (const system_error& e) + { + fail << "unable to create directory " << d << ": " << e.what (); + } + + ts |= target_state::changed; + } + + return ts; + } + + target_state fsdir_rule:: + perform_clean (action a, target& t) + { + // The reverse order of update: first delete this directory, + // then clean prerequisites (e.g., delete parent directories). + // + // Don't fail if we couldn't remove the directory because it + // is not empty (or is current working directory). In this + // case rmdir() will issue a warning when appropriate. + // + target_state ts (rmdir (t.dir, t) + ? target_state::changed + : target_state::unchanged); + + if (!t.prerequisite_targets.empty ()) + ts |= reverse_execute_prerequisites (a, t); + + return ts; + } + + fsdir_rule fsdir_rule::instance; + + // fallback_rule + // + fallback_rule fallback_rule::instance; +} diff --git a/build2/scope b/build2/scope new file mode 100644 index 0000000..830436c --- /dev/null +++ b/build2/scope @@ -0,0 +1,312 @@ +// file : build2/scope -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_SCOPE +#define BUILD2_SCOPE + +#include // function +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace build2 +{ + class scope + { + public: + // Absolute and normalized. + // + const dir_path& + out_path () const {return *out_path_;} + + const dir_path& + src_path () const {return *src_path_;} + + // These are pointers to the keys in scope_map. + // + const dir_path* out_path_ {nullptr}; + const dir_path* src_path_ {nullptr}; + + bool + root () const {return root_ == this;} + + scope* + parent_scope () const {return parent_;} + + // Root scope of this scope or NULL if this scope is not (yet) + // in any (known) project. Note that if the scope itself is + // root, then this function return this. To get to the outer + // root, query the root scope of the parent. + // + scope* + root_scope () const {return root_;} + + // Root scope of a strong amalgamation of this scope or NULL if + // this scope is not (yet) in any (known) project. If there is + // no strong amalgamation, then this function returns the root + // scope of the project (in other words, in this case a project + // is treated as its own strong amalgamation). + // + scope* + strong_scope () const + { + return root_ != nullptr + ? root_->strong_ != nullptr ? root_->strong_ : root_ + : nullptr; + } + + // Root scope of the outermost amalgamation or NULL if this scope is not + // (yet) in any (known) project. If there is no amalgamation, then this + // function returns the root scope of the project (in other words, in this + // case a project is treated as its own amalgamation). + // + scope* + weak_scope () const + { + scope* r (root_); + if (r != nullptr) + for (; r->parent_->root_ != nullptr; r = r->parent_->root_) ; + return r; + } + + // Variables. + // + public: + variable_map vars; + + // Lookup, including in outer scopes. If you only want to lookup + // in this scope, do it on the the variables map directly. + // + build2::lookup + operator[] (const variable& var) const + { + return lookup (nullptr, nullptr, var); + } + + build2::lookup + operator[] (const std::string& name) const + { + return operator[] (var_pool.find (name)); + } + + // As above, but includes target type/pattern-specific variables. + // + build2::lookup + lookup (const target_key& tk, const variable& var) const + { + return lookup (tk.type, tk.name, var); + } + + build2::lookup + lookup (const target_key& tk, const string& var) const + { + return lookup (tk, var_pool.find (var)); + } + + build2::lookup + lookup (const target_type& tt, + const string& name, + const variable& var) const + { + return lookup (&tt, &name, var); + } + + build2::lookup + lookup (const target_type& tt, const string& name, const string& var) const + { + return lookup (tt, name, var_pool.find (var)); + } + + build2::lookup + lookup (const target_type*, const string* name, const variable&) const; + + // Return a value suitable for assignment (or append if you only + // want to append to the value from this scope). If the variable + // does not exist in this scope's map, then a new one with the + // NULL value is added and returned. Otherwise the existing value + // is returned. + // + value& + assign (const variable& var) {return vars.assign (var).first.get ();} + + value& + assign (const std::string& name) {return vars.assign (name).first.get ();} + + // Return a value suitable for appending. If the variable does not + // exist in this scope's map, then outer scopes are searched for + // the same variable. If found then a new variable with the found + // value is added to this scope and returned. Otherwise this + // function proceeds as assign(). + // + value& + append (const variable&); + + value& + append (const std::string& name) + { + return append (var_pool.find (name)); + } + + // Target type/pattern-specific variables. + // + variable_type_map target_vars; + + // Prerequisite cache. + // + public: + prerequisite_set prerequisites; + + // Meta/operations supported by this project (set on the root + // scope only). + // + build2::meta_operations meta_operations; + build2::operations operations; + + typedef build2::path path_type; + + // Set of buildfiles already loaded for this scope. The included + // buildfiles are checked against the project's root scope while + // imported -- against the global scope (global_scope). + // + std::unordered_set buildfiles; + + // Target types. + // + public: + target_type_map target_types; + + const target_type* + find_target_type (const string&, const scope** = nullptr) const; + + // Given a name, figure out its type, taking into account extensions, + // special names (e.g., '.' and '..'), or anything else that might be + // relevant. Also process the name (in place) by extracting the + // extension, adjusting dir/value, etc., (note that the dir is not + // necessarily normalized). Return NULL if not found. + // + const target_type* + find_target_type (name&, const string*& ext) const; + + // Rules. + // + public: + rule_map rules; + + // Modules. + // + public: + loaded_module_map modules; // Only on root scope. + + public: + bool + empty () const + { + return + vars.empty () && + target_vars.empty () && + prerequisites.empty () && + meta_operations.empty () && + operations.empty () && + buildfiles.empty () && + target_types.empty () && + rules.empty () && + modules.empty (); + } + + private: + friend class scope_map; + friend class temp_scope; + + // These two from set strong_. + // + friend void create_bootstrap_outer (scope&); + friend scope& create_bootstrap_inner (scope&, const dir_path&); + + scope () = default; + + scope* parent_; + scope* root_; + scope* strong_ = nullptr; // Only set on root sopes. + // NULL means no strong amalgamtion. + }; + + // Temporary scope. The idea is to be able to create a temporary + // scope in order not to change the variables in the current scope. + // Such a scope is not entered in to the scope map. As a result it + // can only be used as a temporary set of variables. In particular, + // defining targets/prerequisites directly in such a scope will surely + // end up badly. Defining any nested scopes will be as if defining + // such a scope in the parent (since path() returns parent's path). + // + class temp_scope: public scope + { + public: + temp_scope (scope& p) + { + out_path_ = p.out_path_; + src_path_ = p.src_path_; + parent_ = &p; + root_ = p.root_; + // No need to copy strong_ since we are never root scope. + } + }; + + class scope_map + { + public: + using map_type = butl::dir_path_map; + using iterator = map_type::iterator; + using const_iterator = map_type::const_iterator; + + // Note that we assume the first insertion into the map is that + // of the global scope. If the passed scope pointer is not NULL, + // then insert this scope instead of a new one. + // + iterator + insert (const dir_path&, scope*, bool parent, bool root); + + // Find the most qualified scope that encompasses this path. + // + scope& + find (const dir_path&) const; + + scope& + find (const path& p) const + { + // Natural thing to do here would be to call find (p.directory ()). + // However, there could be a situation where the passed path is a + // directory (i.e., the calling code does not know what it is dealing + // with), so let's use the whole path. + // + return find (dir_path (p.string ())); + } + + const_iterator begin () const {return map_.begin ();} + const_iterator end () const {return map_.end ();} + + void + clear (); + + ~scope_map () {clear ();} + + private: + map_type map_; + }; + + extern scope_map scopes; + extern scope* global_scope; +} + +#endif // BUILD2_SCOPE diff --git a/build2/scope.cxx b/build2/scope.cxx new file mode 100644 index 0000000..4d95a95 --- /dev/null +++ b/build2/scope.cxx @@ -0,0 +1,317 @@ +// file : build2/scope.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include + +using namespace std; + +namespace build2 +{ + // scope + // + lookup scope:: + lookup (const target_type* tt, const string* name, const variable& var) const + { + using result = build2::lookup; + + for (const scope* s (this); s != nullptr; ) + { + if (tt != nullptr && !s->target_vars.empty ()) + { + if (auto l = s->target_vars.lookup (*tt, *name, var)) + return l; + } + + if (auto r = s->vars.find (var)) + return result (r, &s->vars); + + switch (var.visibility) + { + case variable_visibility::scope: + s = nullptr; + break; + case variable_visibility::project: + s = s->root () ? nullptr : s->parent_scope (); + break; + case variable_visibility::normal: + s = s->parent_scope (); + break; + } + } + + return result (); + } + + value& scope:: + append (const variable& var) + { + auto l (operator[] (var)); + + if (l && l.belongs (*this)) // Existing variable in this scope. + return const_cast (*l); + + value& r (assign (var)); + + if (l) + r = *l; // Copy value from the outer scope. + + return r; + } + + const target_type* scope:: + find_target_type (const string& tt, const scope** rs) const + { + // Search scopes outwards, stopping at the project root. + // + for (const scope* s (this); + s != nullptr; + s = s->root () ? global_scope : s->parent_scope ()) + { + if (s->target_types.empty ()) + continue; + + auto i (s->target_types.find (tt)); + + if (i != s->target_types.end ()) + { + if (rs != nullptr) + *rs = s; + + return &i->second.get (); + } + } + + return nullptr; + } + + static const string dir_tt ("dir"); + static const string file_tt ("file"); + + const target_type* scope:: + find_target_type (name& n, const string*& ext) const + { + ext = nullptr; + + string& v (n.value); + + // First determine the target type. + // + const string* tt; + if (n.untyped ()) + { + // Empty name or '.' and '..' signify a directory. + // + if (v.empty () || v == "." || v == "..") + tt = &dir_tt; + else + //@@ TODO: derive type from extension. + // + tt = &file_tt; + } + else + tt = &n.type; + + const target_type* r (find_target_type (*tt)); + + if (r == nullptr) + return r; + + // Directories require special name processing. If we find that more + // targets deviate, then we should make this target-type-specific. + // + if (r->is_a () || r->is_a ()) + { + // The canonical representation of a directory name is with empty + // value. + // + if (!v.empty ()) + { + n.dir /= dir_path (v); // Move name value to dir. + v.clear (); + } + } + else + { + // Split the path into its directory part (if any) the name part, + // and the extension (if any). We cannot assume the name part is + // a valid filesystem name so we will have to do the splitting + // manually. + // + path::size_type i (path::traits::rfind_separator (v)); + + if (i != string::npos) + { + n.dir /= dir_path (v, i != 0 ? i : 1); // Special case: "/". + v = string (v, i + 1, string::npos); + } + + // Extract the extension. + // + string::size_type j (path::traits::find_extension (v)); + + if (j != string::npos) + { + ext = &extension_pool.find (v.c_str () + j + 1); + v.resize (j); + } + } + + return r; + } + + // scope_map + // + scope_map scopes; + scope* global_scope; + + auto scope_map:: + insert (const dir_path& k, scope* ns, bool parent, bool root) -> iterator + { + auto er (map_.emplace (k, nullptr)); + scope*& ps (er.first->second); + + if (er.second) + ps = ns == nullptr ? new scope : ns; + else if (ns != nullptr && ps != ns) + { + assert (ps->out_path_ == nullptr || ps->src_path_ == nullptr); + + if (!ps->empty ()) + fail << "attempt to replace non-empty scope " << k; + + // Un-parent ourselves. We will becomes a new parent below, + // if requested by the caller. + // + auto r (map_.find_prefix (k)); // The first entry is ourselves. + for (++r.first; r.first != r.second; ++r.first) + { + scope& c (*r.first->second); + + if (c.parent_ == ps) // No intermediate parent. + c.parent_ = ps->parent_; + } + + delete ps; + ps = ns; + er.second = true; + } + + scope& s (*ps); + + if (parent) + { + if (er.second) + { + scope* p (nullptr); + + // Update scopes of which we are a new parent/root (unless this + // is the global scope). Also find our parent while at it. + // + if (map_.size () > 1) + { + // The first entry is ourselves. + // + auto r (map_.find_prefix (k)); + for (++r.first; r.first != r.second; ++r.first) + { + scope& c (*r.first->second); + + // The child-parent relationship is based on the out hierarchy, + // thus the extra check. + // + if (c.out_path_ != nullptr && !c.out_path_->sub (k)) + continue; + + // The first scope of which we are a parent is the least + // (shortest) one which means there is no other scope + // between it and our parent. + // + if (p == nullptr) + p = c.parent_; + + if (root && c.root_ == p->root_) // No intermediate root. + c.root_ = &s; + + if (p == c.parent_) // No intermediate parent. + c.parent_ = &s; + } + + // We couldn't get the parent from one of its old children + // so we have to find it ourselves. + // + if (p == nullptr) + p = &find (k.directory ()); + } + + s.parent_ = p; + s.root_ = root ? &s : (p != nullptr ? p->root_ : nullptr); + } + else if (root && !s.root ()) + { + // Upgrade to root scope. + // + auto r (map_.find_prefix (k)); + for (++r.first; r.first != r.second; ++r.first) + { + scope& c (*r.first->second); + + if (c.root_ == s.root_) // No intermediate root. + c.root_ = &s; + } + + s.root_ = &s; + } + } + else + assert (s.parent_ != nullptr); + + return er.first; + } + + // Find the most qualified scope that encompasses this path. + // + scope& scope_map:: + find (const dir_path& k) const + { + // Normally we would have a scope for the full path so try + // that before making any copies. + // + auto i (map_.find (k)), e (map_.end ()); + + if (i != e) + return *i->second; + + for (dir_path d (k.directory ());; d = d.directory ()) + { + auto i (map_.find (d)); + + if (i != e) + return *i->second; + + assert (!d.empty ()); // We should have the global scope. + } + } + + void scope_map:: + clear () + { + for (auto& p: map_) + { + scope* s (p.second); + + if (s->out_path_ == &p.first) + s->out_path_ = nullptr; + + if (s->src_path_ == &p.first) + s->src_path_ = nullptr; + + if (s->out_path_ == nullptr && s->src_path_ == nullptr) + delete s; + } + + map_.clear (); + } +} diff --git a/build2/search b/build2/search new file mode 100644 index 0000000..f5db9eb --- /dev/null +++ b/build2/search @@ -0,0 +1,31 @@ +// file : build2/search -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_SEARCH +#define BUILD2_SEARCH + +#include + +namespace build2 +{ + class target; + class prerequisite_key; + + // Search for an existing target in this prerequisite's scope. + // + target* + search_existing_target (const prerequisite_key&); + + // Search for an existing file in the specified list of search paths. + // + target* + search_existing_file (const prerequisite_key&, const dir_paths&); + + // Create a new target in this prerequisite's scope. + // + target& + create_new_target (const prerequisite_key&); +} + +#endif // BUILD2_SEARCH diff --git a/build2/search.cxx b/build2/search.cxx new file mode 100644 index 0000000..58384f2 --- /dev/null +++ b/build2/search.cxx @@ -0,0 +1,171 @@ +// file : build2/search.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include // move() +#include + +#include + +#include +#include +#include +#include + +using namespace std; +using namespace butl; + +namespace build2 +{ + target* + search_existing_target (const prerequisite_key& pk) + { + tracer trace ("search_existing_target"); + + const target_key& tk (pk.tk); + + // Look for an existing target in this directory scope. + // + dir_path d; + if (tk.dir->absolute ()) + d = *tk.dir; // Already normalized. + else + { + d = pk.scope->out_path (); + + if (!tk.dir->empty ()) + { + d /= *tk.dir; + d.normalize (); + } + } + + auto i (targets.find (*tk.type, d, *tk.name, *tk.ext, trace)); + + if (i == targets.end ()) + return 0; + + target& t (**i); + + level5 ([&]{trace << "existing target " << t << " for prerequisite " + << pk;}); + + return &t; + } + + target* + search_existing_file (const prerequisite_key& cpk, const dir_paths& sp) + { + tracer trace ("search_existing_file"); + + prerequisite_key pk (cpk); // Make a copy so we can update extension. + target_key& tk (pk.tk); + assert (tk.dir->relative ()); + + // Figure out the extension. Pretty similar logic to file::derive_path(). + // + const string* ext (*tk.ext); + + if (ext == nullptr) + { + if (auto f = tk.type->extension) + { + ext = &f (tk, *pk.scope); // Already from the pool. + tk.ext = &ext; + } + else + { + // What should we do here, fail or say we didn't find anything? + // Current think is that if the target type didn't provide the + // default extension, then it doesn't want us to search for an + // existing file (of course, if the user specified the extension + // explicitly, we will still do so). But let me know what you + // think. + // + //fail << "no default extension for prerequisite " << pk; + level4 ([&]{trace << "no existing file found for prerequisite " + << pk;}); + return nullptr; + } + } + + // Go over paths looking for a file. + // + for (const dir_path& d: sp) + { + path f (d / *tk.dir / path (*tk.name)); + f.normalize (); + + if (!ext->empty ()) + { + f += '.'; + f += *ext; + } + + timestamp mt (file_mtime (f)); + + if (mt == timestamp_nonexistent) + continue; + + level5 ([&]{trace << "found existing file " << f << " for prerequisite " + << pk;}); + + // Find or insert. Note: using our updated extension. + // + auto r (targets.insert (*tk.type, f.directory (), *tk.name, ext, trace)); + + // Has to be a file_target. + // + file& t (dynamic_cast (r.first)); + + level5 ([&]{trace << (r.second ? "new" : "existing") << " target " + << t << " for prerequisite " << pk;}); + + if (t.path ().empty ()) + t.path (move (f)); + + t.mtime (mt); + return &t; + } + + level4 ([&]{trace << "no existing file found for prerequisite " << pk;}); + return nullptr; + } + + target& + create_new_target (const prerequisite_key& pk) + { + tracer trace ("create_new_target"); + + const target_key& tk (pk.tk); + + // We default to the target in this directory scope. + // + dir_path d; + if (tk.dir->absolute ()) + d = *tk.dir; // Already normalized. + else + { + d = pk.scope->out_path (); + + if (!tk.dir->empty ()) + { + d /= *tk.dir; + d.normalize (); + } + } + + // Find or insert. + // + auto r (targets.insert (*tk.type, move (d), *tk.name, *tk.ext, trace)); + assert (r.second); + + target& t (r.first); + + level5 ([&]{trace << "new target " << t << " for prerequisite " << pk;}); + + return t; + } +} diff --git a/build2/spec b/build2/spec new file mode 100644 index 0000000..fc981ed --- /dev/null +++ b/build2/spec @@ -0,0 +1,61 @@ +// file : build2/spec -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_SPEC +#define BUILD2_SPEC + +#include +#include +#include +#include // move() + +#include + +namespace build2 +{ + struct targetspec + { + typedef build2::name name_type; + + explicit + targetspec (name_type n): name (std::move (n)) {} + targetspec (dir_path sb, name_type n) + : src_base (std::move (sb)), name (std::move (n)) {} + + dir_path src_base; + name_type name; + }; + + struct opspec: std::vector + { + opspec () = default; + opspec (std::string n): name (std::move (n)) {} + + std::string name; + }; + + struct metaopspec: std::vector + { + metaopspec () = default; + metaopspec (std::string n): name (std::move (n)) {} + + std::string name; + }; + + typedef std::vector buildspec; + + std::ostream& + operator<< (std::ostream&, const targetspec&); + + std::ostream& + operator<< (std::ostream&, const opspec&); + + std::ostream& + operator<< (std::ostream&, const metaopspec&); + + std::ostream& + operator<< (std::ostream&, const buildspec&); +} + +#endif // BUILD2_SPEC diff --git a/build2/spec.cxx b/build2/spec.cxx new file mode 100644 index 0000000..d9a4e12 --- /dev/null +++ b/build2/spec.cxx @@ -0,0 +1,81 @@ +// file : build2/spec.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include + +#include +#include + +using namespace std; + +namespace build2 +{ + ostream& + operator<< (ostream& os, const targetspec& s) + { + if (!s.src_base.empty ()) + { + string d (diag_relative (s.src_base, false)); + + if (!d.empty ()) + os << d << '@'; + } + + os << s.name; + return os; + } + + ostream& + operator<< (ostream& os, const opspec& s) + { + bool hn (!s.name.empty ()); + bool ht (!s.empty ()); + + //os << s.name; + os << (hn ? "\"" : "") << s.name << (hn ? "\"" : ""); + + if (hn && ht) + os << '('; + + for (auto b (s.begin ()), i (b); i != s.end (); ++i) + os << (i != b ? " " : "") << *i; + + if (hn && ht) + os << ')'; + + return os; + } + + ostream& + operator<< (ostream& os, const metaopspec& s) + { + bool hn (!s.name.empty ()); + bool ho (!s.empty ()); + + //os << s.name; + os << (hn ? "\'" : "") << s.name << (hn ? "\'" : ""); + + if (hn && ho) + os << '('; + + for (auto b (s.begin ()), i (b); i != s.end (); ++i) + os << (i != b ? " " : "") << *i; + + if (hn && ho) + os << ')'; + + return os; + } + + ostream& + operator<< (ostream& os, const buildspec& s) + { + for (auto b (s.begin ()), i (b); i != s.end (); ++i) + os << (i != b ? " " : "") << *i; + + return os; + } +} diff --git a/build2/target b/build2/target new file mode 100644 index 0000000..19bec9e --- /dev/null +++ b/build2/target @@ -0,0 +1,1084 @@ +// file : build2/target -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_TARGET +#define BUILD2_TARGET + +#include +#include +#include +#include // unique_ptr +#include // size_t +#include // uint8_t +#include // reference_wrapper +#include +#include +#include // move(), forward(), declval() +#include +#include + +#include // reverse_iterate() +#include // map_iterator_adapter + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace build2 +{ + class scope; + class target; + + target& + search (prerequisite&); // From . + + // Target state. + // + enum class target_state: std::uint8_t + { + // The order of the enumerators is arranged so that their integral + // values indicate whether one "overrides" the other in the merge + // operator (see below). + // + unknown, + unchanged, + postponed, + changed, + failed, + group // Target's state is the group's state. + }; + + std::ostream& + operator<< (std::ostream&, target_state); + + inline target_state& + operator |= (target_state& l, target_state r) + { + if (static_cast (r) > static_cast (l)) + l = r; + + return l; + } + + // Recipe. + // + // The returned target state should be changed, unchanged, or + // postponed, though you shouldn't be returning postponed + // directly. If there is an error, then the recipe should + // throw rather than returning failed. + // + // The return value of the recipe is used to update the target + // state except if the state was manually set by the recipe to + // target_state::group. Note that in this case the returned by + // the recipe value is still used as the resulting target state + // so it should match the group's state. + // + using recipe_function = target_state (action, target&); + using recipe = std::function; + + // Commonly-used recipes. The default recipe executes the action on + // all the prerequisites in a loop, skipping ignored. Specifically, + // for actions with the "first" execution mode, it calls + // execute_prerequisites() while for those with the "last" mode -- + // reverse_execute_prerequisites(); see , + // for details. The group recipe call's the group's + // recipe. + // + extern const recipe empty_recipe; + extern const recipe noop_recipe; + extern const recipe default_recipe; + extern const recipe group_recipe; + + target_state + noop_action (action, target&); // Defined in . + + target_state + group_action (action, target&); // Defined in . + + // Prerequisite references as used in the target::prerequisites list + // below. + // + struct prerequisite_ref: std::reference_wrapper + { + typedef std::reference_wrapper base; + + using base::base; + + // Return true if this reference belongs to the target's prerequisite + // list. Note that this test only works if you use references to + // the container elements and the container hasn't been resized + // since such a reference was obtained. Normally this function is + // used when iterating over a combined prerequisites range (see + // group_prerequisites below). + // + bool + belongs (const target&) const; + }; + + // A view of target group members. + // + struct group_view + { + target* const* members; // NULL means not yet known. + std::size_t count; + }; + + // Target. + // + class target + { + public: + typedef build2::action action_type; + + virtual + ~target () = default; + + target (const target&) = delete; + target& operator= (const target&) = delete; + + target (dir_path d, std::string n, const std::string* e) + : dir (std::move (d)), name (std::move (n)), ext (e) {} + + // Reset the target before matching a rule for it. The + // default implementation clears prerequisite_targets. + // + virtual void + reset (action_type); + + const dir_path dir; // Absolute and normalized. + const std::string name; + const std::string* ext; // Extension, NULL means unspecified, + // empty means no extension. + + // Target group to which this target belongs, if any. Note that + // we assume that the group and all its members are in the same + // scope (for example, in variable lookup). We also don't support + // nested groups. + // + // The semantics of the interaction between the group and its + // members and what it means to, say, update the group, is + // unspecified and determined by the group's type. In particular, + // a group can be created out of member types that have no idea + // they are part of this group (e.g., cli.cxx{}). + // + // Normally, however, there are two kinds of groups: "alternatives" + // and "combination". In an alternatives group, normally one of the + // members is selected when the group is mentioned as a prerequisite + // with, perhaps, an exception for special rules, like aliases, where + // it makes more sense to treat the group as a whole. In this case we + // say that the rule "semantically recognizes" the group and picks + // some of its members. + // + // Updating an alternatives group as a whole can mean updating some + // subset of its members (e.g., lib{}). Or the group may not support + // this at all (e.g., obj{}). + // + // In a combination group, when a group is updated, normally all + // members are updates (and usually with a single command), though + // there could be some members that are omitted, depending on the + // configuration (e.g., an inline file not/being generated). When + // a combination group is mentioned as a prerequisite, the rule + // is usually interested in the individual members rather than + // the whole group. For example, a C++ compile rule would like to + // "see" the ?xx{} members when it gets a cli.cxx{} group. + // + // Which brings us to the group iteration mode. The target type + // contains a member called see_through that indicates whether the + // default iteration mode for the group should be "see through"; + // that is, whether we see the members or the group itself. For + // the iteration support itself, see the *_prerequisite_members() + // machinery below. + // + target* group {nullptr}; + + // You should not call this function directly; rather use + // resolve_group_members() from . + // + virtual group_view + group_members (action_type) const; + + target_key + key () const {return target_key {&type (), &dir, &name, &ext};} + + // Scoping. + // + public: + // Most qualified scope that contains this target. + // + scope& + base_scope () const; + + // Root scope of a project that contains this target. Note that + // a target can be out of any (known) project root in which case + // this function asserts. If you need to detect this situation, + // then use base_scope().root_scope() expression instead. + // + scope& + root_scope () const; + + // Root scope of a strong amalgamation that contains this target. + // The same notes as to root_scope() apply. + // + scope& + strong_scope () const {return *root_scope ().strong_scope ();} + + // Root scope of the outermost amalgamation that contains this target. + // The same notes as to root_scope() apply. + // + scope& + weak_scope () const {return *root_scope ().weak_scope ();} + + + bool + in (const scope& s) const + { + return + (s.out_path_ != nullptr && dir.sub (*s.out_path_)) || + (s.src_path_ != nullptr && dir.sub (*s.src_path_)); + } + + // Prerequisites. + // + public: + typedef std::vector prerequisites_type; + prerequisites_type prerequisites; + + // Targets to which prerequisites resolve for this recipe. Note + // that unlike prerequisite::target, these can be resolved to + // group members. NULL means the target should be skipped (or + // the rule may simply not add such a target to the list). + // + // Note also that it is possible the target can vary from + // action to action, just like recipes. We don't need to keep + // track of the action here since the targets will be updated + // if the recipe is updated, normally as part of rule::apply(). + // + typedef std::vector prerequisite_targets_type; + prerequisite_targets_type prerequisite_targets; + + // Check if there are any prerequisites, taking into account + // group prerequisites. + // + bool + has_prerequisites () const + { + return !prerequisites.empty () || + (group != nullptr && !group->prerequisites.empty ()); + } + + // Target-specific variables. + // + public: + variable_map vars; + + // Lookup, including in groups to which this target belongs and + // then in outer scopes (including target type/pattern-specific + // variables). If you only want to lookup in this target, do it + // on the variable map directly. + // + lookup + operator[] (const variable&) const; + + lookup + operator[] (const std::string& name) const + { + return operator[] (var_pool.find (name)); + } + + // Return a value suitable for assignment. See class scope for + // details. + // + value& + assign (const variable& var) {return vars.assign (var).first;} + + value& + assign (const std::string& name) {return vars.assign (name).first;} + + // Return a value suitable for appending. See class scope for + // details. + // + value& + append (const variable&); + + value& + append (const std::string& name) + { + return append (var_pool.find (name)); + } + + public: + target_state raw_state; + + target_state + state () const + { + return raw_state != target_state::group ? raw_state : group->raw_state; + } + + // Number of direct targets that depend on this target in the current + // action. It is incremented during the match phase and then decremented + // during execution, before running the recipe. As a result, the recipe + // can detect the last chance (i.e., last dependent) to execute the + // command (see also the first/last execution modes in ). + // + // Note that setting a new recipe (which happens when we match the rule + // and which in turn is triggered by the first dependent) clears this + // counter. However, if the previous action was the same as the current, + // then the existing recipe is reused. In this case, however, the counter + // should have been decremented to 0 naturally, as part of the previous + // action execution. + // + std::size_t dependents; + + public: + action_type action; // Action this recipe is for. + + public: + typedef build2::recipe recipe_type; + + const recipe_type& + recipe (action_type a) const {return a > action ? empty_recipe : recipe_;} + + void + recipe (action_type, recipe_type); + + // Target type info. + // + public: + template + T* + is_a () {return dynamic_cast (this);} + + template + const T* + is_a () const {return dynamic_cast (this);} + + // Dynamic derivation to support define. + // + const target_type* derived_type = nullptr; + + const target_type& + type () const + { + return derived_type != nullptr ? *derived_type : dynamic_type (); + } + + virtual const target_type& dynamic_type () const = 0; + static const target_type static_type; + + private: + recipe_type recipe_; + }; + + std::ostream& + operator<< (std::ostream&, const target&); + + // A "range" that presents the prerequisites of a group and one of + // its members as one continuous sequence, or, in other words, as + // if they were in a single container. The group's prerequisites + // come first followed by the member's. If you need to see them + // in the other direction, iterate in reverse, for example: + // + // for (prerequisite_ref& pr: group_prerequisites (t)) + // + // for (prerequisite_ref& pr: reverse_iterate (group_prerequisites (t)) + // + // Note that in this case the individual elements of each list will + // also be traversed in reverse, but that's what you usually want, + // anyway. + // + class group_prerequisites + { + public: + typedef target::prerequisites_type prerequisites_type; + + explicit + group_prerequisites (target& t): t_ (t) {} + + struct iterator + { + typedef prerequisites_type::iterator base_iterator; + + typedef base_iterator::value_type value_type; + typedef base_iterator::pointer pointer; + typedef base_iterator::reference reference; + typedef base_iterator::difference_type difference_type; + typedef std::bidirectional_iterator_tag iterator_category; + + iterator () {} + iterator (target* t, prerequisites_type* c, base_iterator i) + : t_ (t), c_ (c), i_ (i) {} + + iterator& + operator++ () + { + if (++i_ == c_->end () && c_ != &t_->prerequisites) + { + c_ = &t_->prerequisites; + i_ = c_->begin (); + } + return *this; + } + + iterator + operator++ (int) {iterator r (*this); operator++ (); return r;} + + iterator& + operator-- () + { + if (i_ == c_->begin () && c_ == &t_->prerequisites) + { + c_ = &t_->group->prerequisites; + i_ = c_->end (); + } + + --i_; + return *this; + } + + iterator + operator-- (int) {iterator r (*this); operator-- (); return r;} + + reference operator* () const {return *i_;} + pointer operator-> () const {return i_.operator -> ();} + + friend bool + operator== (const iterator& x, const iterator& y) + { + return x.t_ == y.t_ && x.c_ == y.c_ && x.i_ == y.i_; + } + + friend bool + operator!= (const iterator& x, const iterator& y) {return !(x == y);} + + private: + target* t_ {nullptr}; + prerequisites_type* c_ {nullptr}; + base_iterator i_; + }; + + typedef std::reverse_iterator reverse_iterator; + + iterator + begin () const + { + auto& c ((t_.group != nullptr && !t_.group->prerequisites.empty () + ? *t_.group : t_).prerequisites); + return iterator (&t_, &c, c.begin ()); + } + + iterator + end () const + { + auto& c (t_.prerequisites); + return iterator (&t_, &c, c.end ()); + } + + reverse_iterator + rbegin () const {return reverse_iterator (end ());} + + reverse_iterator + rend () const {return reverse_iterator (begin ());} + + std::size_t + size () const + { + return t_.prerequisites.size () + + (t_.group != nullptr ? t_.group->prerequisites.size () : 0); + } + + private: + target& t_; + }; + + // A member of a prerequisite. If 'target' is NULL, then this is the + // prerequisite itself. Otherwise, it is its member. In this case + // 'prerequisite' still refers to the prerequisite. + // + struct prerequisite_member + { + typedef build2::target target_type; + typedef build2::prerequisite prerequisite_type; + + prerequisite_ref& prerequisite; + target_type* target; + + template + bool + is_a () const + { + return target != nullptr + ? target->is_a () != nullptr + : prerequisite.get ().is_a (); + } + + prerequisite_key + key () const + { + return target != nullptr + ? prerequisite_key {prerequisite.get ().proj, target->key (), nullptr} + : prerequisite.get ().key (); + } + + const build2::target_type& + type () const + { + return target != nullptr ? target->type () : prerequisite.get ().type; + } + + const std::string& + name () const + { + return target != nullptr ? target->name : prerequisite.get ().name; + } + + const std::string* + proj () const + { + // Target cannot be project-qualified. + // + return target != nullptr ? nullptr : prerequisite.get ().proj; + } + + target_type& + search () const + { + return target != nullptr ? *target : build2::search (prerequisite); + } + + prerequisite_type& + as_prerequisite (tracer&) const; + }; + + inline std::ostream& + operator<< (std::ostream& os, const prerequisite_member& pm) + { + return os << pm.key (); + } + + // A "range" that presents a sequence of prerequisites (e.g., from + // group_prerequisites()) as a sequence of prerequisite_member's. For + // each group prerequisite you will "see" either the prerequisite + // itself or all its members, depending on the default iteration + // mode of the target group type. You can skip the rest of the + // group members with leave_group() and you can force iteration + // over the members with enter_group(). Usage: + // + // for (prerequisite_member pm: prerequisite_members (a, ...)) + // + // Where ... can be: + // + // t.prerequisites + // reverse_iterate(t.prerequisites) + // group_prerequisites (t) + // reverse_iterate (group_prerequisites (t)) + // + // But use shortcuts instead: + // + // prerequisite_members (a, t) + // reverse_prerequisite_members (a, t) + // group_prerequisite_members (a, t) + // reverse_group_prerequisite_members (a, t) + // + template + class prerequisite_members_range; + + template + inline prerequisite_members_range + prerequisite_members (action a, T&& x, bool members = true) + { + return prerequisite_members_range (a, std::forward (x), members); + } + + template + class prerequisite_members_range + { + public: + prerequisite_members_range (action a, T&& r, bool m) + : a_ (a), members_ (m), r_ (std::forward (r)), e_ (r_.end ()) {} + + using base_iterator = decltype (std::declval ().begin ()); + + struct iterator + { + typedef prerequisite_member value_type; + typedef const value_type* pointer; + typedef const value_type& reference; + typedef typename base_iterator::difference_type difference_type; + typedef std::forward_iterator_tag iterator_category; + + iterator (): r_ (nullptr) {} + iterator (const prerequisite_members_range* r, const base_iterator& i) + : r_ (r), i_ (i), g_ {nullptr, 0} + { + if (r_->members_ && i_ != r_->e_ && i_->get ().type.see_through) + { + bool r (switch_members ()); + assert (r); // Group could not be resolved. + } + } + + iterator& operator++ (); + iterator operator++ (int) {iterator r (*this); operator++ (); return r;} + + // Skip iterating over the rest of this group's members, if any. + // Note that the only valid operation after this call is to + // increment the iterator. + // + void + leave_group () + { + // Pretend we are on the last member of some group. + // + j_ = 0; + g_.count = 1; + } + + // Iterate over this group's members. Return false if the member + // information is not available. Similar to leave_group(), you + // should increment the iterator after calling this function + // (provided it returned true). + // + bool + enter_group () + { + bool r (switch_members ()); + if (r) + --j_; // Compensate for the increment that will follow. + return r; + } + + value_type operator* () const + { + return value_type {*i_, g_.count != 0 ? g_.members[j_ - 1] : nullptr}; + } + + pointer operator-> () const + { + static_assert ( + std::is_trivially_destructible::value, + "prerequisite_member is not trivially destructible"); + + return new (&m_) + value_type {*i_, g_.count != 0 ? g_.members[j_ - 1] : nullptr}; + } + + friend bool + operator== (const iterator& x, const iterator& y) + { + return x.i_ == y.i_ && + x.g_.count == y.g_.count && + (x.g_.count == 0 || x.j_ == y.j_); + } + + friend bool + operator!= (const iterator& x, const iterator& y) {return !(x == y);} + + private: + bool + switch_members (); + + private: + const prerequisite_members_range* r_; + base_iterator i_; + group_view g_; + std::size_t j_; // 1-based index, to support enter_group(). + mutable std::aligned_storage::type m_; + }; + + iterator + begin () const {return iterator (this, r_.begin ());} + + iterator + end () const {return iterator (this, e_);} + + private: + action a_; + bool members_; // Go into group members by default? + T r_; + base_iterator e_; + }; + + // prerequisite_members(t.prerequisites) + // + inline auto + prerequisite_members (action a, target& t, bool members = true) + { + return prerequisite_members (a, t.prerequisites, members); + } + + // prerequisite_members(reverse_iterate(t.prerequisites)) + // + inline auto + reverse_prerequisite_members (action a, target& t, bool members = true) + { + return prerequisite_members ( + a, butl::reverse_iterate (t.prerequisites), members); + } + + // prerequisite_members(group_prerequisites (t)) + // + inline auto + group_prerequisite_members (action a, target& t, bool members = true) + { + return prerequisite_members (a, group_prerequisites (t), members); + } + + // prerequisite_members(reverse_iterate (group_prerequisites (t))) + // + inline auto + reverse_group_prerequisite_members (action a, target& t, bool members = true) + { + return prerequisite_members ( + a, butl::reverse_iterate (group_prerequisites (t)), members); + } + + // + // + struct target_set + { + typedef std::map> map; + typedef butl::map_iterator_adapter iterator; + + iterator + find (const target_key& k, tracer& trace) const; + + iterator + find (const target_type& type, + const dir_path& dir, + const std::string& name, + const std::string* ext, + tracer& trace) const + { + return find (target_key {&type, &dir, &name, &ext}, trace); + } + + // As above but ignore the extension and return the target or + // nullptr instead of the iterator. + // + template + T* + find (const dir_path& dir, const std::string& name) const + { + const std::string* e (nullptr); + auto i (map_.find (target_key {&T::static_type, &dir, &name, &e})); + return i != map_.end () ? static_cast (i->second.get ()) : nullptr; + } + + iterator begin () const {return map_.begin ();} + iterator end () const {return map_.end ();} + + std::pair + insert (const target_type&, + dir_path dir, + std::string name, + const std::string* ext, + tracer&); + + template + T& + insert (const dir_path& dir, + const std::string& name, + const std::string* ext, + tracer& t) + { + return static_cast ( + insert (T::static_type, dir, name, ext, t).first); + } + + template + T& + insert (const dir_path& dir, const std::string& name, tracer& t) + { + return static_cast ( + insert (T::static_type, dir, name, nullptr, t).first); + } + + void + clear () {map_.clear ();} + + private: + map map_; + }; + + extern target_set targets; + + // Modification time-based target. + // + class mtime_target: public target + { + public: + using target::target; + + // Generally, modification time for a target can only be queried + // after a rule has been matched since that's where the path is + // normally gets assigned. Normally, however, it would make sense + // to first execute the rule to get the "updated" timestamp. + // + // The rule for groups that utilize the group state is as follows: + // if it has any members that are mtime_targets, then the group + // should be mtime_target and the members get the mtime from it. + // + timestamp + mtime () const + { + const mtime_target* t (raw_state == target_state::group + ? static_cast (group) + : this); + + if (t->mtime_ == timestamp_unknown) + t->mtime_ = t->load_mtime (); + + return t->mtime_; + } + + void + mtime (timestamp mt) + { + // While we can cache the mtime at any time, it may be ignored + // if the target state is group (see the mtime() accessor). + // + mtime_ = mt; + } + + protected: + virtual timestamp + load_mtime () const = 0; + + public: + static const target_type static_type; + + private: + mutable timestamp mtime_ {timestamp_unknown}; + }; + + // Filesystem path-based target. + // + class path_target: public mtime_target + { + public: + using mtime_target::mtime_target; + + typedef build2::path path_type; + + const path_type& + path () const {return path_;} + + void + path (path_type p) {assert (path_.empty ()); path_ = std::move (p);} + + // Derive a path from target's dir, name, and, if specified, ext. + // If ext is not specified, then use default_ext and also update + // the target's extension (this becomes important if later we need + // to reliably determine whether this file has an extension; think + // hxx{foo.bar.} and hxx.ext is empty). + // + // If name_prefix is not NULL, add it before the name part and after + // the directory. Similarly, if name_suffix is not NULL, add it after + // the name part and before the extension. + // + // Finally, if the path was already assigned to this target, then + // this function verifies that the two are the same. + // + void + derive_path (const char* default_ext = nullptr, + const char* name_prefix = nullptr, + const char* name_suffix = nullptr); + + public: + static const target_type static_type; + + private: + path_type path_; + }; + + // File target. + // + class file: public path_target + { + public: + using path_target::path_target; + + protected: + // Note that it is final in order to be consistent with file_rule, + // search_existing_file(). + // + virtual timestamp + load_mtime () const final; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + + // Alias target. It represents a list of targets (its prerequisites) + // as a single "name". + // + class alias: public target + { + public: + using target::target; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + + // Directory target. Note that this is not a filesystem directory + // but rather an alias target with the directory name. For actual + // filesystem directory (creation), see fsdir. + // + class dir: public alias + { + public: + using alias::alias; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + + // While a filesystem directory is mtime-based, the semantics is + // not very useful in our case. In particular, if another target + // depends on fsdir{}, then all that's desired is the creation of + // the directory if it doesn't already exist. In particular, we + // don't want to update the target just because some unrelated + // entry was created in that directory. + // + class fsdir: public target + { + public: + using target::target; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + + class buildfile: public file + { + public: + using file::file; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + + // Common documentation file targets. + // + // @@ Maybe these should be in the built-in doc module? + // + class doc: public file + { + public: + using file::file; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + + // The problem with man pages is this: different platforms have + // different sets of sections. What seems to be the "sane" set + // is 1-9 (Linux and BSDs). SysV (e.g., Solaris) instead maps + // 8 to 1M (system administration). The section determines two + // things: the directory where the page is installed (e.g., + // /usr/share/man/man1) as well as the extension of the file + // (e.g., test.1). Note also that there could be sub-sections, + // e.g., 1p (for POSIX). Such a page would still go into man1 + // but will have the .1p extension (at least that's what happens + // on Linux). The challenge is to somehow handle this in a + // portable manner. So here is the plan: + // + // First of all, we have the man{} target type which can be used + // for a custom man page. That is, you can have any extension and + // install it anywhere you please: + // + // man{foo.X}: install = man/manX + // + // Then we have man1..9{} target types which model the "sane" + // section set and that would be automatically installed into + // correct locations on other platforms. In other words, the + // idea is that you should be able to have the foo.8 file, + // write man8{foo} and have it installed as man1m/foo.1m on + // some SysV host. + // + // Re-mapping the installation directory is easy: to help with + // that we have assigned install.man1..9 directory names. The + // messy part is to change the extension. It seems the only + // way to do that would be to have special logic for man pages + // in the generic install rule. @@ This is still a TODO. + // + // Note that handling subsections with man1..9{} is easy, we + // simply specify the extension explicitly, e.g., man{foo.1p}. + // + class man: public doc + { + public: + using doc::doc; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + + class man1: public man + { + public: + using man::man; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + + // Common implementation of the target factory, extension, and + // search functions. + // + template + target* + target_factory (const target_type&, dir_path d, string n, const string* e) + { + return new T (move (d), move (n), e); + } + + // Return fixed target extension. + // + template + const std::string& + target_extension_fix (const target_key&, scope&); + + // Get the extension from the variable or use the default if none set. + // Issue diagnostics and fail if the default is NULL. + // + template + const std::string& + target_extension_var (const target_key&, scope&); + + // The default behavior, that is, look for an existing target in the + // prerequisite's directory scope. + // + target* + search_target (const prerequisite_key&); + + // First look for an existing target as above. If not found, then look + // for an existing file in the target-type-specific list of paths. + // + target* + search_file (const prerequisite_key&); + +} + +#include +#include + +#endif // BUILD2_TARGET diff --git a/build2/target-key b/build2/target-key new file mode 100644 index 0000000..cd8e270 --- /dev/null +++ b/build2/target-key @@ -0,0 +1,55 @@ +// file : build2/target-key -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_TARGET_KEY +#define BUILD2_TARGET_KEY + +#include +#include +#include +#include // reference_wrapper + +#include // compare_c_string + +#include +#include + +namespace build2 +{ + // Light-weight (by being shallow-pointing) target key. + // + class target_key + { + public: + mutable const target_type* type; + mutable const dir_path* dir; + mutable const std::string* name; + mutable const std::string* const* ext; // Note: only *ext can be NULL. + + friend bool + operator< (const target_key& x, const target_key& y) + { + const target_type* xt (x.type); + const target_type* yt (y.type); + + //@@ TODO: use compare() to compare once. + + // Unspecified and specified extension are assumed equal. The + // extension strings are from the pool, so we can just compare + // pointers. + // + return + (xt < yt) || + (xt == yt && *x.name < *y.name) || + (xt == yt && *x.name == *y.name && *x.dir < *y.dir) || + (xt == yt && *x.name == *y.name && *x.dir == *y.dir && + *x.ext != nullptr && *y.ext != nullptr && **x.ext < **y.ext); + } + }; + + std::ostream& + operator<< (std::ostream&, const target_key&); // Defined in target.cxx +} + +#endif // BUILD2_TARGET_KEY diff --git a/build2/target-type b/build2/target-type new file mode 100644 index 0000000..52a45f4 --- /dev/null +++ b/build2/target-type @@ -0,0 +1,97 @@ +// file : build2/target-type -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_TARGET_TYPE +#define BUILD2_TARGET_TYPE + +#include +#include +#include + +#include + +namespace build2 +{ + class scope; + class target; + class target_key; + class prerequisite_key; + + // Target type. + // + // Note that we assume there is always a single instance of this class + // for any target type. As a result, we can use address comparison to + // determine if two target types are the same. + // + // + struct target_type + { + const char* name; + const target_type* base; + target* (*factory) (const target_type&, dir_path, string, const string*); + const string& (*extension) (const target_key&, scope&); + target* (*search) (const prerequisite_key&); + bool see_through; // A group with the default "see through" semantics. + + bool + is_a (const target_type&) const; // Defined in target.cxx + + template + bool + is_a () const {return is_a (T::static_type);} + }; + + inline bool + operator< (const target_type& x, const target_type& y) {return &x < &y;} + + inline bool + operator== (const target_type& x, const target_type& y) {return &x == &y;} + + inline bool + operator!= (const target_type& x, const target_type& y) {return &x != &y;} + + inline std::ostream& + operator<< (std::ostream& os, const target_type& tt) {return os << tt.name;} + + // Target type map. + // + struct target_type_ref + { + // Like reference_wrapper except it sometimes deletes the target type. + // + explicit + target_type_ref (const target_type& r): p_ (&r), d_ (false) {} + + explicit + target_type_ref (unique_ptr&& p) + : p_ (p.release ()), d_ (true) {} + + target_type_ref (target_type_ref&& r) + : p_ (r.p_), d_ (r.d_) {r.p_ = nullptr;} + + ~target_type_ref () {if (p_ != nullptr && d_) delete p_;} + + explicit operator const target_type& () const {return *p_;} + const target_type& get () const {return *p_;} + + private: + const target_type* p_; + bool d_; + }; + + using target_type_map_base = std::map; + + class target_type_map: public target_type_map_base + { + public: + void + insert (const target_type& tt) {emplace (tt.name, target_type_ref (tt));} + + template + void + insert () {insert (T::static_type);} + }; +} + +#endif // BUILD2_TARGET_TYPE diff --git a/build2/target.cxx b/build2/target.cxx new file mode 100644 index 0000000..3932466 --- /dev/null +++ b/build2/target.cxx @@ -0,0 +1,537 @@ +// file : build2/target.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include + +#include + +#include +#include +#include +#include + +using namespace std; + +namespace build2 +{ + // target_type + // + bool target_type:: + is_a (const target_type& tt) const + { + for (const target_type* p (this); p != nullptr; p = p->base) + if (*p == tt) + return true; + + return false; + } + + // target_state + // + static const char* target_state_[] = { + "unknown", "unchanged", "postponed", "changed", "failed", "group"}; + + ostream& + operator<< (ostream& os, target_state ts) + { + return os << target_state_[static_cast (ts)]; + } + + // recipe + // + const recipe empty_recipe; + const recipe noop_recipe (&noop_action); + const recipe default_recipe (&default_action); + const recipe group_recipe (&group_action); + + // target + // + + void target:: + recipe (action_type a, recipe_type r) + { + assert (a > action || !recipe_); + + bool override (a == action && recipe_); // See action::operator<. + + // Only noop_recipe can be overridden. + // + if (override) + { + recipe_function** f (recipe_.target ()); + assert (f != nullptr && *f == &noop_action); + } + + action = a; + recipe_ = std::move (r); + + // Also reset the target state. If this is a noop recipe, then + // mark the target unchanged so that we don't waste time executing + // the recipe. + // + raw_state = target_state::unknown; + + if (recipe_function** f = recipe_.target ()) + { + if (*f == &noop_action) + raw_state = target_state::unchanged; + } + + // This one is tricky: we don't want to reset the dependents count + // if we are merely overriding with a "stronger" recipe. + // + if (!override) + dependents = 0; + } + + void target:: + reset (action_type) + { + prerequisite_targets.clear (); + } + + group_view target:: + group_members (action_type) const + { + assert (false); // Not a group or doesn't expose its members. + return group_view {nullptr, 0}; + } + + scope& target:: + base_scope () const + { + return scopes.find (dir); + } + + scope& target:: + root_scope () const + { + // This is tricky to cache so we do the lookup for now. + // + scope* r (scopes.find (dir).root_scope ()); + assert (r != nullptr); + return *r; + } + + lookup target:: + operator[] (const variable& var) const + { + using result = lookup; + + if (auto p = vars.find (var)) + return result (p, &vars); + + if (group != nullptr) + { + if (auto p = group->vars.find (var)) + return result (p, &group->vars); + } + + // We cannot simply delegate to scope's lookup() since we also need + // to check the group. + // + for (const scope* s (&base_scope ()); s != nullptr; ) + { + if (!s->target_vars.empty ()) + { + if (auto l = s->target_vars.lookup (type (), name, var)) + return l; + + if (group != nullptr) + { + if (auto l = s->target_vars.lookup (group->type (), group->name, var)) + return l; + } + } + + if (auto r = s->vars.find (var)) + return result (r, &s->vars); + + switch (var.visibility) + { + case variable_visibility::scope: + s = nullptr; + break; + case variable_visibility::project: + s = s->root () ? nullptr : s->parent_scope (); + break; + case variable_visibility::normal: + s = s->parent_scope (); + break; + } + } + + return result (); + } + + value& target:: + append (const variable& var) + { + auto l (operator[] (var)); + + if (l && l.belongs (*this)) // Existing variable in this target. + return const_cast (*l); + + value& r (assign (var)); + + if (l) + r = *l; // Copy value from the outer scope. + + return r; + } + + ostream& + operator<< (ostream& os, const target& t) + { + return os << target_key {&t.type (), &t.dir, &t.name, &t.ext}; + } + + // target_set + // + target_set targets; + + auto target_set:: + find (const target_key& k, tracer& trace) const -> iterator + { + iterator i (map_.find (k)); + + if (i != end ()) + { + target& t (**i); + + // Update the extension if the existing target has it unspecified. + // + const string* ext (*k.ext); + if (t.ext != ext) + { + level5 ([&]{ + diag_record r (trace); + r << "assuming target " << t << " is the same as the one with "; + if (ext == nullptr) + r << "unspecified extension"; + else if (ext->empty ()) + r << "no extension"; + else + r << "extension " << *ext; + }); + + if (ext != nullptr) + t.ext = ext; + } + } + + return i; + } + + pair target_set:: + insert (const target_type& tt, + dir_path dir, + string name, + const string* ext, + tracer& trace) + { + iterator i (find (target_key {&tt, &dir, &name, &ext}, trace)); + bool r (i == end ()); + + if (r) + { + unique_ptr pt (tt.factory (tt, move (dir), move (name), ext)); + i = map_.emplace ( + make_pair (target_key {&tt, &pt->dir, &pt->name, &pt->ext}, + move (pt))).first; + } + + return pair (**i, r); + } + + ostream& + operator<< (ostream& os, const target_key& k) + { + // If the name is empty, then we want to print the directory + // inside {}, e.g., dir{bar/}, not bar/dir{}. + // + bool n (!k.name->empty ()); + string d (diag_relative (*k.dir, false)); + + if (n) + os << d; + + os << k.type->name << '{'; + + if (n) + { + os << *k.name; + + if (*k.ext != nullptr && !(*k.ext)->empty ()) + os << '.' << **k.ext; + } + else + os << d; + + os << '}'; + + return os; + } + + // path_target + // + void path_target:: + derive_path (const char* de, const char* np, const char* ns) + { + string n; + + if (np != nullptr) + n += np; + + n += name; + + if (ns != nullptr) + n += ns; + + // Update the extension. + // + // See also search_existing_file() if updating anything here. + // + if (ext == nullptr) + { + // If provided by the caller, then use that. + // + if (de != nullptr) + ext = &extension_pool.find (de); + // + // Otherwis see if the target type has function that will + // give us the default extension. + // + else if (auto f = type ().extension) + ext = &f (key (), base_scope ()); // Already from the pool. + else + fail << "no default extension for target " << *this; + } + + // Add the extension. + // + if (!ext->empty ()) + { + n += '.'; + n += *ext; + } + + path_type p (dir / path_type (move (n))); + const path_type& ep (path ()); + + if (ep.empty ()) + path (p); + else if (p != ep) + fail << "path mismatch for target " << *this << + info << "assigned '" << ep << "'" << + info << "derived '" << p << "'"; + } + + // file_target + // + timestamp file:: + load_mtime () const + { + const path_type& f (path ()); + assert (!f.empty ()); + return file_mtime (f); + } + + // Search functions. + // + + target* + search_target (const prerequisite_key& pk) + { + // The default behavior is to look for an existing target in the + // prerequisite's directory scope. + // + return search_existing_target (pk); + } + + target* + search_file (const prerequisite_key& pk) + { + // First see if there is an existing target. + // + if (target* t = search_existing_target (pk)) + return t; + + // Then look for an existing file in this target-type-specific + // list of paths (@@ TODO: comes from the variable). + // + if (pk.tk.dir->relative ()) + { + dir_paths sp; + sp.push_back (pk.scope->src_path ()); // src_base + return search_existing_file (pk, sp); + } + else + return nullptr; + } + + static target* + search_alias (const prerequisite_key& pk) + { + // For an alias we don't want to silently create a target since it + // will do nothing and it most likely not what the user intended. + // + target* t (search_existing_target (pk)); + + if (t == nullptr) + fail << "no explicit target for prerequisite " << pk; + + return t; + } + + // type info + // + + const target_type target::static_type + { + "target", + nullptr, + nullptr, + nullptr, + &search_target, + false + }; + + const target_type mtime_target::static_type + { + "mtime_target", + &target::static_type, + nullptr, + nullptr, + &search_target, + false + }; + + const target_type path_target::static_type + { + "path_target", + &mtime_target::static_type, + nullptr, + nullptr, + &search_target, + false + }; + + template + static target* + file_factory (const target_type&, dir_path d, string n, const string* e) + { + // The file target type doesn't imply any extension. So if one + // wasn't specified, set it to empty rather than unspecified. + // In other words, we always treat file{foo} as file{foo.}. + // + return new T (move (d), + move (n), + (e != nullptr ? e : &extension_pool.find (""))); + } + + constexpr const char file_ext_var[] = "extension"; + constexpr const char file_ext_def[] = ""; + + const target_type file::static_type + { + "file", + &path_target::static_type, + &file_factory, + &target_extension_var, + &search_file, + false + }; + + const target_type alias::static_type + { + "alias", + &target::static_type, + &target_factory, + nullptr, // Should never need. + &search_alias, + false + }; + + const target_type dir::static_type + { + "dir", + &alias::static_type, + &target_factory, + nullptr, // Should never need. + &search_alias, + false + }; + + const target_type fsdir::static_type + { + "fsdir", + &target::static_type, + &target_factory, + nullptr, // Should never need. + &search_target, + false + }; + + static const std::string& + buildfile_target_extension (const target_key& tk, scope&) + { + // If the name is special 'buildfile', then there is no extension, + // otherwise it is .build. + // + return extension_pool.find (*tk.name == "buildfile" ? "" : "build"); + } + + const target_type buildfile::static_type + { + "buildfile", + &file::static_type, + &file_factory, + &buildfile_target_extension, + &search_file, + false + }; + + const target_type doc::static_type + { + "doc", + &file::static_type, + &file_factory, + &target_extension_var, // Same as file. + &search_file, + false + }; + + static target* + man_factory (const target_type&, dir_path d, string n, const string* e) + { + if (e == nullptr) + fail << "man target '" << n << "' must include extension (man section)"; + + return new man (move (d), move (n), e); + } + + const target_type man::static_type + { + "man", + &doc::static_type, + &man_factory, + nullptr, // Should be specified explicitly. + &search_file, + false + }; + + constexpr const char man1_ext[] = "1"; + const target_type man1::static_type + { + "man1", + &man::static_type, + &file_factory, + &target_extension_fix, + &search_file, + false + }; +} diff --git a/build2/target.ixx b/build2/target.ixx new file mode 100644 index 0000000..25c33ba --- /dev/null +++ b/build2/target.ixx @@ -0,0 +1,85 @@ +// file : build2/target.ixx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +namespace build2 +{ + // prerequisite_ref + // + inline bool prerequisite_ref:: + belongs (const target& t) const + { + const auto& p (t.prerequisites); + return !(p.empty () || this < &p.front () || this > &p.back ()); + } + + // prerequisite_member + // + inline prerequisite& prerequisite_member:: + as_prerequisite (tracer& trace) const + { + if (target == nullptr) + return prerequisite; + + // The use of the group's prerequisite scope is debatable. + // + scope& s (prerequisite.get ().scope); + return s.prerequisites.insert (nullptr, key ().tk, s, trace).first; + } + + // prerequisite_members + // + group_view + resolve_group_members (action, target&); // + + template + inline auto prerequisite_members_range::iterator:: + operator++ () -> iterator& + { + if (g_.count != 0) + { + if (++j_ <= g_.count) + return *this; + + // Switch back to prerequisite iteration mode. + // + g_.count = 0; + } + + ++i_; + + // Switch to member iteration mode. + // + if (r_->members_ && i_ != r_->e_ && i_->get ().type.see_through) + { + bool r (switch_members ()); + assert (r); // Group could not be resolved. + } + + return *this; + } + + template + inline bool prerequisite_members_range::iterator:: + switch_members () + { + do + { + g_ = resolve_group_members (r_->a_, search (*i_)); + + // If members are not know, iterate over the group as itself. + // + if (g_.members == nullptr) + { + g_.count = 0; + return false; + } + } + while (g_.count == 0 && // Skip empty groups. + ++i_ != r_->e_ && + i_->get ().type.see_through); + + j_ = 1; // Start from the first group member. + return true; + } +} diff --git a/build2/target.txx b/build2/target.txx new file mode 100644 index 0000000..3209720 --- /dev/null +++ b/build2/target.txx @@ -0,0 +1,58 @@ +// file : build2/target.txx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include +#include // extension_pool +#include +#include + +namespace build2 +{ + template + const string& + target_extension_fix (const target_key&, scope&) + { + return extension_pool.find (ext); + } + + template + const string& + target_extension_var (const target_key& tk, scope& s) + { + // Include target type/pattern-specific variables. + // + if (auto l = s.lookup (tk, var)) + { + // Help the user here and strip leading '.' from the extension. + // + const string& e (as (*l)); + return extension_pool.find ( + !e.empty () && e.front () == '.' ? string (e, 1) : e); + } + + if (def != nullptr) + return extension_pool.find (def); + + { + diag_record dr; + dr << error << "no default extension in variable '" << var << "'" + << info << "required to derive file name for "; + + // This is a bit hacky: we may be dealing with a target (see + // file::derive_path()) or prerequsite (see search_existing_file()). + // So we are going to check if dir is absolute. If it is, then + // we assume this is a target, otherwise -- prerequsite. + // + if (tk.dir->absolute ()) + dr << "target " << tk; + else + dr << "prerequisite " << prerequisite_key {nullptr, tk, &s}; + + dr << info << "perhaps you forgot to add " + << tk.type->name << "{*}: " << var << " = ..."; + } + + throw failed (); + } +} diff --git a/build2/test/module b/build2/test/module new file mode 100644 index 0000000..76fd9e1 --- /dev/null +++ b/build2/test/module @@ -0,0 +1,24 @@ +// file : build2/test/module -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_TEST_MODULE +#define BUILD2_TEST_MODULE + +#include +#include + +namespace build2 +{ + namespace test + { + extern "C" void + test_boot (scope&, const location&, unique_ptr&); + + extern "C" bool + test_init ( + scope&, scope&, const location&, unique_ptr&, bool, bool); + } +} + +#endif // BUILD2_TEST_MODULE diff --git a/build2/test/module.cxx b/build2/test/module.cxx new file mode 100644 index 0000000..133849a --- /dev/null +++ b/build2/test/module.cxx @@ -0,0 +1,88 @@ +// file : build2/test/module.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include +#include +#include +#include + +#include +#include + +using namespace std; +using namespace butl; + +namespace build2 +{ + namespace test + { + static rule rule_; + + extern "C" void + test_boot (scope& root, const location&, unique_ptr&) + { + tracer trace ("test::boot"); + + level5 ([&]{trace << "for " << root.out_path ();}); + + // Register the test operation. + // + root.operations.insert (test_id, test); + } + + extern "C" bool + test_init (scope& root, + scope&, + const location& l, + unique_ptr&, + bool first, + bool) + { + tracer trace ("test::init"); + + if (!first) + { + warn (l) << "multiple test module initializations"; + return true; + } + + const dir_path& out_root (root.out_path ()); + level5 ([&]{trace << "for " << out_root;}); + + // Enter module variables. + // + { + auto& v (var_pool); + + v.find ("test", bool_type); + v.find ("test.input", name_type); + v.find ("test.output", name_type); + v.find ("test.roundtrip", name_type); + v.find ("test.options", strings_type); + v.find ("test.arguments", strings_type); + } + + // Register rules. + // + { + auto& r (root.rules); + + // Register our test running rule. + // + r.insert (perform_test_id, "test", rule_); + + // Register our rule for the dist meta-operation. We need + // to do this because we have "ad-hoc prerequisites" (test + // input/output files) that need to be entered into the + // target list. + // + r.insert (dist_id, test_id, "test", rule_); + } + + return true; + } + } +} diff --git a/build2/test/operation b/build2/test/operation new file mode 100644 index 0000000..f924d4c --- /dev/null +++ b/build2/test/operation @@ -0,0 +1,18 @@ +// file : build2/test/operation -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_TEST_OPERATION +#define BUILD2_TEST_OPERATION + +#include + +namespace build2 +{ + namespace test + { + extern operation_info test; + } +} + +#endif // BUILD2_TEST_OPERATION diff --git a/build2/test/operation.cxx b/build2/test/operation.cxx new file mode 100644 index 0000000..2bae629 --- /dev/null +++ b/build2/test/operation.cxx @@ -0,0 +1,32 @@ +// file : build2/test/operation.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +using namespace std; +using namespace butl; + +namespace build2 +{ + namespace test + { + static operation_id + test_pre (meta_operation_id mo) + { + // Run update as a pre-operation, unless we are disfiguring. + // + return mo != disfigure_id ? update_id : 0; + } + + operation_info test { + "test", + "test", + "testing", + "has nothing to test", // We cannot "be tested". + execution_mode::first, + &test_pre, + nullptr + }; + } +} diff --git a/build2/test/rule b/build2/test/rule new file mode 100644 index 0000000..1102876 --- /dev/null +++ b/build2/test/rule @@ -0,0 +1,30 @@ +// file : build2/test/rule -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_TEST_RULE +#define BUILD2_TEST_RULE + +#include +#include + +namespace build2 +{ + namespace test + { + class rule: public build2::rule + { + public: + virtual match_result + match (action, target&, const std::string&) const; + + virtual recipe + apply (action, target&, const match_result&) const; + + static target_state + perform_test (action, target&); + }; + } +} + +#endif // BUILD2_TEST_RULE diff --git a/build2/test/rule.cxx b/build2/test/rule.cxx new file mode 100644 index 0000000..abf9fcf --- /dev/null +++ b/build2/test/rule.cxx @@ -0,0 +1,439 @@ +// file : build2/test/rule.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include + +#include +#include +#include +#include + +#include // add_options() + +using namespace std; +using namespace butl; + +namespace build2 +{ + namespace test + { + match_result rule:: + match (action a, target& t, const std::string&) const + { + // First determine if this is a test. This is controlled by + // the test target variable and text. scope variables. + // Also, it feels redundant to specify, say, "test = true" + // and "test.output = test.out" -- the latter already says + // this is a test. So take care of that as well. + // + bool r (false); + lookup l; + + // @@ This logic doesn't take into account target type/pattern- + // specific variables. + // + // @@ Perhaps a find_any()? + // + for (auto p (t.vars.find_namespace ("test")); + p.first != p.second; + ++p.first) + { + const variable& var (p.first->first); + const value& val (p.first->second); + + // If we have test, then always use that. + // + if (var.name == "test") + { + l = lookup (val, t); + break; + } + + // Otherwise check for variables that would indicate this + // is a test. + // + if (var.name == "test.input" || + var.name == "test.output" || + var.name == "test.roundtrip" || + var.name == "test.options" || + var.name == "test.arguments") + { + r = true; + break; + } + } + + if (!r) + { + // See if there is a scope variable. + // + if (!l.defined ()) + l = t.base_scope ()[ + var_pool.find (string("test.") + t.type ().name, bool_type)]; + + r = l && as (*l); + } + + // If this is the update pre-operation, then all we really need to + // do is say we are not a match and the standard matching machinery + // will (hopefully) find the rule to update this target. + // + // There is one thing that compilates this simple approach: test + // input/output. While normally they will be existing (in src_base) + // files, they could also be auto-generated. In fact, they could + // only be needed for testing, which means the normall update won't + // even know about them (nor clean, for that matter; this is why we + // need cleantest). + // + // To make generated input/output work we will have to cause their + // update ourselves. I other words, we may have to do some actual + // work for (update, test), and not simply "guide" (update, 0) as + // to which targets need updating. For how exactly we are going to + // do it, see apply() below. + // + match_result mr (t, r); + + // If this is the update pre-operation, change the recipe action + // to (update, 0) (i.e., "unconditional update"). + // + if (r && a.operation () == update_id) + mr.recipe_action = action (a.meta_operation (), update_id); + + return mr; + } + + recipe rule:: + apply (action a, target& t, const match_result& mr) const + { + tracer trace ("test::rule::apply"); + + if (!mr.bvalue) // Not a test. + return noop_recipe; + + // Ok, if we are here, then this means: + // + // 1. This target is a test. + // 2. The action is either + // a. (perform, test, 0) or + // b. (*, update, install) + // + // In both cases, the next step is to see if we have test.{input, + // output,roundtrip}. + // + + // First check the target-specific vars since they override any + // scope ones. + // + auto il (t.vars["test.input"]); + auto ol (t.vars["test.output"]); + auto rl (t.vars["test.roundtrip"]); + auto al (t.vars["test.arguments"]); // Should be input or arguments. + + if (al) + { + if (il) + fail << "both test.input and test.arguments specified for " + << "target " << t; + + if (rl) + fail << "both test.roundtrip and test.arguments specified for " + << "target " << t; + } + + scope& bs (t.base_scope ()); + + if (!il && !ol && !rl) + { + string n ("test."); + n += t.type ().name; + + const variable& in (var_pool.find (n + ".input", name_type)); + const variable& on (var_pool.find (n + ".output", name_type)); + const variable& rn (var_pool.find (n + ".roundtrip", name_type)); + + // We should only keep value(s) that were specified together + // in the innermost scope. + // + // @@ Shouldn't we stop at project root? + // + for (scope* s (&bs); s != nullptr; s = s->parent_scope ()) + { + ol = s->vars[on]; + + if (!al) // Not overriden at target level by test.arguments? + { + il = s->vars[in]; + rl = s->vars[rn]; + } + + if (il || ol || rl) + break; + } + } + + const name* in; + const name* on; + + // Reduce the roundtrip case to input/output. + // + if (rl) + { + if (il || ol) + fail << "both test.roundtrip and test.input/output specified " + << "for target " << t; + + in = on = &as (*rl); + } + else + { + in = il ? &as (*il) : nullptr; + on = ol ? &as (*ol) : nullptr; + } + + // Resolve them to targets, which normally would be existing files + // but could also be targets that need updating. + // + target* it (in != nullptr ? &search (*in, bs) : nullptr); + target* ot (on != nullptr ? in == on ? it : &search (*on, bs) : nullptr); + + if (a.operation () == update_id) + { + // First see if input/output are existing, up-to-date files. This + // is a common case optimization. + // + if (it != nullptr) + { + build2::match (a, *it); + + if (it->state () == target_state::unchanged) + { + unmatch (a, *it); + it = nullptr; + } + } + + if (ot != nullptr) + { + if (in != on) + { + build2::match (a, *ot); + + if (ot->state () == target_state::unchanged) + { + unmatch (a, *ot); + ot = nullptr; + } + } + else + ot = it; + } + + + // Find the "real" update rule, that is, the rule that would + // have been found if we signalled that we do not match from + // match() above. + // + recipe d (match_delegate (a, t).first); + + // If we have no input/output that needs updating, then simply + // redirect to it. + // + if (it == nullptr && ot == nullptr) + return d; + + // Ok, time to handle the worst case scenario: we need to + // cause update of input/output targets and also delegate + // to the real update. + // + return [it, ot, dr = move (d)] (action a, target& t) -> target_state + { + // Do the general update first. + // + target_state r (execute_delegate (dr, a, t)); + + if (it != nullptr) + r |= execute (a, *it); + + if (ot != nullptr) + r |= execute (a, *ot); + + return r; + }; + } + else + { + // Cache the targets in our prerequsite targets lists where they + // can be found by perform_test(). If we have either or both, + // then the first entry is input and the second -- output (either + // can be NULL). + // + if (it != nullptr || ot != nullptr) + { + auto& pts (t.prerequisite_targets); + pts.resize (2, nullptr); + pts[0] = it; + pts[1] = ot; + } + + return &perform_test; + } + } + + static void + add_arguments (cstrings& args, const target& t, const char* n) + { + string var ("test."); + var += n; + + auto l (t.vars[var]); + + if (!l) + { + var.resize (5); + var += t.type ().name; + var += '.'; + var += n; + l = t.base_scope ()[var_pool.find (var, strings_type)]; + } + + if (l) + config::append_options (args, as (*l)); + } + + // The format of args shall be: + // + // name1 arg arg ... nullptr + // name2 arg arg ... nullptr + // ... + // nameN arg arg ... nullptr nullptr + // + static bool + run_test (target& t, + diag_record& dr, + char const** args, + process* prev = nullptr) + { + // Find the next process, if any. + // + char const** next (args); + for (next++; *next != nullptr; next++) ; + next++; + + // Redirect stdout to a pipe unless we are last, in which + // case redirect it to stderr. + // + int out (*next == nullptr ? 2 : -1); + bool pr, wr; + + try + { + if (prev == nullptr) + { + // First process. + // + process p (args, 0, out); + pr = *next == nullptr || run_test (t, dr, next, &p); + wr = p.wait (); + } + else + { + // Next process. + // + process p (args, *prev, out); + pr = *next == nullptr || run_test (t, dr, next, &p); + wr = p.wait (); + } + } + catch (const process_error& e) + { + error << "unable to execute " << args[0] << ": " << e.what (); + + if (e.child ()) + exit (1); + + throw failed (); + } + + if (!wr) + { + if (pr) // First failure? + dr << fail << "test " << t << " failed"; // Multi test: test 1. + + dr << error << "non-zero exit status: "; + print_process (dr, args); + } + + return pr && wr; + } + + target_state rule:: + perform_test (action, target& t) + { + // @@ Would be nice to print what signal/core was dumped. + // + // @@ Doesn't have to be a file target if we have test.cmd. + // + + file& ft (static_cast (t)); + assert (!ft.path ().empty ()); // Should have been assigned by update. + + cstrings args {ft.path ().string ().c_str ()}; + + // Do we have options? + // + add_arguments (args, t, "options"); + + // Do we have input? + // + auto& pts (t.prerequisite_targets); + if (pts.size () != 0 && pts[0] != nullptr) + { + file& it (static_cast (*pts[0])); + assert (!it.path ().empty ()); // Should have been assigned by update. + args.push_back (it.path ().string ().c_str ()); + } + // Maybe arguments then? + // + else + add_arguments (args, t, "arguments"); + + args.push_back (nullptr); + + // Do we have output? + // + if (pts.size () != 0 && pts[1] != nullptr) + { + file& ot (static_cast (*pts[1])); + assert (!ot.path ().empty ()); // Should have been assigned by update. + + args.push_back ("diff"); + args.push_back ("-u"); + args.push_back (ot.path ().string ().c_str ()); + args.push_back ("-"); + args.push_back (nullptr); + } + + args.push_back (nullptr); // Second. + + if (verb >= 2) + print_process (args); + else if (verb) + text << "test " << t; + + { + diag_record dr; + + if (!run_test (t, dr, args.data ())) + { + dr << info << "test command line: "; + print_process (dr, args); + } + } + + return target_state::changed; + } + } +} diff --git a/build2/token b/build2/token new file mode 100644 index 0000000..55ab1ee --- /dev/null +++ b/build2/token @@ -0,0 +1,73 @@ +// file : build2/token -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_TOKEN +#define BUILD2_TOKEN + +#include +#include +#include // size_t +#include // uint64_t +#include // move + +namespace build2 +{ + enum class token_type + { + eos, + name, + newline, + pair_separator, + colon, + lcbrace, + rcbrace, + equal, + equal_plus, + plus_equal, + dollar, + lparen, + rparen + }; + + class token + { + public: + token_type type; + bool separated; // Whitespace-separated from the previous token. + bool quoted; // Name (or some part of it) was quoted. + + char pair; // Only valid for pair_separator. + std::string value; // Only valid for name. + + std::uint64_t line; + std::uint64_t column; + + public: + token (token_type t, bool s, std::uint64_t l, std::uint64_t c) + : type (t), separated (s), quoted (false), line (l), column (c) {} + + token (char p, bool s, std::uint64_t l, std::uint64_t c) + : type (token_type::pair_separator), + separated (s), + quoted (false), + pair (p), + line (l), + column (c) {} + + token (std::string n, bool s, bool q, std::uint64_t l, std::uint64_t c) + : type (token_type::name), + separated (s), + quoted (q), + value (std::move (n)), + line (l), + column (c) {} + }; + + // Output the token value in a format suitable for diagnostics. + // + std::ostream& + operator<< (std::ostream&, const token&); +} + +#endif // BUILD2_TOKEN diff --git a/build2/token.cxx b/build2/token.cxx new file mode 100644 index 0000000..b14fc00 --- /dev/null +++ b/build2/token.cxx @@ -0,0 +1,35 @@ +// file : build2/token.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include + +using namespace std; + +namespace build2 +{ + ostream& + operator<< (ostream& os, const token& t) + { + switch (t.type) + { + case token_type::eos: os << ""; break; + case token_type::newline: os << ""; break; + case token_type::pair_separator: os << ""; break; + case token_type::colon: os << ":"; break; + case token_type::lcbrace: os << "{"; break; + case token_type::rcbrace: os << "}"; break; + case token_type::equal: os << "="; break; + case token_type::equal_plus: os << "=+"; break; + case token_type::plus_equal: os << "+="; break; + case token_type::dollar: os << "$"; break; + case token_type::lparen: os << "("; break; + case token_type::rparen: os << ")"; break; + case token_type::name: os << t.value; break; + } + + return os; + } +} diff --git a/build2/types b/build2/types new file mode 100644 index 0000000..da96805 --- /dev/null +++ b/build2/types @@ -0,0 +1,55 @@ +// file : build2/types -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_TYPES +#define BUILD2_TYPES + +#include +#include +#include // pair +#include // unique_ptr, shared_ptr +#include // size_t +#include // reference_wrapper + +#include +#include + +#include + +namespace build2 +{ + // Commonly-used types. + // + using std::pair; + using std::size_t; + using std::string; + using std::unique_ptr; + using std::shared_ptr; + using std::reference_wrapper; + using std::vector; + + using strings = vector; + using cstrings = vector; + + // + // + using butl::path; + using butl::dir_path; + using butl::basic_path; + using butl::invalid_path; + + using paths = std::vector; + using dir_paths = std::vector; + + // + // + using butl::system_clock; + using butl::timestamp; + using butl::duration; + using butl::timestamp_unknown; + using butl::timestamp_nonexistent; + using butl::operator<<; +} + +#endif // BUILD2_TYPES diff --git a/build2/utility b/build2/utility new file mode 100644 index 0000000..a460bab --- /dev/null +++ b/build2/utility @@ -0,0 +1,85 @@ +// file : build2/utility -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_UTILITY +#define BUILD2_UTILITY + +#include // move(), make_pair() +#include // assert() +#include // uncaught_exception() +#include + +#include + +namespace build2 +{ + using std::move; + using std::make_pair; + + // Empty string and path. + // + extern const std::string empty_string; + extern const path empty_path; + extern const dir_path empty_dir_path; + + // Parse version string in the X.Y.Z[-{a|b}N] to a version integer in the + // AABBCCDD form as describe in . Throw invalid_argument + // if the passed string is not a valid version. + // + unsigned int + to_version (const string&); + + // Call a function if there is an exception. + // + + // Means we are in the body of a destructor that is being called + // as part of the exception stack unwindining. Used to compensate + // for the deficiencies of uncaught_exception() until C++17 + // uncaught_exceptions() becomes available. + // + // @@ MT: will have to be TLS. + // + extern bool exception_unwinding_dtor; + + template + struct exception_guard; + + template + inline exception_guard + make_exception_guard (F f) + { + return exception_guard (move (f)); + } + + template + struct exception_guard + { + exception_guard (F f): f_ (move (f)) {} + ~exception_guard () + { + if (std::uncaught_exception ()) + { + exception_unwinding_dtor = true; + f_ (); + exception_unwinding_dtor = false; + } + } + + private: + F f_; + }; + + // Pools (@@ perhaps move into a separate header). + // + struct string_pool: std::unordered_set + { + const std::string& + find (const char* s) {return *emplace (s).first;} + + const std::string& + find (const std::string& s) {return *emplace (s).first;} + }; +} + +#endif // BUILD2_UTILITY diff --git a/build2/utility.cxx b/build2/utility.cxx new file mode 100644 index 0000000..fcecb5b --- /dev/null +++ b/build2/utility.cxx @@ -0,0 +1,94 @@ +// file : build2/utility.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include // strtol() + +using namespace std; + +namespace build2 +{ + const string empty_string; + const path empty_path; + const dir_path empty_dir_path; + + unsigned int + to_version (const string& s) + { + // See tests/version. + // + + auto parse = [&s] (size_t& p, const char* m, long min = 0, long max = 99) + -> unsigned int + { + if (s[p] == '-' || s[p] == '+') // stoi() allows these. + throw invalid_argument (m); + + const char* b (s.c_str () + p); + char* e; + long r (strtol (b, &e, 10)); + + if (b == e || r < min || r > max) + throw invalid_argument (m); + + p = e - s.c_str (); + return static_cast (r); + }; + + auto bail = [] (const char* m) {throw invalid_argument (m);}; + + unsigned int ma, mi, bf, ab (0); + + size_t p (0), n (s.size ()); + ma = parse (p, "invalid major version"); + + if (p >= n || s[p] != '.') + bail ("'.' expected after major version"); + + mi = parse (++p, "invalid minor version"); + + if (p >= n || s[p] != '.') + bail ("'.' expected after minor version"); + + bf = parse (++p, "invalid bugfix version"); + + if (p < n) + { + if (s[p] != '-') + bail ("'-' expected after bugfix version"); + + char k (s[++p]); + + if (k != 'a' && k != 'b') + bail ("'a' or 'b' expected in release component"); + + ab = parse (++p, "invalid release component", 1, 49); + + if (p != n) + bail ("junk after release component"); + + if (k == 'b') + ab += 50; + } + + // AABBCCDD + unsigned int r (ma * 1000000U + + mi * 10000U + + bf * 100U); + + if (ab != 0) + { + if (r == 0) + bail ("0.0.0 version with release component"); + + r -= 100; + r += ab; + } + + return r; + } + + bool exception_unwinding_dtor = false; +} diff --git a/build2/variable b/build2/variable new file mode 100644 index 0000000..6304979 --- /dev/null +++ b/build2/variable @@ -0,0 +1,831 @@ +// file : build2/variable -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_VARIABLE +#define BUILD2_VARIABLE + +#include +#include +#include // nullptr_t +#include // pair, make_pair() +#include +#include // hash +#include // conditional, is_reference, remove_reference, etc. +#include + +#include + +#include +#include + +#include + +namespace build2 +{ + struct variable; + + // If assign is NULL, then the value is assigned as is. If append + // is NULL, then the names are appended to the end of the value + // and assign is called, if not NULL. Variable is provided primarily + // for diagnostics. Return true if the resulting value is not empty. + // + struct value_type + { + const char* name; + bool (*const assign) (names&, const variable&); + bool (*const append) (names&, names, const variable&); + }; + + enum class variable_visibility + { + scope, // This scope (no outer scopes). + project, // This project (no outer projects). + normal // All outer scopes. + }; + + // variable + // + // The two variables are considered the same if they have the same name. + // + struct variable + { + std::string name; + const value_type* type; // If NULL, then not (yet) typed. + variable_visibility visibility; + char pairs; // Pair symbold or '\0' if not used. + }; + + inline bool + operator== (const variable& x, const variable& y) {return x.name == y.name;} + + typedef std::reference_wrapper variable_cref; + + // value + // + class value + { + public: + // By default we create NULL value. + // + explicit value (const value_type* t = nullptr) + : type (t), state_ (state_type::null) {} + + value (value&&) = default; + + value& + operator= (std::nullptr_t) + { + data_.clear (); + state_ = state_type::null; + return *this; + } + + value& + operator= (value&&); + + value& + operator= (const value& v) + { + if (&v != this) + *this = value (v); + return *this; + } + + value& + operator= (reference_wrapper v) {return *this = v.get ();} + + value& + operator= (reference_wrapper v) {return *this = v.get ();} + + value& + append (value, const variable&); // Aka operator+=(). + + value& + prepend (value, const variable&); // Aka operator=+(). + + // Forwarded to the representation type's assign()/append() (see below). + // + template value& operator= (T); + value& operator= (const char* v) {return *this = std::string (v);} + + template value& operator+= (T); + value& operator+= (const char* v) {return *this += std::string (v);} + + private: + explicit value (const value&) = default; + + public: + const value_type* type; // NULL means this value is not (yet) typed. + + bool null () const {return state_ == state_type::null;} + bool empty () const {return state_ == state_type::empty;} + + explicit operator bool () const {return !null ();} + bool operator== (std::nullptr_t) const {return null ();} + bool operator!= (std::nullptr_t) const {return !null ();} + + // Raw data read interface. + // + using const_iterator = names::const_iterator; + + const_iterator begin () const {return data_.begin ();} + const_iterator end () const {return data_.end ();} + + // Raw data write interface. Note that it triggers callbacks for + // typed values. Variable is passed for diagnostics. + // + void + assign (names, const variable&); + + void + append (names, const variable&); + + void + prepend (names, const variable&); + + public: + // Don't use directly except in the implementation of representation + // types, in which case take care to update state. + // + enum class state_type {null, empty, filled} state_; + names data_; + }; + + //@@ Right now we assume that for each value type each value has a + // unique representation. This is currently not the case for map. + // + inline bool + operator== (const value& x, const value& y) + { + return x.state_ == y.state_ && x.data_ == y.data_; + } + + inline bool + operator!= (const value& x, const value& y) {return !(x == y);} + + // lookup + // + // A variable can be undefined, NULL, or contain a (potentially + // empty) value. + // + struct variable_map; + + template + struct lookup + { + V* value; + const variable_map* vars; + + bool + defined () const {return value != nullptr;} + + // Note: returns true if defined and not NULL. + // + explicit operator bool () const {return defined () && !value->null ();} + + V& operator* () const {return *value;} + V* operator-> () const {return value;} + + // Return true if this value belongs to the specified scope or target. + // Note that it can also be a target type/pattern-specific value. + // + template + bool + belongs (const T& x) const {return vars == &x.vars;} + + lookup (): value (nullptr), vars (nullptr) {} + lookup (V* v, const variable_map* vs) + : value (v), vars (v != nullptr ? vs : nullptr) {} + + template + lookup (V& v, const T& x): lookup (&v, &x.vars) {} + }; + + // Representation types. + // + template struct value_traits; + + // Assign value type to the value. + // + template + void assign (value&, const variable&); + void assign (value&, const value_type*, const variable&); + + template typename value_traits::type as (value&); + template typename value_traits::const_type as (const value&); + + // "Assign" simple value type to the value stored in name. Return false + // if the value is not valid for this type. + // + template bool assign (name&); + + template typename value_traits::type as (name&); + template typename value_traits::const_type as (const name&); + + // bool + // + template + struct bool_value + { + explicit + operator bool () const {return d->value[0] == 't';} + + bool_value& + operator= (bool v) + { + d->value = v ? "true" : "false"; + return *this; + } + + bool_value& + operator+= (bool v) + { + if (!*this && v) + d->value = "true"; + return *this; + } + + // Implementation details. + // + public: + explicit bool_value (D& d): d (&d) {} + + bool_value (const bool_value&) = delete; + bool_value& operator= (const bool_value&) = delete; // Rebind or deep? + + bool_value (bool_value&&) = default; + bool_value& operator= (bool_value&&) = delete; + + D* d; // name + }; + + template <> + struct value_traits + { + using type = bool_value; + using const_type = bool_value; + + static type as (name& n) {return type (n);} + static const_type as (const name& n) {return const_type (n);} + + static type as (value&); + static const_type as (const value&); + + static bool assign (name&); + static void assign (value&, bool); + static void append (value&, bool); + + static const build2::value_type value_type; + }; + + extern const value_type* bool_type; + + // string + // + template <> + struct value_traits + { + using type = std::string&; + using const_type = const std::string&; + + static type as (name& n) {return n.value;} + static const_type as (const name& n) {return n.value;} + + static type as (value&); + static const_type as (const value&); + + static bool assign (name&); + static void assign (value&, std::string); + static void append (value&, std::string); + + static const build2::value_type value_type; + }; + + extern const value_type* string_type; + + // dir_path + // + template <> + struct value_traits + { + using type = dir_path&; + using const_type = const dir_path&; + + static type as (name& n) {return n.dir;} + static const_type as (const name& n) {return n.dir;} + + static type as (value&); + static const_type as (const value&); + + static bool assign (name&); + static void assign (value&, dir_path); + static void append (value&, dir_path); + + static const build2::value_type value_type; + }; + + extern const value_type* dir_path_type; + + // name + // + template <> + struct value_traits + { + using type = name&; + using const_type = const name&; + + static type as (name& n) {return n;} + static const_type as (const name& n) {return n;} + + static type as (value&); + static const_type as (const value&); + + static bool assign (name&) {return true;} + static void assign (value&, name); + static void append (value&, name) = delete; + + static const build2::value_type value_type; + }; + + extern const value_type* name_type; + + // vector + // + template + struct vector_value + { + using size_type = typename D::size_type; + + using value_type = typename value_traits::type; + using const_value_type = typename value_traits::const_type; + + template + struct iterator_impl: I + { + using value_type = V; + using pointer = value_type*; + using reference = R; + using difference_type = typename I::difference_type; + + iterator_impl () = default; + iterator_impl (const I& i): I (i) {} + + // Note: operator->() is unavailable if R is a value. + // + reference operator* () const {return as (I::operator* ());} + pointer operator-> () const {return &as (I::operator* ());} + reference operator[] (difference_type n) const + { + return as (I::operator[] (n)); + } + }; + + // Make iterator the same as const_iterator if our data type is const. + // + using const_iterator = + iterator_impl; + using iterator = typename std::conditional< + std::is_const::value, + const_iterator, + iterator_impl>::type; + + public: + vector_value& + operator= (std::vector v) {assign (std::move (v)); return *this;} + + vector_value& + assign (std::vector); + + template + vector_value& + assign (const vector_value&); + + template + vector_value& + append (const vector_value&); + + public: + bool empty () const {return d->empty ();} + size_type size () const {return d->size ();} + + value_type operator[] (size_type i) {return as ((*d)[i]);} + const_value_type operator[] (size_type i) const {return as ((*d)[i]);} + + iterator begin () {return iterator (d->begin ());} + iterator end () {return iterator (d->end ());} + + const_iterator begin () const {return const_iterator (d->begin ());} + const_iterator end () const {return const_iterator (d->end ());} + + const_iterator cbegin () const {return const_iterator (d->begin ());} + const_iterator cend () const {return const_iterator (d->end ());} + + // Implementation details. + // + public: + explicit vector_value (D& d): d (&d) {} + + vector_value (const vector_value&) = delete; + vector_value& operator= (const vector_value&) = delete; // Rebind or deep? + + vector_value (vector_value&&) = default; + vector_value& operator= (vector_value&&) = default; //@@ TMP + + explicit vector_value (std::nullptr_t): d (nullptr) {} //@@ TMP + + D* d; // names + }; + + template + struct value_traits> + { + using type = vector_value; + using const_type = vector_value; + + static type as (value&); + static const_type as (const value&); + + template static void assign (value&, V); + template static void append (value&, V); + + static const std::string type_name; + static const build2::value_type value_type; + }; + + template + struct value_traits>: value_traits> {}; + + using strings_value = vector_value; + using const_strings_value = vector_value; + + extern const value_type* strings_type; // vector aka strings + extern const value_type* dir_paths_type; // vector aka dir_paths + extern const value_type* names_type; // vector aka names + + // map + // + template + struct map_value + { + template + struct pair + { + using first_type = typename std::conditional< + std::is_reference::value, + std::reference_wrapper::type>, + F>::type; + + using second_type = typename std::conditional< + std::is_reference::value, + std::reference_wrapper::type>, + S>::type; + + first_type first; + second_type second; + }; + + template + struct iterator_impl + { + using value_type = T; + using pointer = value_type*; + using reference = value_type; // Note: value. + using difference_type = typename I::difference_type; + using iterator_category = std::bidirectional_iterator_tag; + + iterator_impl () = default; + iterator_impl (const I& i): i_ (i) {} + + pointer operator-> () const = delete; + reference operator* () const + { + return value_type {as (*i_), as (*(i_ + 1))}; + } + + iterator_impl& operator++ () {i_ += 2; return *this;} + iterator_impl operator++ (int) {auto r (*this); operator++ (); return r;} + + iterator_impl& operator-- () {i_ -= 2; return *this;} + iterator_impl operator-- (int) {auto r (*this); operator-- (); return r;} + + bool operator== (const iterator_impl& y) const {return i_ == y.i_;} + bool operator!= (const iterator_impl& y) const {return i_ != y.i_;} + + private: + I i_; + }; + + using size_type = typename D::size_type; + + using value_type = pair::const_type, + typename value_traits::type>; + + using const_value_type = pair::const_type, + typename value_traits::const_type>; + + // Make iterator the same as const_iterator if our data type is const. + // + using const_iterator = + iterator_impl; + using iterator = typename std::conditional< + std::is_const::value, + const_iterator, + iterator_impl>::type; + + + public: + map_value& + operator= (std::map m) {assign (std::move (m)); return *this;} + + map_value& + assign (std::map); + + bool empty () const {return d->empty ();} + size_type size () const {return d->size ();} + + iterator find (const K&); + const_iterator find (const K&) const; + + iterator begin () {return iterator (d->begin ());} + iterator end () {return iterator (d->end ());} + + const_iterator begin () const {return const_iterator (d->begin ());} + const_iterator end () const {return const_iterator (d->end ());} + + // Implementation details. + // + public: + explicit map_value (D& d): d (&d) {} + + map_value (const map_value&) = delete; + map_value& operator= (const map_value&) = delete; // Rebind or deep? + + map_value (map_value&&) = default; + map_value& operator= (map_value&&) = delete; + + D* d; // names + }; + + template + struct value_traits> + { + using type = map_value; + using const_type = map_value; + + static type as (value&); + static const_type as (const value&); + + template static void assign (value&, M); + template static void append (value&, M); + + static const std::string type_name; + static const build2::value_type value_type; + }; + + template + struct value_traits>: value_traits> {}; +} + +namespace std +{ + template <> + struct hash: hash + { + size_t + operator() (const build2::variable& v) const noexcept + { + return hash::operator() (v.name); + } + }; +} + +namespace butl +{ + template <> + struct compare_prefix: compare_prefix + { + typedef compare_prefix base; + + explicit + compare_prefix (char d): base (d) {} + + bool + operator() (const build2::variable& x, const build2::variable& y) const + { + return base::operator() (x.name, y.name); + } + + bool + prefix (const build2::variable& p, const build2::variable& k) const + { + return base::prefix (p.name, k.name); + } + }; +} + +namespace build2 +{ + // variable_pool + // + using variable_pool_base = std::unordered_set; + struct variable_pool: private variable_pool_base + { + const variable& + find (string name, const build2::value_type* t = nullptr, char p = '\0') + { + return find (name, nullptr, t, p); + } + + const variable& + find (string name, + variable_visibility v, + const build2::value_type* t = nullptr, + char p = '\0') + { + return find (name, &v, t, p); + } + + using variable_pool_base::clear; + + private: + const variable& + find (string name, + const variable_visibility* vv, + const build2::value_type* t, + char p) + { + auto r ( + insert ( + variable { + std::move (name), + t, + vv != nullptr ? *vv : variable_visibility::normal, + p})); + const variable& v (*r.first); + + // Update type? + // + if (!r.second && t != nullptr && v.type != t) + { + assert (v.type == nullptr); + const_cast (v).type = t; // Not changing the key. + } + + // Change visibility? While this might at first seem like a bad idea, + // it can happen that the variable lookup happens before any values + // were set, in which case the variable will be entered with the + // default visibility. + // + if (!r.second && vv != nullptr && v.visibility != *vv) + { + assert (v.visibility == variable_visibility::normal); // Default. + const_cast (v).visibility = *vv; // Not changing the key. + } + + return v; + } + }; + + extern variable_pool var_pool; + + // variable_map + // + struct variable_map + { + using map_type = butl::prefix_map; + using size_type = map_type::size_type; + + template + struct iterator_adapter: I + { + iterator_adapter () = default; + iterator_adapter (const I& i): I (i) {} + typename I::reference operator* () const; + typename I::pointer operator-> () const; + }; + + using const_iterator = iterator_adapter; + + const value* + find (const variable& var) const + { + auto i (m_.find (var)); + const value* r (i != m_.end () ? &i->second : nullptr); + + // First access after being assigned a type? + // + if (r != nullptr && var.type != nullptr && r->type != var.type) + build2::assign (const_cast (*r), var.type, var); + + return r; + } + + value* + find (const variable& var) + { + auto i (m_.find (var)); + value* r (i != m_.end () ? &i->second : nullptr); + + // First access after being assigned a type? + // + if (r != nullptr && var.type != nullptr && r->type != var.type) + build2::assign (*r, var.type, var); + + return r; + } + + lookup + operator[] (const variable& var) const + { + return lookup (find (var), this); + } + + lookup + operator[] (const std::string& name) const + { + return operator[] (var_pool.find (name)); + } + + // Non-const lookup. Only exposed on the map directly. + // + lookup + operator[] (const variable& var) + { + return lookup (find (var), this); + } + + lookup + operator[] (const std::string& name) + { + return operator[] (var_pool.find (name)); + } + + // The second member in the pair indicates whether the new + // value (which will be NULL) was assigned. + // + std::pair, bool> + assign (const variable& var) + { + auto r (m_.emplace (var, value (var.type))); + value& v (r.first->second); + + // First access after being assigned a type? + // + if (!r.second && var.type != nullptr && v.type != var.type) + build2::assign (v, var.type, var); + + return std::make_pair (std::reference_wrapper (v), r.second); + } + + std::pair, bool> + assign (const std::string& name) + { + return assign (var_pool.find (name)); + } + + std::pair + find_namespace (const std::string& ns) const + { + auto r (m_.find_prefix (var_pool.find (ns))); + return std::make_pair (const_iterator (r.first), + const_iterator (r.second)); + } + + const_iterator + begin () const {return m_.begin ();} + + const_iterator + end () const {return m_.end ();} + + bool + empty () const {return m_.empty ();} + + size_type + size () const {return m_.size ();} + + private: + map_type m_; + }; + + // Target type/pattern-specific variables. + // + // @@ In quite a few places we assume that we can store a reference + // to the returned value (e.g., install::lookup_install()). If + // we "instantiate" the value on the fly, then we will need to + // consider its lifetime. + // + using variable_pattern_map = std::map; + + struct variable_type_map: std::map, + variable_pattern_map> + { + build2::lookup + lookup (const target_type&, const string& name, const variable&) const; + }; +} + +#include +#include + +#endif // BUILD2_VARIABLE diff --git a/build2/variable.cxx b/build2/variable.cxx new file mode 100644 index 0000000..27555ba --- /dev/null +++ b/build2/variable.cxx @@ -0,0 +1,452 @@ +// file : build2/variable.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include // make_move_iterator() + +#include +#include + +using namespace std; + +namespace build2 +{ + // value + // + void + assign (value& v, const value_type* t, const variable& var) + { + if (v.type == nullptr) + { + v.type = t; + + if (v && t->assign != nullptr) + v.state_ = t->assign (v.data_, var) + ? value::state_type::filled + : value::state_type::empty; + } + else + fail << "variable '" << var.name << "' type mismatch" << + info << "value '" << v.data_ << "' is " << v.type->name << + info << (t == var.type ? "variable" : "new type") << " is " + << (var.type != nullptr ? var.type->name : "untyped"); + } + + value& value:: + operator= (value&& v) + { + assert (type == nullptr || type == v.type); + + // Since the types are the same, we don't need to call + // the callbacks. + // + type = v.type; + state_ = v.state_; + data_ = move (v.data_); + + return *this; + } + + value& value:: + append (value v, const variable& var) + { + assert (type == v.type); + append (move (v.data_), var); + return *this; + } + + value& value:: + prepend (value v, const variable& var) + { + assert (type == v.type); + prepend (move (v.data_), var); + return *this; + } + + void value:: + append (names v, const variable& var) + { + // Treat append to NULL as assign. + // + if (!null () && type != nullptr && type->append != nullptr) + { + state_ = type->append (data_, move (v), var) + ? state_type::filled + : state_type::empty; + return; + } + + if (data_.empty ()) + data_ = move (v); + else + data_.insert (data_.end (), + make_move_iterator (v.begin ()), + make_move_iterator (v.end ())); + + state_ = (type != nullptr && type->assign != nullptr + ? type->assign (data_, var) + : !data_.empty ()) + ? state_type::filled + : state_type::empty; + } + + void value:: + prepend (names v, const variable& var) + { + // Reduce to append. + // + if (!null () && type != nullptr && type->append != nullptr) + { + state_ = type->append (v, move (data_), var) + ? state_type::filled + : state_type::empty; + swap (data_, v); + return; + } + + if (data_.empty ()) + data_ = move (v); + else + { + v.insert (v.end (), + make_move_iterator (data_.begin ()), + make_move_iterator (data_.end ())); + swap (data_, v); + } + + state_ = (type != nullptr && type->assign != nullptr + ? type->assign (data_, var) + : !data_.empty ()) + ? state_type::filled + : state_type::empty; + } + + // bool value + // + bool value_traits:: + assign (name& n) + { + if (n.simple ()) + { + const string& s (n.value); + + if (s == "true" || s == "false") + return true; + } + + return false; + } + + static bool + bool_assign (names& v, const variable& var) + { + // Verify the value is either "true" or "false". + // + if (v.size () == 1) + { + name& n (v.front ()); + + if (value_traits::assign (n)) + return true; + } + + fail << "invalid bool variable '" << var.name << "' value '" << v << "'"; + return false; + } + + static bool + bool_append (names& v, names a, const variable& var) + { + // Translate append to OR. + // + bool_assign (a, var); // Verify "true" or "false". + + if (a.front ().value[0] == 't' && v.front ().value[0] == 'f') + v = move (a); + + return true; + } + + const value_type value_traits::value_type + { + "bool", + &bool_assign, + &bool_append + }; + + const value_type* bool_type = &value_traits::value_type; + + // string value + // + bool value_traits:: + assign (name& n) + { + // The below code is quite convoluted because we don't want to + // modify the name until we know it good (if it is not, then it + // will most likely be printed by the caller in diagnostics). + + // Suspend project qualification. + // + const string* p (n.proj); + n.proj = nullptr; + + // Convert directory to string. + // + if (n.directory ()) + { + n.value = std::move (n.dir).string (); // Move string out of path. + + // Add / back to the end of the path unless it is already there. + // Note that the string cannot be empty (n.directory () would + // have been false). + // + if (!dir_path::traits::is_separator (n.value[n.value.size () - 1])) + n.value += '/'; + } + + if (!n.simple ()) + { + n.proj = p; // Restore. + return false; + } + + // Convert project qualification to its string representation. + // + if (p != nullptr) + { + string s (*p); + s += '%'; + s += n.value; + s.swap (n.value); + } + + return true; + } + + static bool + string_assign (names& v, const variable& var) + { + // Verify/convert the value is/to a single simple name. + // + if (v.empty ()) + { + v.emplace_back (name ()); // Canonical empty string representation. + return false; + } + else if (v.size () == 1) + { + name& n (v.front ()); + + if (value_traits::assign (n)) + return !n.value.empty (); + } + + fail << "invalid string variable '" << var.name << "' value '" << v << "'"; + return false; + } + + static bool + string_append (names& v, names a, const variable& var) + { + // Translate append to string concatenation. + // + string_assign (a, var); // Verify/convert value is/to string. + + if (v.front ().value.empty ()) + v = move (a); + else + v.front ().value += a.front ().value; + + return !v.front ().value.empty (); + } + + const value_type value_traits::value_type + { + "string", + &string_assign, + &string_append + }; + + const value_type* string_type = &value_traits::value_type; + + // dir_path value + // + bool value_traits:: + assign (name& n) + { + if (n.directory ()) + return true; + + if (n.simple ()) + { + try + { + n.dir = n.empty () ? dir_path () : dir_path (move (n.value)); + n.value.clear (); + return true; + } + catch (const invalid_path&) {} // Fall through. + } + + return false; + } + + static bool + dir_path_assign (names& v, const variable& var) + { + // Verify/convert the value is/to a single directory name. + // + if (v.empty ()) + { + v.emplace_back (dir_path ()); // Canonical empty path representation. + return false; + } + else if (v.size () == 1) + { + name& n (v.front ()); + + if (value_traits::assign (n)) + return !n.dir.empty (); + } + + fail << "invalid dir_path variable '" << var.name << "' " + << "value '" << v << "'"; + return false; + } + + static bool + dir_path_append (names& v, names a, const variable& var) + { + // Translate append to path concatenation. + // + dir_path_assign (a, var); // Verify/convert value is/to dir_path. + + dir_path& d (a.front ().dir); + if (d.relative ()) + return !(v.front ().dir /= d).empty (); + else + fail << "append of absolute path '" << d << "' to dir_path variable " + << var.name; + + return false; + } + + const value_type value_traits::value_type + { + "dir_path", + &dir_path_assign, + &dir_path_append + }; + + const value_type* dir_path_type = &value_traits::value_type; + + // name value + // + static bool + name_assign (names& v, const variable& var) + { + // Verify the value is a single name. + // + if (v.size () == 1) + return v.front ().empty (); + + fail << "invalid string variable '" << var.name << "' value '" << v << "'"; + return false; + } + + static bool + name_append (names&, names, const variable& var) + { + fail << "append to name variable '" << var.name << "'"; + return false; + } + + const value_type value_traits::value_type + { + "name", + &name_assign, + &name_append + }; + + const value_type* name_type = &value_traits::value_type; + + // vector value + // + const value_type* strings_type = &value_traits::value_type; + const value_type* dir_paths_type = &value_traits::value_type; + const value_type* names_type = &value_traits::value_type; + + // variable_set + // + variable_pool var_pool; + + // variable_type_map + // + lookup variable_type_map:: + lookup (const target_type& type, + const string& name, + const variable& var) const + { + using result = build2::lookup; + + // Search across target type hierarchy. + // + for (auto tt (&type); tt != nullptr; tt = tt->base) + { + auto i (find (*tt)); + + if (i == end ()) + continue; + + // Try to match the pattern, starting from the longest values + // so that the more "specific" patterns (i.e., those that cover + // fewer characters with the wildcard) take precedence. See + // tests/variable/type-pattern. + // + const variable_pattern_map& m (i->second); + + for (auto j (m.rbegin ()); j != m.rend (); ++j) + { + const string& p (j->first); + + size_t nn (name.size ()); + size_t pn (p.size ()); + + if (nn < pn - 1) // One for '*'. + continue; + + size_t w (p.find ('*')); + assert (w != string::npos); + + // Compare prefix. + // + if (w != 0 && + name.compare (0, w, p, 0, w) != 0) + continue; + + ++w; // First suffix character. + pn -= w; // Suffix length. + + // Compare suffix. + // + if (pn != 0 && + name.compare (nn - pn, pn, p, w, pn) != 0) + continue; + + // Ok, this pattern matches. But is there a variable? + // + if (const value* v = j->second.find (var)) + { + //@@ TODO: should we detect ambiguity? 'foo-*' '*-foo' and + // 'foo-foo'? Right now the last defined will be used. + // + return result (v, &j->second); + } + } + } + + return result (); + } +} diff --git a/build2/variable.ixx b/build2/variable.ixx new file mode 100644 index 0000000..2933deb --- /dev/null +++ b/build2/variable.ixx @@ -0,0 +1,398 @@ +// file : build2/variable.ixx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +namespace build2 +{ + // value + // + template + inline void + assign (value& v, const variable& var) + { + auto t (&value_traits::value_type); + + if (v.type != t) + assign (v, t, var); + } + + template + inline typename value_traits::type + as (value& v) + { + return value_traits::as (v); + } + + template + inline typename value_traits::const_type + as (const value& v) + { + return value_traits::as (v); + } + + template + inline bool + assign (name& n) + { + return value_traits::assign (n); + } + + template + inline typename value_traits::type + as (name& n) + { + return value_traits::as (n); + } + + template + inline typename value_traits::const_type + as (const name& n) + { + return value_traits::as (n); + } + + template + inline value& value:: + operator= (T v) + { + value_traits::assign (*this, std::move (v)); + return *this; + } + + template + inline value& value:: + operator+= (T v) + { + value_traits::append (*this, std::move (v)); + return *this; + } + + inline void value:: + assign (names v, const variable& var) + { + data_ = std::move (v); + state_ = (type != nullptr && type->assign != nullptr + ? type->assign (data_, var) + : !data_.empty ()) + ? state_type::filled + : state_type::empty; + } + + // bool value + // + inline bool_value value_traits:: + as (value& v) + { + assert (v.type == bool_type); + return bool_value (v.data_.front ()); + } + + inline bool_value value_traits:: + as (const value& v) + { + assert (v.type == bool_type); + return bool_value (v.data_.front ()); + } + + inline void value_traits:: + assign (value& v, bool x) + { + if (v.null ()) + { + if (v.type == nullptr) + v.type = bool_type; + v.data_.emplace_back (name ()); + v.state_ = value::state_type::empty; + } + + as (v) = x; + v.state_ = value::state_type::filled; + } + + inline void value_traits:: + append (value& v, bool x) + { + if (v.null ()) + assign (v, x); + else + as (v) += x; // Cannot be empty. + } + + // string value + // + inline std::string& value_traits:: + as (value& v) + { + assert (v.type == string_type); + return v.data_.front ().value; + } + + inline const std::string& value_traits:: + as (const value& v) + { + assert (v.type == string_type); + return v.data_.front ().value; + } + + inline void value_traits:: + assign (value& v, std::string x) + { + if (v.null ()) + { + if (v.type == nullptr) + v.type = string_type; + v.data_.emplace_back (name ()); + v.state_ = value::state_type::empty; + } + + v.state_ = (as (v) = std::move (x)).empty () + ? value::state_type::empty + : value::state_type::filled; + } + + inline void value_traits:: + append (value& v, std::string x) + { + if (v.null ()) + assign (v, std::move (x)); + else + v.state_ = (as (v) += std::move (x)).empty () + ? value::state_type::empty + : value::state_type::filled; + } + + // dir_path value + // + inline dir_path& value_traits:: + as (value& v) + { + assert (v.type == dir_path_type); + return v.data_.front ().dir; + } + + inline const dir_path& value_traits:: + as (const value& v) + { + assert (v.type == dir_path_type); + return v.data_.front ().dir; + } + + inline void value_traits:: + assign (value& v, dir_path x) + { + if (v.null ()) + { + if (v.type == nullptr) + v.type = dir_path_type; + v.data_.emplace_back (name ()); + v.state_ = value::state_type::empty; + } + + v.state_ = (as (v) = std::move (x)).empty () + ? value::state_type::empty + : value::state_type::filled; + } + + inline void value_traits:: + append (value& v, dir_path x) + { + if (v.null ()) + assign (v, std::move (x)); + else + v.state_ = (as (v) /= std::move (x)).empty () + ? value::state_type::empty + : value::state_type::filled; + } + + // name value + // + inline name& value_traits:: + as (value& v) + { + assert (v.type == name_type); + return v.data_.front (); + } + + inline const name& value_traits:: + as (const value& v) + { + assert (v.type == name_type); + return v.data_.front (); + } + + inline void value_traits:: + assign (value& v, name x) + { + if (v.null ()) + { + if (v.type == nullptr) + v.type = name_type; + v.data_.emplace_back (name ()); + v.state_ = value::state_type::empty; + } + + v.state_ = (as (v) = std::move (x)).empty () + ? value::state_type::empty + : value::state_type::filled; + } + + // vector value + // + template + inline vector_value& vector_value:: + assign (std::vector v) + { + d->clear (); + d->insert (d->end (), + std::make_move_iterator (v.begin ()), + std::make_move_iterator (v.end ())); + return *this; + } + + template + template + inline vector_value& vector_value:: + assign (const vector_value& v) + { + d->clear (); + d->insert (d->end (), v.begin (), v.end ()); + return *this; + } + + template + template + inline vector_value& vector_value:: + append (const vector_value& v) + { + d->insert (d->end (), v.begin (), v.end ()); + return *this; + } + + template + inline vector_value value_traits>:: + as (value& v) + { + assert (v.type == &value_traits>::value_type); + return vector_value (v.data_); + } + + template + inline vector_value value_traits>:: + as (const value& v) + { + assert (v.type == &value_traits>::value_type); + return vector_value (v.data_); + } + + template + template + inline void value_traits>:: + assign (value& v, V x) + { + if (v.null ()) + { + if (v.type == nullptr) + v.type = &value_traits>::value_type; + v.state_ = value::state_type::empty; + } + + v.state_ = (as (v).assign (std::move (x))).empty () + ? value::state_type::empty + : value::state_type::filled; + } + + template + template + inline void value_traits>:: + append (value& v, V x) + { + if (v.null ()) + assign (v, std::move (x)); + else + v.state_ = (as (v).append (std::move (x))).empty () + ? value::state_type::empty + : value::state_type::filled; + } + + // map value + // + template + inline map_value value_traits>:: + as (value& v) + { + assert ((v.type == &value_traits>::value_type)); + return map_value (v.data_); + } + + template + inline map_value value_traits>:: + as (const value& v) + { + assert ((v.type == &value_traits>::value_type)); + return map_value (v.data_); + } + + template + template + inline void value_traits>:: + assign (value& v, M x) + { + if (v.null ()) + { + if (v.type == nullptr) + v.type = &value_traits>::value_type; + v.state_ = value::state_type::empty; + } + + v.state_ = (as (v).assign (std::move (x))).empty () + ? value::state_type::empty + : value::state_type::filled; + } + + template + template + inline void value_traits>:: + append (value& v, M x) + { + if (v.null ()) + assign (v, std::move (x)); + else + v.state_ = (as (v).append (std::move (x))).empty () + ? value::state_type::empty + : value::state_type::filled; + } + + // variable_map::iterator_adapter + // + template + inline typename I::reference variable_map::iterator_adapter:: + operator* () const + { + auto& r (I::operator* ()); + const variable& var (r.first); + auto& val (r.second); + + // First access after being assigned a type? + // + if (var.type != nullptr && val.type != var.type) + build2::assign (const_cast (val), var.type, var); + + return r; + } + + template + inline typename I::pointer variable_map::iterator_adapter:: + operator-> () const + { + auto p (I::operator-> ()); + const variable& var (p->first); + auto& val (p->second); + + // First access after being assigned a type? + // + if (var.type != nullptr && val.type != var.type) + build2::assign (const_cast (val), var.type, var); + + return p; + } +} diff --git a/build2/variable.txx b/build2/variable.txx new file mode 100644 index 0000000..907072e --- /dev/null +++ b/build2/variable.txx @@ -0,0 +1,168 @@ +// file : build2/variable.txx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include // make_move_iterator() + +#include + +namespace build2 +{ + // vector value + // + template + bool + vector_assign (names& v, const variable& var) + { + // Verify each element has valid value of T. + // + for (name& n: v) + { + if (!assign (n)) + fail << "invalid " << value_traits::value_type.name << " element " + << "'" << n << "' in variable '" << var.name << "'"; + } + + return !v.empty (); + } + + template + bool + vector_append (names& v, names a, const variable& var) + { + // Verify that what we are appending is valid. + // + vector_assign (a, var); + + if (v.empty ()) + v = move (a); + else + v.insert (v.end (), + std::make_move_iterator (a.begin ()), + std::make_move_iterator (a.end ())); + + return !v.empty (); + } + + template + const std::string value_traits>::type_name = std::string ( + value_traits::value_type.name) + 's'; + + template + const value_type value_traits>::value_type + { + value_traits>::type_name.c_str (), + &vector_assign, + &vector_append + }; + + // map value + // + template + map_value& map_value:: + assign (std::map m) + { + d->clear (); + for (auto& p: m) + { + d->emplace_back (p.first); // Const, can't move. + d->back ().pair = '='; + d->emplace_back (std::move (p.second)); + } + + return *this; + } + + template + auto map_value:: + find (const K& k) -> iterator + { + // @@ Scan backwards to handle duplicates. + // + for (auto i (d->rbegin ()); i != d->rend (); ++i) + if (as (*++i) == k) + return iterator (--(i.base ())); + + return end (); + } + + template + auto map_value:: + find (const K& k) const -> const_iterator + { + // @@ Scan backwards to handle duplicates. + // + for (auto i (d->rbegin ()); i != d->rend (); ++i) + if (as (*++i) == k) + return const_iterator (--(i.base ())); + + return end (); + } + + template + bool + map_assign (names& v, const variable& var) + { + // Verify we have a sequence of pairs and each lhs/rhs is a valid + // value of K/V. + // + for (auto i (v.begin ()); i != v.end (); ++i) + { + if (i->pair == '\0') + fail << value_traits>::value_type.name << " key-value " + << "pair expected instead of '" << *i << "' " + << "in variable '" << var.name << "'"; + + if (!assign (*i)) + fail << "invalid " << value_traits::value_type.name << " key " + << "'" << *i << "' in variable '" << var.name << "'"; + + ++i; // Got to have the second half of the pair. + + if (!assign (*i)) + fail << "invalid " << value_traits::value_type.name << " value " + << "'" << *i << "' in variable '" << var.name << "'"; + } + + //@@ When doing sorting, note that assign() can convert the + // value. + + //@@ Is sorting really the right trade-off (i.e., insertion + // vs search)? Perhaps linear search is ok? + + return !v.empty (); + } + + template + bool + map_append (names& v, names a, const variable& var) + { + //@@ Not weeding out duplicates. + + // Verify that what we are appending is valid. + // + map_assign (a, var); + + if (v.empty ()) + v = move (a); + else + v.insert (v.end (), + std::make_move_iterator (a.begin ()), + std::make_move_iterator (a.end ())); + + return !v.empty (); + } + + template + const std::string value_traits>::type_name = std::string ( + value_traits::value_type.name) + '_' + + value_traits::value_type.name + "_map"; + + template + const value_type value_traits>::value_type + { + value_traits>::type_name.c_str (), + &map_assign, + &map_append + }; +} diff --git a/build2/version b/build2/version new file mode 100644 index 0000000..3339078 --- /dev/null +++ b/build2/version @@ -0,0 +1,37 @@ +// file : build2/version -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_VERSION // Note: using the version macro itself. + +#include // LIBBUTL_VERSION + +// Version format is AABBCCDD where +// +// AA - major version number +// BB - minor version number +// CC - bugfix version number +// DD - alpha / beta (DD + 50) version number +// +// When DD is not 00, 1 is subtracted from AABBCC. For example: +// +// Version AABBCCDD +// 2.0.0 02000000 +// 2.1.0 02010000 +// 2.1.1 02010100 +// 2.2.0-a1 02019901 +// 3.0.0-b2 02999952 +// +#define BUILD2_VERSION 10000 +#define BUILD2_VERSION_STR "0.1.0" + +// Generally, we expect minor versions to be source code backwards- +// compatible, thought we might have a minimum version requirement. +// +// Note: does not apply during early development. +// +#if LIBBUTL_VERSION != 10000 +# error incompatible libbutl version +#endif + +#endif // BUILD2_VERSION -- cgit v1.1