diff options
author | Boris Kolpackov <boris@codesynthesis.com> | 2016-01-05 11:55:15 +0200 |
---|---|---|
committer | Boris Kolpackov <boris@codesynthesis.com> | 2016-01-05 11:55:15 +0200 |
commit | 9fb791e9fad6c63fc1dac49f4d05ae63b8a3db9b (patch) | |
tree | d60322d4382ca5f97b676c5abe2e39524f35eab4 /build | |
parent | f159b1dac68c8714f7ba71ca168e3b695891aad9 (diff) |
Rename build directory/namespace to build2
Diffstat (limited to 'build')
112 files changed, 0 insertions, 22307 deletions
diff --git a/build/.gitignore b/build/.gitignore index 62f3460..225c27f 100644 --- a/build/.gitignore +++ b/build/.gitignore @@ -1,3 +1 @@ -b -b-* config.build diff --git a/build/algorithm b/build/algorithm deleted file mode 100644 index d144d6c..0000000 --- a/build/algorithm +++ /dev/null @@ -1,222 +0,0 @@ -// file : build/algorithm -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD_ALGORITHM -#define BUILD_ALGORITHM - -#include <string> -#include <utility> // pair - -#include <build/types> -#include <build/target> -#include <build/operation> - -namespace build -{ - class scope; - class prerequisite; - class prerequisite_key; - - // The default prerequisite search implementation. It first calls the - // target-type-specific search function. If that doesn't yeld anything, - // it creates a new target. - // - target& - search (prerequisite&); - - // As above but specify the prerequisite to search as a key. - // - target& - search (const prerequisite_key&); - - // As above but override the target type. Useful for searching for - // target group members where we need to search for a different - // target type. - // - target& - search (const target_type&, const prerequisite_key&); - - // As above but specify the prerequisite to search as individual - // key components. - // - target& - search (const target_type& type, - const dir_path& dir, - const std::string& name, - const std::string* ext, - scope*); - - // As above but specify the target type as template argument. - // - template <typename T> - T& - search (const dir_path& dir, - const std::string& name, - const std::string* ext, - scope*); - - // Search for a target identified by the name. The semantics - // is "as if" we first created a prerequisite based on this - // name in exactly the same way as the parser would and then - // searched based on this prerequisite. - // - target& - search (name, scope&); - - // Match and apply a rule to the action/target with ambiguity - // detection. Increment the target's dependents count, which - // means that you should call this function with the intent - // to also call execute(). In case of optimizations that would - // avoid calling execute(), call unmatch() to indicate this. - // - void - match (action, target&); - - // Note that calling this function only makes sense if the - // target itself doesn't have its own dependents. - // - void - unmatch (action, target&); - - // Match (but do not apply) a rule to the action/target with - // ambiguity detection. Note that this function does not touch - // the dependents count. - // - void - match_only (action, target&); - - // Match a "delegate rule" from withing another rules' apply() - // function. Return recipe and recipe action (if any). Note - // that unlike match(), this call doesn't increment the - // dependents count. See also the companion execute_delegate(). - // - std::pair<recipe, action> - match_delegate (action, target&); - - // The standard prerequisite search and match implementations. They call - // search_and_match_*() versions below passing non-empty directory for - // the clean operation. - // - void - search_and_match_prerequisites (action, target&); - - // If we are cleaning, this function doesn't go into group members, - // as an optimization (the group should clean everything up). - // - void - search_and_match_prerequisite_members (action, target&); - - // The actual prerequisite search and match implementations. They call - // search() and then match() for each prerequisite in a loop. If this - // target is a member of a group, then they first do this to the group's - // prerequisites. - // - // If the directory argument is not empty, then they ignore (do not - // match) prerequisites that are not in the same or its subdirectory. - // - void - search_and_match_prerequisites (action, target&, const dir_path&); - - void - search_and_match_prerequisite_members (action, target&, const dir_path&); - - // Unless already available, match, and, if necessary, execute the group - // in order to obtain its members list. Note that even after that the - // member's list might still not be available (e.g., if some wildcard/ - // fallback rule matched). - // - group_view - resolve_group_members (action, target&); - - // Inject dependency on the parent directory's fsdir{}, unless it is - // the project's out_root (or is outside of any project; say, for - // example, an install directory). Normally this function is called - // from the rule's apply() function. - // - void - inject_parent_fsdir (action, target&); - - // Execute the action on target, assuming a rule has been matched - // and the recipe for this action has been set. This is the default - // executor implementation. Decrements the dependents count. - // - target_state - execute (action, target&); - - // Execute the recipe obtained with match_delegate(). Note that - // the target's state is neither checked nor updated by this - // function. In other words, the appropriate usage is to call - // this function from another recipe and to factor the obtained - // state into the one returned. - // - target_state - execute_delegate (const recipe&, action, target&); - - // A special version of the above that should be used for "direct" - // and "now" execution, that is, side-stepping the normal target- - // prerequisite relationship (so no dependents count is decremented) - // and execution order (so this function will never return postponed - // target state). - // - target_state - execute_direct (action, target&); - - // The default prerequisite execute implementation. It calls execute() - // on each non-ignored (non-NULL) prerequisite target in a loop. If this - // target is a member of a group, then it first does this to the group's - // prerequisites. Returns target_state::changed if any of them were - // changed and target_state::unchanged otherwise. Note that this - // function can be used as a recipe. - // - target_state - execute_prerequisites (action, target&); - - // As above but iterates over the prerequisites in reverse. - // - target_state - reverse_execute_prerequisites (action, target&); - - // A version of the above that also determines whether the action - // needs to be executed on the target based on the passed mtime - // timestamp. - // - // Note that because we use mtime, this function should normally - // only be used in the perform_update action. - // - bool - execute_prerequisites (action, target&, const timestamp&); - - // Another version of the above that does two extra things for the - // caller: it determines whether the action needs to be executed on - // the target based on the passed timestamp and, if so, finds a - // prerequisite of the specified type (e.g., a source file). If - // there are multiple prerequisites of this type, then the last - // is returned. - // - template <typename T> - T* - execute_prerequisites (action, target&, const timestamp&); - - // Return noop_recipe instead of using this function directly. - // - target_state - noop_action (action, target&); - - // Default action implementation which forwards to the prerequisites. - // Use default_recipe instead of using this function directly. - // - target_state - default_action (action, target&); - - // Standard perform(clean) action implementation for the file target - // or derived. - // - target_state - perform_clean (action, target&); -} - -#include <build/algorithm.ixx> -#include <build/algorithm.txx> - -#endif // BUILD_ALGORITHM diff --git a/build/algorithm.cxx b/build/algorithm.cxx deleted file mode 100644 index a2ee7c0..0000000 --- a/build/algorithm.cxx +++ /dev/null @@ -1,504 +0,0 @@ -// file : build/algorithm.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <build/algorithm> - -#include <memory> // unique_ptr -#include <cstddef> // size_t -#include <utility> // move -#include <cassert> - -#include <butl/utility> // reverse_iterate - -#include <build/scope> -#include <build/target> -#include <build/prerequisite> -#include <build/rule> -#include <build/file> // import() -#include <build/search> -#include <build/context> -#include <build/utility> -#include <build/diagnostics> - -using namespace std; -using namespace butl; - -namespace build -{ - target& - search (const prerequisite_key& pk) - { - // If this is a project-qualified prerequisite, then this - // is import's business. - // - if (pk.proj != nullptr) - return import (pk); - - if (target* t = pk.tk.type->search (pk)) - return *t; - - return create_new_target (pk); - } - - target& - search (name n, scope& s) - { - const string* e; - const target_type* tt (s.find_target_type (n, e)); - - if (tt == nullptr) - fail << "unknown target type " << n.type << " in name " << n; - - n.dir.normalize (); - - return search (*tt, move (n.dir), move (n.value), e, &s); - } - - pair<const rule*, match_result> - match_impl (action a, target& t, bool apply) - { - pair<const rule*, match_result> r; - - // By default, clear the resolved targets list before calling - // match(). The rule is free to modify this list in match() - // (provided that it matches) in order to, for example, prepare - // it for apply(). - // - t.reset (a); - - // If this is a nested operation, first try the outer operation. - // This allows a rule to implement a "precise match", that is, - // both inner and outer operations match. - // - for (operation_id oo (a.outer_operation ()), io (a.operation ()), - o (oo != 0 ? oo : io); o != 0; o = (oo != 0 ? io : 0)) - { - // Adjust action for recipe: on the first iteration we want it - // {inner, outer} (which is the same as 'a') while on the second - // -- {inner, 0}. Note that {inner, 0} is the same or "stronger" - // (i.e., overrides; see action::operator<()) than 'a'. This - // allows "unconditional inner" to override "inner for outer" - // recipes. - // - action ra (a.meta_operation (), io, o != oo ? 0 : oo); - - scope& bs (t.base_scope ()); - - for (auto tt (&t.type ()); tt != nullptr; tt = tt->base) - { - // Search scopes outwards, stopping at the project root. - // - for (const scope* s (&bs); - s != nullptr; - s = s->root () ? global_scope : s->parent_scope ()) - { - const operation_rule_map* om (s->rules[a.meta_operation ()]); - - if (om == nullptr) - continue; // No entry for this meta-operation id. - - // First try the map for the actual operation. If that - // doesn't yeld anything, try the wildcard map. - // - for (size_t oi (o), oip (o); oip != 0; oip = oi, oi = 0) - { - const target_type_rule_map* ttm ((*om)[oi]); - - if (ttm == nullptr) - continue; // No entry for this operation id. - - if (ttm->empty ()) - continue; // Empty map for this operation id. - - auto i (ttm->find (tt)); - - if (i == ttm->end () || i->second.empty ()) - continue; // No rules registered for this target type. - - const auto& rules (i->second); // Hint map. - - // @@ TODO - // - // Different rules can be used for different operations (update - // vs test is a good example). So, at some point, we will probably - // have to support a list of hints or even an operation-hint map - // (e.g., 'hint=cxx test=foo' if cxx supports the test operation - // but we want the foo rule instead). This is also the place where - // the '{build clean}=cxx' construct (which we currently do not - // support) can come handy. - // - // Also, ignore the hint (that is most likely ment for a different - // operation) if this is a unique match. - // - string hint; - auto rs (rules.size () == 1 - ? make_pair (rules.begin (), rules.end ()) - : rules.find_prefix (hint)); - - for (auto i (rs.first); i != rs.second; ++i) - { - const string& n (i->first); - const rule& ru (i->second); - - match_result m; - { - auto g ( - make_exception_guard ( - [ra, &t, &n]() - { - info << "while matching rule " << n << " to " - << diag_do (ra, t); - })); - - if (!(m = ru.match (ra, t, hint))) - continue; - - if (!m.recipe_action.valid ()) - m.recipe_action = ra; // Default, if not set. - } - - // Do the ambiguity test. - // - bool ambig (false); - - diag_record dr; - - for (++i; i != rs.second; ++i) - { - const string& n1 (i->first); - const rule& ru1 (i->second); - - { - auto g ( - make_exception_guard ( - [ra, &t, &n1]() - { - info << "while matching rule " << n1 << " to " - << diag_do (ra, t); - })); - - if (!ru1.match (ra, t, hint)) - continue; - } - - if (!ambig) - { - dr << fail << "multiple rules matching " - << diag_doing (ra, t) - << info << "rule " << n << " matches"; - ambig = true; - } - - dr << info << "rule " << n1 << " also matches"; - } - - if (!ambig) - { - ra = m.recipe_action; // Use custom, if set. - - if (apply) - { - auto g ( - make_exception_guard ( - [ra, &t, &n]() - { - info << "while applying rule " << n << " to " - << diag_do (ra, t); - })); - - // @@ We could also allow the rule to change the recipe - // action in apply(). Could be useful with delegates. - // - t.recipe (ra, ru.apply (ra, t, m)); - } - else - { - r.first = &ru; - r.second = move (m); - } - - return r; - } - else - dr << info << "use rule hint to disambiguate this match"; - } - } - } - } - } - - diag_record dr; - dr << fail << "no rule to " << diag_do (a, t); - - if (verb < 4) - dr << info << "re-run with --verbose 4 for more information"; - - return r; - } - - group_view - resolve_group_members_impl (action a, target& g) - { - group_view r; - - // Unless we already have a recipe, try matching the target to - // the rule. - // - if (!g.recipe (a)) - { - auto rp (match_impl (a, g, false)); - - r = g.group_members (a); - if (r.members != nullptr) - return r; - - // That didn't help, so apply the rule and go to the building - // phase. - // - const match_result& mr (rp.second); - g.recipe (mr.recipe_action, rp.first->apply (mr.recipe_action, g, mr)); - } - - // Note that we use execute_direct() rather than execute() here to - // sidestep the dependents count logic. In this context, this is by - // definition the first attempt to execute this rule (otherwise we - // would have already known the members list) and we really do need - // to execute it now. - // - execute_direct (a, g); - - r = g.group_members (a); - return r; // Might still be unresolved. - } - - void - search_and_match_prerequisites (action a, target& t, const dir_path& d) - { - const bool e (d.empty ()); - - for (prerequisite p: group_prerequisites (t)) - { - target& pt (search (p)); - - if (e || pt.dir.sub (d)) - { - match (a, pt); - t.prerequisite_targets.push_back (&pt); - } - } - } - - void - search_and_match_prerequisite_members (action a, - target& t, - const dir_path& d) - { - const bool e (d.empty ()); - - for (prerequisite_member p: group_prerequisite_members (a, t)) - { - target& pt (p.search ()); - - if (e || pt.dir.sub (d)) - { - match (a, pt); - t.prerequisite_targets.push_back (&pt); - } - } - } - - void - inject_parent_fsdir (action a, target& t) - { - tracer trace ("inject_parent_fsdir"); - - scope& s (t.base_scope ()); - scope* rs (s.root_scope ()); - - if (rs == nullptr) // Could be outside any project. - return; - - const dir_path& out_root (rs->out_path ()); - - // If t is a directory (name is empty), say foo/bar/, then - // t is bar and its parent directory is foo/. - // - const dir_path& d (t.name.empty () ? t.dir.directory () : t.dir); - - if (!d.sub (out_root) || d == out_root) - return; - - level6 ([&]{trace << "for " << t;}); - - fsdir& dt (search<fsdir> (d, string (), nullptr, &s)); - match (a, dt); - t.prerequisite_targets.emplace_back (&dt); - } - - target_state - execute_impl (action a, target& t) - { - // Implementation with some multi-threading ideas in mind. - // - switch (t.raw_state) - { - case target_state::group: // Means group's state is unknown. - case target_state::unknown: - case target_state::postponed: - { - auto g ( - make_exception_guard ( - [a, &t]() - { - t.raw_state = target_state::failed; - info << "while " << diag_doing (a, t); - })); - - target_state ts (t.recipe (a) (a, t)); - assert (ts != target_state::unknown && ts != target_state::failed); - - // Set the target's state unless it should be the group's state. - // - if (t.raw_state != target_state::group) - t.raw_state = ts; - - return ts; - } - case target_state::unchanged: - case target_state::changed: - // Should have been handled by inline execute(). - assert (false); - case target_state::failed: - break; - } - - throw failed (); - } - - target_state - execute_prerequisites (action a, target& t) - { - target_state r (target_state::unchanged); - - for (target* pt: t.prerequisite_targets) - { - if (pt == nullptr) // Skipped. - continue; - - r |= execute (a, *pt); - } - - return r; - } - - target_state - reverse_execute_prerequisites (action a, target& t) - { - target_state r (target_state::unchanged); - - for (target* pt: reverse_iterate (t.prerequisite_targets)) - { - if (pt == nullptr) // Skipped. - continue; - - r |= execute (a, *pt); - } - - return r; - } - - bool - execute_prerequisites (action a, target& t, const timestamp& mt) - { - bool e (mt == timestamp_nonexistent); - - for (target* pt: t.prerequisite_targets) - { - if (pt == nullptr) // Skipped. - continue; - - target_state ts (execute (a, *pt)); - - if (!e) - { - // If this is an mtime-based target, then compare timestamps. - // - if (auto mpt = dynamic_cast<const mtime_target*> (pt)) - { - timestamp mp (mpt->mtime ()); - - // What do we do if timestamps are equal? This can happen, for - // example, on filesystems that don't have subsecond resolution. - // There is not much we can do here except detect the case where - // the prerequisite was changed in this run which means the - // action must be executed on the target as well. - // - if (mt < mp || (mt == mp && ts == target_state::changed)) - e = true; - } - else - { - // Otherwise we assume the prerequisite is newer if it was changed. - // - if (ts == target_state::changed) - e = true; - } - } - } - - return e; - } - - target_state - noop_action (action, target&) - { - assert (false); // We shouldn't be called, see target::recipe(). - return target_state::unchanged; - } - - target_state - group_action (action a, target& t) - { - target_state r (execute (a, *t.group)); - - // Indicate to the standard execute() logic that this target's - // state comes from the group. - // - t.raw_state = target_state::group; - - return r; - } - - target_state - default_action (action a, target& t) - { - return current_mode == execution_mode::first - ? execute_prerequisites (a, t) - : reverse_execute_prerequisites (a, t); - } - - target_state - perform_clean (action a, target& t) - { - // The reverse order of update: first delete the file, then clean - // prerequisites. - // - file& ft (dynamic_cast<file&> (t)); - - target_state r (rmfile (ft.path (), ft) - ? target_state::changed - : target_state::unchanged); - - // Update timestamp in case there are operations after us that - // could use the information. - // - ft.mtime (timestamp_nonexistent); - - // Clean prerequisites. - // - r |= reverse_execute_prerequisites (a, t); - - return r; - } -} diff --git a/build/algorithm.ixx b/build/algorithm.ixx deleted file mode 100644 index 309f99b..0000000 --- a/build/algorithm.ixx +++ /dev/null @@ -1,197 +0,0 @@ -// file : build/algorithm.ixx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <utility> // pair - -#include <build/rule> -#include <build/prerequisite> -#include <build/context> - -namespace build -{ - inline target& - search (prerequisite& p) - { - if (p.target == nullptr) - p.target = &search (p.key ()); - - return *p.target; - } - - inline target& - search (const target_type& t, const prerequisite_key& k) - { - return search ( - prerequisite_key - {k.proj, {&t, k.tk.dir, k.tk.name, k.tk.ext}, k.scope}); - } - - inline target& - search (const target_type& type, - const dir_path& dir, - const std::string& name, - const std::string* ext, - scope* scope) - { - return search ( - prerequisite_key {nullptr, {&type, &dir, &name, &ext}, scope}); - } - - template <typename T> - inline T& - search (const dir_path& dir, - const std::string& name, - const std::string* ext, - scope* scope) - { - return static_cast<T&> (search (T::static_type, dir, name, ext, scope)); - } - - std::pair<const rule*, match_result> - match_impl (action, target&, bool apply); - - inline void - match (action a, target& t) - { - if (!t.recipe (a)) - match_impl (a, t, true); - - t.dependents++; - dependency_count++; - - // text << "M " << t << ": " << t.dependents << " " << dependency_count; - } - - inline void - unmatch (action, target& t) - { - // text << "U " << t << ": " << t.dependents << " " << dependency_count; - - assert (t.dependents != 0 && dependency_count != 0); - t.dependents--; - dependency_count--; - } - - inline void - match_only (action a, target& t) - { - if (!t.recipe (a)) - match_impl (a, t, false); - } - - inline std::pair<recipe, action> - match_delegate (action a, target& t) - { - auto rp (match_impl (a, t, false)); - const match_result& mr (rp.second); - return std::make_pair (rp.first->apply (mr.recipe_action, t, mr), - mr.recipe_action); - } - - group_view - resolve_group_members_impl (action, target&); - - inline group_view - resolve_group_members (action a, target& g) - { - group_view r (g.group_members (a)); - return r.members != nullptr ? r : resolve_group_members_impl (a, g); - } - - inline void - search_and_match_prerequisites (action a, target& t) - { - search_and_match_prerequisites ( - a, - t, - a.operation () != clean_id - ? dir_path () - : t.strong_scope ().out_path ()); - } - - inline void - search_and_match_prerequisite_members (action a, target& t) - { - if (a.operation () != clean_id) - search_and_match_prerequisite_members (a, t, dir_path ()); - else - // Note that here we don't iterate over members even for see- - // through groups since the group target should clean eveything - // up. A bit of an optimization. - // - search_and_match_prerequisites (a, t, t.strong_scope ().out_path ()); - } - - target_state - execute_impl (action, target&); - - inline target_state - execute (action a, target& t) - { - // text << "E " << t << ": " << t.dependents << " " << dependency_count; - - if (dependency_count != 0) // Re-examination of a postponed target? - { - assert (t.dependents != 0); - t.dependents--; - dependency_count--; - } - - switch (target_state ts = t.state ()) - { - case target_state::unchanged: - case target_state::changed: - return ts; - default: - { - // Handle the "last" execution mode. - // - // This gets interesting when we consider interaction with - // groups. It seem to make sense to treat group members as - // dependents of the group, so, for example, if we try to - // clean the group via three of its members, only the last - // attempt will actually execute the clean. This means that - // when we match a group member, inside we should also match - // the group in order to increment the dependents count. This - // seems to be a natural requirement: if we are delegating to - // the group, we need to find a recipe for it, just like we - // would for a prerequisite. - // - // Note that below we are going to change the group state - // to postponed. This is not a mistake: until we execute - // the recipe, we want to keep returning postponed. And - // once the recipe is executed, it will reset the state - // to group (see group_action()). To put it another way, - // the execution of this member is postponed, not of the - // group. - // - // One important invariant to keep in mind: the return - // value from execute() should always be the same as what - // would get returned by a subsequent call to state(). - // - if (current_mode == execution_mode::last && t.dependents != 0) - return (t.raw_state = target_state::postponed); - - return execute_impl (a, t); - } - } - } - - inline target_state - execute_delegate (const recipe& r, action a, target& t) - { - return r (a, t); - } - - inline target_state - execute_direct (action a, target& t) - { - switch (target_state ts = t.state ()) - { - case target_state::unchanged: - case target_state::changed: return ts; - default: return execute_impl (a, t); - } - } -} diff --git a/build/algorithm.txx b/build/algorithm.txx deleted file mode 100644 index 2490962..0000000 --- a/build/algorithm.txx +++ /dev/null @@ -1,58 +0,0 @@ -// file : build/algorithm.txx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -namespace build -{ - template <typename T> - T* - execute_prerequisites (action a, target& t, const timestamp& mt) - { - //@@ Can factor the bulk of it into a non-template code. Can - // either do a function template that will do dynamic_cast check - // or can scan the target type info myself. I think latter. - // - bool e (mt == timestamp_nonexistent); - T* r (nullptr); - - for (target* pt: t.prerequisite_targets) - { - if (pt == nullptr) // Skip ignored. - continue; - - target_state ts (execute (a, *pt)); - - if (!e) - { - // If this is an mtime-based target, then compare timestamps. - // - if (auto mpt = dynamic_cast<const mtime_target*> (pt)) - { - timestamp mp (mpt->mtime ()); - - // What do we do if timestamps are equal? This can happen, for - // example, on filesystems that don't have subsecond resolution. - // There is not much we can do here except detect the case where - // the prerequisite was changed in this run which means the - // action must be executed on the target as well. - // - if (mt < mp || (mt == mp && ts == target_state::changed)) - e = true; - } - else - { - // Otherwise we assume the prerequisite is newer if it was changed. - // - if (ts == target_state::changed) - e = true; - } - } - - if (T* tmp = dynamic_cast<T*> (pt)) - r = tmp; - } - - assert (r != nullptr); - return e ? r : nullptr; - } -} diff --git a/build/b.cxx b/build/b.cxx deleted file mode 100644 index d72fdfb..0000000 --- a/build/b.cxx +++ /dev/null @@ -1,855 +0,0 @@ -// file : build/b.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <time.h> // tzset() -#include <string.h> // strerror() - -#include <stdlib.h> // getenv() -#include <unistd.h> // getuid() -#include <sys/types.h> // uid_t -#include <pwd.h> // struct passwd, getpwuid() - -#include <sstream> -#include <cassert> -#include <typeinfo> -#include <iostream> -#include <system_error> - -#include <butl/filesystem> - -#include <build/version> - -#include <build/types> -#include <build/spec> -#include <build/operation> -#include <build/scope> -#include <build/target> -#include <build/prerequisite> -#include <build/rule> -#include <build/file> -#include <build/module> -#include <build/algorithm> -#include <build/diagnostics> -#include <build/context> -#include <build/utility> -#include <build/variable> - -#include <build/token> -#include <build/lexer> -#include <build/parser> - -#include <build/options> - -using namespace std; - -#include <build/config/module> -#include <build/dist/module> -#include <build/bin/module> -#include <build/cxx/module> -#include <build/cli/module> -#include <build/test/module> -#include <build/install/module> - -using namespace build; - -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 " << BUILD_VERSION_STR<< endl - << "libbutl " << LIBBUTL_VERSION_STR << endl - << "Copyright (c) 2014-2015 Code Synthesis Ltd" << endl - << "This is free software released under the MIT license." << endl; - return 0; - } - - // Help. - // - if (ops.help ()) - { - ostream& o (cout); - - o << "Usage: " << argv[0] << " [options] [variables] [buildspec]" << endl - << "Options:" << endl; - - options::print_usage (o); - return 0; - } - - // Diagnostics verbosity. - // - verb = ops.verbose_specified () - ? ops.verbose () - : ops.v () ? 2 : ops.q () ? 0 : 1; - - // Initialize time conversion data that is used by localtime_r(). - // - tzset (); - - // Register builtin modules. - // - builtin_modules["config"] = module_functions {&config::config_boot, - &config::config_init}; - builtin_modules["dist"] = module_functions {&dist::dist_boot, - &dist::dist_init}; - builtin_modules["test"] = module_functions {&test::test_boot, - &test::test_init}; - builtin_modules["install"] = module_functions {&install::install_boot, - &install::install_init}; - - builtin_modules["bin"] = module_functions {nullptr, &bin::bin_init}; - builtin_modules["cxx"] = module_functions {nullptr, &cxx::cxx_init}; - builtin_modules["cli"] = module_functions {nullptr, &cli::cli_init}; - - // Figure out work and home directories. - // - work = dir_path::current (); - - if (const char* h = getenv ("HOME")) - home = dir_path (h); - else - { - struct passwd* pw (getpwuid (getuid ())); - - if (pw == nullptr) - { - const char* msg (strerror (errno)); - fail << "unable to determine home directory: " << msg; - } - - home = dir_path (pw->pw_dir); - } - - if (verb >= 5) - { - trace << "work dir: " << work; - trace << "home dir: " << home; - } - - // Initialize the dependency state. - // - reset (); - - // Parse command line variables. They should come before the - // buildspec. - // - int argi (1); - for (; argi != argc; argi++) - { - const char* s (argv[argi]); - - istringstream is (s); - is.exceptions (istringstream::failbit | istringstream::badbit); - lexer l (is, "<cmdline>"); - token t (l.next ()); - - if (t.type == token_type::eos) - continue; // Whitespace-only argument. - - // Unless this is a name followed by = or +=, assume it is - // a start of the buildspec. - // - if (t.type != token_type::name) - break; - - token_type tt (l.next ().type); - - if (tt != token_type::equal && - tt != token_type::equal_plus && - tt != token_type::plus_equal) - break; - - parser p; - t = p.parse_variable (l, *global_scope, t.value, tt); - - if (t.type != token_type::eos) - fail << "unexpected " << t << " in variable " << s; - } - - // Parse the buildspec. - // - buildspec bspec; - { - // Merge all the individual buildspec arguments into a single - // string. Instead, we could also parse them individually ( - // and merge the result). The benefit of doing it this way - // is potentially better diagnostics (i.e., we could have - // used <buildspec-1>, <buildspec-2> to give the idea about - // which argument is invalid). - // - string s; - for (; argi != argc;) - { - s += argv[argi]; - if (++argi != argc) - s += ' '; - } - - try - { - istringstream is (s); - is.exceptions (istringstream::failbit | istringstream::badbit); - - parser p; - bspec = p.parse_buildspec (is, "<buildspec>"); - } - catch (const istringstream::failure&) - { - fail << "unable to parse buildspec '" << s << "'"; - } - } - - level5 ([&]{trace << "buildspec: " << bspec;}); - - if (bspec.empty ()) - bspec.push_back (metaopspec ()); // Default meta-operation. - - for (metaopspec& ms: bspec) - { - if (ms.empty ()) - ms.push_back (opspec ()); // Default operation. - - meta_operation_id mid (0); // Not yet translated. - const meta_operation_info* mif (nullptr); - - bool lifted (false); // See below. - - for (opspec& os: ms) - { - const location l ("<buildspec>", 1, 0); //@@ TODO - - if (os.empty ()) // Default target: dir{}. - os.push_back (targetspec (name ("dir", string ()))); - - operation_id oid (0); // Not yet translated. - const operation_info* oif (nullptr); - - operation_id pre_oid (0); - const operation_info* pre_oif (nullptr); - - operation_id post_oid (0); - const operation_info* post_oif (nullptr); - - // We do meta-operation and operation batches sequentially (no - // parallelism). But multiple targets in an operation batch - // can be done in parallel. - // - action_targets tgs; - tgs.reserve (os.size ()); - - // If the previous operation was lifted to meta-operation, - // end the meta-operation batch. - // - if (lifted) - { - if (mif->meta_operation_post != nullptr) - mif->meta_operation_post (); - - level5 ([&]{trace << "end meta-operation batch " << mif->name - << ", id " << static_cast<uint16_t> (mid);}); - - mid = 0; - lifted = false; - } - - for (targetspec& ts: os) - { - name& tn (ts.name); - - // First figure out the out_base of this target. The logic - // is as follows: if a directory was specified in any form, - // then that's the out_base. Otherwise, we check if the name - // value has a directory prefix. This has a good balance of - // control and the expected result in most cases. - // - dir_path out_base (tn.dir); - if (out_base.empty ()) - { - const string& v (tn.value); - - // Handle a few common cases as special: empty name, '.', - // '..', as well as dir{foo/bar} (without trailing '/'). - // This code must be consistent with find_target_type(). - // - if (v.empty () || v == "." || v == ".." || tn.type == "dir") - out_base = dir_path (v); - // - // Otherwise, if this is a simple name, see if there is a - // directory part in value. - // - else if (tn.untyped ()) - { - // We cannot assume it is a valid filesystem name so we - // will have to do the splitting manually. - // - path::size_type i (path::traits::rfind_separator (v)); - - if (i != string::npos) - out_base = dir_path (v, i != 0 ? i : 1); // Special case: "/". - } - } - - if (out_base.relative ()) - out_base = work / out_base; - - out_base.normalize (); - - // The order in which we determine the roots depends on whether - // src_base was specified explicitly. There will also be a few - // cases where we are guessing things that can turn out wrong. - // Keep track of that so that we can issue more extensive - // diagnostics for such cases. - // - bool guessing (false); - dir_path src_root; - dir_path out_root; - - dir_path& src_base (ts.src_base); // Update it in buildspec. - - if (!src_base.empty ()) - { - // Make sure it exists. While we will fail further down - // if it doesn't, the diagnostics could be confusing (e.g., - // unknown operation because we don't load bootstrap.build). - // - if (!dir_exists (src_base)) - fail << "src_base directory " << src_base << " does not exist"; - - if (src_base.relative ()) - src_base = work / src_base; - - src_base.normalize (); - - // If the src_base was explicitly specified, search for src_root. - // - src_root = find_src_root (src_base); - - // If not found, assume this is a simple project with src_root - // being the same as src_base. - // - if (src_root.empty ()) - { - src_root = src_base; - out_root = out_base; - } - else - // Calculate out_root based on src_root/src_base. - // - try - { - out_root = out_base.directory (src_base.leaf (src_root)); - } - catch (const invalid_path&) - { - fail << "out_base suffix does not match src_root" << - info << "src_root: " << src_root << - info << "out_base: " << out_base; - } - } - else - { - // If no src_base was explicitly specified, search for out_root. - // - bool src; - out_root = find_out_root (out_base, &src); - - // If not found (i.e., we have no idea where the roots are), - // then this can mean two things: an in-tree build of a - // simple project or a fresh out-of-tree build. To test for - // the latter, try to find src_root starting from work. If - // we can't, then assume it is the former case. - // - if (out_root.empty ()) - { - src_root = find_src_root (work); - - if (!src_root.empty ()) - { - src_base = work; - - if (src_root != src_base) - { - try - { - out_root = out_base.directory (src_base.leaf (src_root)); - } - catch (const invalid_path&) - { - fail << "out_base directory suffix does not match src_base" - << info << "src_base is " << src_base - << info << "src_root is " << src_root - << info << "out_base is " << out_base - << info << "consider explicitly specifying src_base " - << "for " << tn; - } - } - else - out_root = out_base; - } - else - src_root = src_base = out_root = out_base; - - guessing = true; - } - else if (src) - src_root = out_root; - } - - // Now we know out_root and, if it was explicitly specified - // or the same as out_root, src_root. The next step is to - // create the root scope and load the out_root bootstrap - // files, if any. Note that we might already have done this - // as a result of one of the preceding target processing. - // - // If we know src_root, set that variable as well. This could - // be of use to the bootstrap files (other than src-root.build, - // which, BTW, doesn't need to exist if src_root == out_root). - // - scope& rs (create_root (out_root, src_root)); - - bootstrap_out (rs); - - // See if the bootstrap process set/changed src_root. - // - value& v (rs.assign ("src_root")); - - if (v) - { - // If we also have src_root specified by the user, make - // sure they match. - // - const dir_path& p (as<dir_path> (v)); - - if (src_root.empty ()) - src_root = p; - else if (src_root != p) - fail << "bootstrapped src_root " << p << " does not match " - << "specified " << src_root; - } - else - { - // Neither bootstrap nor the user produced src_root. - // - if (src_root.empty ()) - { - // If it also wasn't explicitly specified, see if it is - // the same as out_root. - // - if (is_src_root (out_root)) - src_root = out_root; - else - { - // If not, then assume we are running from src_base - // and calculate src_root based on out_root/out_base. - // - src_base = work; - src_root = src_base.directory (out_base.leaf (out_root)); - guessing = true; - } - } - - v = src_root; - } - - setup_root (rs); - - // At this stage we should have both roots and out_base figured - // out. If src_base is still undetermined, calculate it. - // - if (src_base.empty ()) - src_base = src_root / out_base.leaf (out_root); - - // Now that we have src_root, load the src_root bootstrap file, - // if there is one. - // - bool bootstrapped (bootstrap_src (rs)); - - // Check that out_root that we have found is the innermost root - // for this project. If it is not, then it means we are trying - // to load a disfigured sub-project and that we do not support. - // Why don't we support it? Because things are already complex - // enough here. - // - if (auto l = rs.vars["subprojects"]) - { - for (const name& n: *l) - { - if (n.pair != '\0') - continue; // Skip project names. - - if (out_base.sub (out_root / n.dir)) - fail << tn << " is in a subproject of " << out_root << - info << "explicitly specify src_base for this target"; - } - } - - // Create and bootstrap outer roots if any. Loading is done - // by load_root_pre() (that would normally be called by the - // meta-operation's load() callback below). - // - create_bootstrap_outer (rs); - - // The src bootstrap should have loaded all the modules that - // may add new meta/operations. So at this stage they should - // all be known. We store the combined action id in uint8_t; - // see <operation> for details. - // - assert (operation_table.size () <= 128); - assert (meta_operation_table.size () <= 128); - - // Since we now know all the names of meta-operations and - // operations, "lift" names that we assumed (from buildspec - // syntax) were operations but are actually meta-operations. - // Also convert empty names (which means they weren't explicitly - // specified) to the defaults and verify that all the names are - // known. - // - { - const auto& mn (ms.name); - const auto& on (os.name); - - meta_operation_id m (0); - operation_id o (0); - - if (!on.empty ()) - { - m = meta_operation_table.find (on); - - if (m != 0) - { - if (!mn.empty ()) - fail (l) << "nested meta-operation " << mn - << '(' << on << ')'; - - if (!lifted) // If this is the first target. - { - // End the previous meta-operation batch if there was one - // and start a new one. - // - if (mid != 0) - { - assert (oid == 0); - - if (mif->meta_operation_post != nullptr) - mif->meta_operation_post (); - - level5 ([&]{trace << "end meta-operation batch " - << mif->name << ", id " - << static_cast<uint16_t> (mid);}); - - mid = 0; - } - - lifted = true; // Flag to also end it; see above. - } - } - else - { - o = operation_table.find (on); - - if (o == 0) - { - diag_record dr; - dr << fail (l) << "unknown operation " << on; - - // If we guessed src_root and didn't load anything during - // bootstrap, then this is probably a meta-operation that - // would have been added by the module if src_root was - // correct. - // - if (guessing && !bootstrapped) - dr << info << "consider explicitly specifying src_base " - << "for " << tn; - } - } - } - - if (!mn.empty ()) - { - m = meta_operation_table.find (mn); - - if (m == 0) - { - diag_record dr; - dr << fail (l) << "unknown meta-operation " << mn; - - // Same idea as for the operation case above. - // - if (guessing && !bootstrapped) - dr << info << "consider explicitly specifying src_base " - << "for " << tn; - } - } - - // The default meta-operation is perform. The default - // operation is assigned by the meta-operation below. - // - if (m == 0) - m = perform_id; - - // If this is the first target in the meta-operation batch, - // then set the batch meta-operation id. - // - if (mid == 0) - { - mid = m; - mif = rs.meta_operations[m]; - - if (mif == nullptr) - fail (l) << "target " << tn << " does not support meta-" - << "operation " << meta_operation_table[m]; - - level5 ([&]{trace << "start meta-operation batch " << mif->name - << ", id " << static_cast<uint16_t> (mid);}); - - if (mif->meta_operation_pre != nullptr) - mif->meta_operation_pre (); - - current_mif = mif; - } - // - // Otherwise, check that all the targets in a meta-operation - // batch have the same meta-operation implementation. - // - else - { - const meta_operation_info* mi (rs.meta_operations[mid]); - - if (mi == nullptr) - fail (l) << "target " << tn << " does not support meta-" - << "operation " << meta_operation_table[mid]; - - if (mi != mif) - fail (l) << "different implementations of meta-operation " - << mif->name << " in the same meta-operation batch"; - } - - // If this is the first target in the operation batch, then set - // the batch operation id. - // - if (oid == 0) - { - auto lookup = - [&rs, &l, &tn] (operation_id o) -> const operation_info* - { - const operation_info* r (rs.operations[o]); - - if (r == nullptr) - fail (l) << "target " << tn << " does not support " - << "operation " << operation_table[o]; - return r; - }; - - if (o == 0) - o = default_id; - - oif = lookup (o); - - level5 ([&]{trace << "start operation batch " << oif->name - << ", id " << static_cast<uint16_t> (o);}); - - // Allow the meta-operation to translate the operation. - // - if (mif->operation_pre != nullptr) - oid = mif->operation_pre (o); - else // Otherwise translate default to update. - oid = (o == default_id ? update_id : o); - - if (o != oid) - { - oif = lookup (oid); - level5 ([&]{trace << "operation translated to " << oif->name - << ", id " << static_cast<uint16_t> (oid);}); - } - - // Handle pre/post operations. - // - if (oif->pre != nullptr && (pre_oid = oif->pre (mid)) != 0) - { - assert (pre_oid != default_id); - pre_oif = lookup (pre_oid); - } - - if (oif->post != nullptr && (post_oid = oif->post (mid)) != 0) - { - assert (post_oid != default_id); - post_oif = lookup (post_oid); - } - } - // - // Similar to meta-operations, check that all the targets in - // an operation batch have the same operation implementation. - // - else - { - auto check = - [&rs, &l, &tn] (operation_id o, const operation_info* i) - { - const operation_info* r (rs.operations[o]); - - if (r == nullptr) - fail (l) << "target " << tn << " does not support " - << "operation " << operation_table[o]; - - if (r != i) - fail (l) << "different implementations of operation " - << i->name << " in the same operation batch"; - }; - - check (oid, oif); - - if (pre_oid != 0) - check (pre_oid, pre_oif); - - if (post_oid != 0) - check (post_oid, post_oif); - } - } - - if (verb >= 5) - { - trace << "target " << tn << ':'; - trace << " out_base: " << out_base; - trace << " src_base: " << src_base; - trace << " out_root: " << out_root; - trace << " src_root: " << src_root; - } - - path bf (src_base / path ("buildfile")); - - // If we were guessing src_base, check that the buildfile - // exists and if not, issue more detailed diagnostics. - // - if (guessing && !file_exists (bf)) - fail << bf << " does not exist" - << info << "consider explicitly specifying src_base " - << "for " << tn; - - // Load the buildfile. - // - mif->load (bf, rs, out_base, src_base, l); - - // Next search and match the targets. We don't want to start - // building before we know how to for all the targets in this - // operation batch. - // - { - scope& bs (scopes.find (out_base)); - - const string* e; - const target_type* ti (bs.find_target_type (tn, e)); - - if (ti == nullptr) - fail (l) << "unknown target type " << tn.type; - - // If the directory is relative, assume it is relative to work - // (must be consistent with how we derived out_base above). - // - dir_path& d (tn.dir); - - if (d.relative ()) - d = work / d; - - d.normalize (); - - mif->search (rs, target_key {ti, &d, &tn.value, &e}, l, tgs); - } - } - - if (pre_oid != 0) - { - level5 ([&]{trace << "start pre-operation batch " << pre_oif->name - << ", id " << static_cast<uint16_t> (pre_oid);}); - - if (mif->operation_pre != nullptr) - mif->operation_pre (pre_oid); // Cannot be translated. - - current_inner_oif = pre_oif; - current_outer_oif = oif; - current_mode = pre_oif->mode; - dependency_count = 0; - - action a (mid, pre_oid, oid); - - mif->match (a, tgs); - mif->execute (a, tgs, true); // Run quiet. - - if (mif->operation_post != nullptr) - mif->operation_post (pre_oid); - - level5 ([&]{trace << "end pre-operation batch " << pre_oif->name - << ", id " << static_cast<uint16_t> (pre_oid);}); - } - - current_inner_oif = oif; - current_outer_oif = nullptr; - current_mode = oif->mode; - dependency_count = 0; - - action a (mid, oid, 0); - - mif->match (a, tgs); - mif->execute (a, tgs, verb == 0); - - if (post_oid != 0) - { - level5 ([&]{trace << "start post-operation batch " << post_oif->name - << ", id " << static_cast<uint16_t> (post_oid);}); - - if (mif->operation_pre != nullptr) - mif->operation_pre (post_oid); // Cannot be translated. - - current_inner_oif = post_oif; - current_outer_oif = oif; - current_mode = post_oif->mode; - dependency_count = 0; - - action a (mid, post_oid, oid); - - mif->match (a, tgs); - mif->execute (a, tgs, true); // Run quiet. - - if (mif->operation_post != nullptr) - mif->operation_post (post_oid); - - level5 ([&]{trace << "end post-operation batch " << post_oif->name - << ", id " << static_cast<uint16_t> (post_oid);}); - } - - if (mif->operation_post != nullptr) - mif->operation_post (oid); - - level5 ([&]{trace << "end operation batch " << oif->name - << ", id " << static_cast<uint16_t> (oid);}); - } - - if (mif->meta_operation_post != nullptr) - mif->meta_operation_post (); - - level5 ([&]{trace << "end meta-operation batch " << mif->name - << ", id " << static_cast<uint16_t> (mid);}); - } - } - catch (const failed&) - { - return 1; // Diagnostics has already been issued. - } - /* - catch (const std::exception& e) - { - error << e.what (); - return 1; - } - */ -} diff --git a/build/bin/module b/build/bin/module deleted file mode 100644 index 5dd720f..0000000 --- a/build/bin/module +++ /dev/null @@ -1,23 +0,0 @@ -// file : build/bin/module -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD_BIN_MODULE -#define BUILD_BIN_MODULE - -#include <build/types> -#include <build/utility> - -#include <build/module> - -namespace build -{ - namespace bin - { - extern "C" bool - bin_init ( - scope&, scope&, const location&, unique_ptr<module>&, bool, bool); - } -} - -#endif // BUILD_BIN_MODULE diff --git a/build/bin/module.cxx b/build/bin/module.cxx deleted file mode 100644 index 5f65b8d..0000000 --- a/build/bin/module.cxx +++ /dev/null @@ -1,188 +0,0 @@ -// file : build/bin/module.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <build/bin/module> - -#include <build/scope> -#include <build/variable> -#include <build/diagnostics> - -#include <build/config/utility> -#include <build/install/utility> - -#include <build/bin/rule> -#include <build/bin/target> - -using namespace std; - -namespace build -{ - namespace bin - { - static obj_rule obj_; - static lib_rule lib_; - - // Default config.bin.*.lib values. - // - static const strings exe_lib {"shared", "static"}; - static const strings liba_lib {"static"}; - static const strings libso_lib {"shared"}; - - extern "C" bool - bin_init (scope& r, - scope& b, - const location&, - std::unique_ptr<module>&, - bool first, - bool) - { - tracer trace ("bin::init"); - level5 ([&]{trace << "for " << b.out_path ();}); - - // Enter module variables. - // - if (first) - { - auto& v (var_pool); - - v.find ("config.bin.lib", string_type); - v.find ("config.bin.exe.lib", strings_type); - v.find ("config.bin.liba.lib", strings_type); - v.find ("config.bin.libso.lib", strings_type); - v.find ("config.bin.rpath", strings_type); //@@ VAR paths_type - - v.find ("bin.lib", string_type); - v.find ("bin.exe.lib", strings_type); - v.find ("bin.liba.lib", strings_type); - v.find ("bin.libso.lib", strings_type); - v.find ("bin.rpath", strings_type); //@@ VAR paths_type - - v.find ("bin.libprefix", string_type); - } - - // Register target types. - // - { - auto& t (b.target_types); - - t.insert<obja> (); - t.insert<objso> (); - t.insert<obj> (); - t.insert<exe> (); - t.insert<liba> (); - t.insert<libso> (); - t.insert<lib> (); - } - - // Register rules. - // - { - auto& r (b.rules); - - r.insert<obj> (perform_update_id, "bin.obj", obj_); - r.insert<obj> (perform_clean_id, "bin.obj", obj_); - - r.insert<lib> (perform_update_id, "bin.lib", lib_); - r.insert<lib> (perform_clean_id, "bin.lib", lib_); - - // Configure member. - // - r.insert<lib> (configure_update_id, "bin.lib", lib_); - - //@@ Should we check if the install module was loaded - // (by checking if install operation is registered - // for this project)? If we do that, then install - // will have to be loaded before bin. Perhaps we - // should enforce loading of all operation-defining - // modules before all others? - // - r.insert<lib> (perform_install_id, "bin.lib", lib_); - } - - // Configure. - // - using config::required; - - // The idea here is as follows: if we already have one of - // the bin.* variables set, then we assume this is static - // project configuration and don't bother setting the - // corresponding config.bin.* variable. - // - //@@ Need to validate the values. Would be more efficient - // to do it once on assignment than every time on query. - // Custom var type? - // - - // config.bin.lib - // - { - value& v (b.assign ("bin.lib")); - if (!v) - v = required (r, "config.bin.lib", "both").first; - } - - // config.bin.exe.lib - // - { - value& v (b.assign ("bin.exe.lib")); - if (!v) - v = required (r, "config.bin.exe.lib", exe_lib).first; - } - - // config.bin.liba.lib - // - { - value& v (b.assign ("bin.liba.lib")); - if (!v) - v = required (r, "config.bin.liba.lib", liba_lib).first; - } - - // config.bin.libso.lib - // - { - value& v (b.assign ("bin.libso.lib")); - if (!v) - v = required (r, "config.bin.libso.lib", libso_lib).first; - } - - // config.bin.rpath - // - // This one is optional and we merge it into bin.rpath, if any. - // See the cxx module for details on merging. - // - if (const value& v = config::optional (r, "config.bin.rpath")) - b.assign ("bin.rpath") += as<strings> (v); - - - // Configure "installability" of our target types. - // - install::path<exe> (b, dir_path ("bin")); // Install into install.bin. - - // Should shared libraries have executable bit? That depends on - // who you ask. In Debian, for example, it should not unless, it - // really is executable (i.e., has main()). On the other hand, on - // some systems, this may be required in order for the dynamic - // linker to be able to load the library. So, by default, we will - // keep it executable, especially seeing that this is also the - // behavior of autotools. At the same time, it is easy to override - // this, for example: - // - // config.install.lib.mode=644 - // - // And a library that wants to override any such overrides (e.g., - // because it does have main()) can do: - // - // libso{foo}: install.mode=755 - // - // Everyone is happy then? - // - install::path<libso> (b, dir_path ("lib")); // Install into install.lib. - - install::path<liba> (b, dir_path ("lib")); // Install into install.lib. - install::mode<liba> (b, "644"); - - return true; - } - } -} diff --git a/build/bin/rule b/build/bin/rule deleted file mode 100644 index 743b1ca..0000000 --- a/build/bin/rule +++ /dev/null @@ -1,39 +0,0 @@ -// file : build/bin/rule -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD_BIN_RULE -#define BUILD_BIN_RULE - -#include <build/rule> - -namespace build -{ - 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 // BUILD_BIN_RULE diff --git a/build/bin/rule.cxx b/build/bin/rule.cxx deleted file mode 100644 index 21db183..0000000 --- a/build/bin/rule.cxx +++ /dev/null @@ -1,145 +0,0 @@ -// file : build/bin/rule.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <build/bin/rule> - -#include <build/scope> -#include <build/target> -#include <build/algorithm> -#include <build/diagnostics> - -#include <build/bin/target> - -using namespace std; - -namespace build -{ - namespace bin - { - // obj - // - match_result obj_rule:: - match (action a, target& t, const std::string&) const - { - fail << diag_doing (a, t) << " target group" << - info << "explicitly select either obja{} or objso{} member"; - - return nullptr; - } - - recipe obj_rule:: - apply (action, target&, const match_result&) const {return empty_recipe;} - - // lib - // - // The whole logic is pretty much as if we had our two group - // members as our prerequisites. - // - match_result lib_rule:: - match (action a, target& xt, const std::string&) const - { - lib& t (static_cast<lib&> (xt)); - - // @@ We have to re-query it on each match_only()! - - // Get the library type to build. If not set for a target, this - // should be configured at the project scope by init_lib(). - // - const string& type (as<string> (*t["bin.lib"])); - - bool ar (type == "static" || type == "both"); - bool so (type == "shared" || type == "both"); - - if (!ar && !so) - fail << "unknown library type: " << type << - info << "'static', 'shared', or 'both' expected"; - - // Search and pre-match the members. The pre-match here is part - // of the "library meta-information protocol" that could be used - // by the module that actually builds the members. The idea is - // that pre-matching members may populate our prerequisite_targets - // with prerequisite libraries from which others can extract the - // meta-information about the library, such as the options to use - // when linking it, etc. - // - if (ar) - { - if (t.a == nullptr) - t.a = &search<liba> (t.dir, t.name, t.ext, nullptr); - - match_only (a, *t.a); - } - - if (so) - { - if (t.so == nullptr) - t.so = &search<libso> (t.dir, t.name, t.ext, nullptr); - - match_only (a, *t.so); - } - - match_result mr (t, &type); - - // If there is an outer operation, indicate that we match - // unconditionally so that we don't override ourselves. - // - if (a.outer_operation () != 0) - mr.recipe_action = action (a.meta_operation (), a.operation ()); - - return mr; - } - - recipe lib_rule:: - apply (action a, target& xt, const match_result& mr) const - { - lib& t (static_cast<lib&> (xt)); - - const string& type (*static_cast<const string*> (mr.cpvalue)); - - bool ar (type == "static" || type == "both"); - bool so (type == "shared" || type == "both"); - - // Now we do full match. - // - if (ar) - build::match (a, *t.a); - - if (so) - build::match (a, *t.so); - - return &perform; - } - - target_state lib_rule:: - perform (action a, target& xt) - { - lib& t (static_cast<lib&> (xt)); - - //@@ Not cool we have to do this again. Looks like we need - // some kind of a cache vs resolved pointer, like in - // prerequisite vs prerequisite_target. - // - // - const string& type (as<string> (*t["bin.lib"])); - bool ar (type == "static" || type == "both"); - bool so (type == "shared" || type == "both"); - - target* m1 (ar ? t.a : nullptr); - target* m2 (so ? t.so : nullptr); - - if (current_mode == execution_mode::last) - swap (m1, m2); - - target_state r (target_state::unchanged); - - if (m1 != nullptr) - r |= execute (a, *m1); - - if (m2 != nullptr) - r |= execute (a, *m2); - - return r; - } - } -} diff --git a/build/bin/target b/build/bin/target deleted file mode 100644 index cbabcaf..0000000 --- a/build/bin/target +++ /dev/null @@ -1,99 +0,0 @@ -// file : build/bin/target -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD_BIN_TARGET -#define BUILD_BIN_TARGET - -#include <build/target> - -namespace build -{ - 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 // BUILD_BIN_TARGET diff --git a/build/bin/target.cxx b/build/bin/target.cxx deleted file mode 100644 index 735c581..0000000 --- a/build/bin/target.cxx +++ /dev/null @@ -1,190 +0,0 @@ -// file : build/bin/target.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <build/bin/target> - -using namespace std; - -namespace build -{ - namespace bin - { - static target* - obja_factory (const target_type&, dir_path d, string n, const string* e) - { - obj* o (targets.find<obj> (d, n)); - obja* a (new obja (move (d), move (n), e)); - - if ((a->group = o)) - o->a = a; - - return a; - } - - const target_type obja::static_type - { - "obja", - &file::static_type, - &obja_factory, - nullptr, - &search_target, // Note: not _file(); don't look for an existing file. - false - }; - - static target* - objso_factory (const target_type&, dir_path d, string n, const string* e) - { - obj* o (targets.find<obj> (d, n)); - objso* so (new objso (move (d), move (n), e)); - - if ((so->group = o)) - o->so = so; - - return so; - } - - const target_type objso::static_type - { - "objso", - &file::static_type, - &objso_factory, - nullptr, - &search_target, // Note: not _file(); don't look for an existing file. - false - }; - - static target* - obj_factory (const target_type&, dir_path d, string n, const string* e) - { - obja* a (targets.find<obja> (d, n)); - objso* so (targets.find<objso> (d, n)); - obj* o (new obj (move (d), move (n), e)); - - if ((o->a = a)) - a->group = o; - - if ((o->so = so)) - so->group = o; - - return o; - } - - const target_type obj::static_type - { - "obj", - &target::static_type, - &obj_factory, - nullptr, - &search_target, - false - }; - - const target_type exe::static_type - { - "exe", - &file::static_type, - &target_factory<exe>, - nullptr, - &search_file, - false - }; - - static target* - liba_factory (const target_type& t, dir_path d, string n, const string* e) - { - // Only link-up to the group if the types match exactly. - // - lib* l (t == liba::static_type ? targets.find<lib> (d, n) : nullptr); - liba* a (new liba (move (d), move (n), e)); - - if ((a->group = l)) - l->a = a; - - return a; - } - - // @@ - // - // What extensions should we use? At the outset, this is platform- - // dependent. And if we consider cross-compilation, is it build or - // host-dependent? Feels like it should be host-dependent so that - // we can copy things between cross and native environments. So - // these will have to be determined based on what we are building. - // As if this is not complicated enough, the bin module doesn't - // know anything about building. So perhaps the extension should - // come from a variable that is set not by bin but by the module - // whose rule matched the target (e.g., cxx::link). - // - constexpr const char a_ext[] = "a"; - const target_type liba::static_type - { - "liba", - &file::static_type, - &liba_factory, - &target_extension_fix<a_ext>, - &search_file, - false - }; - - static target* - libso_factory (const target_type& t, dir_path d, string n, const string* e) - { - // Only link-up to the group if the types match exactly. - // - lib* l (t == libso::static_type ? targets.find<lib> (d, n) : nullptr); - libso* so (new libso (move (d), move (n), e)); - - if ((so->group = l)) - l->so = so; - - return so; - } - - constexpr const char so_ext[] = "so"; - const target_type libso::static_type - { - "libso", - &file::static_type, - &libso_factory, - &target_extension_fix<so_ext>, - &search_file, - false - }; - - // lib - // - void lib:: - reset (action_type) - { - // Don't clear prerequisite_targets since it is "given" to our - // members to implement "library meta-information protocol". - } - - static target* - lib_factory (const target_type&, dir_path d, string n, const string* e) - { - liba* a (targets.find<liba> (d, n)); - libso* so (targets.find<libso> (d, n)); - lib* l (new lib (move (d), move (n), e)); - - if ((l->a = a)) - a->group = l; - - if ((l->so = so)) - so->group = l; - - return l; - } - - const target_type lib::static_type - { - "lib", - &target::static_type, - &lib_factory, - nullptr, - &search_target, - false - }; - } -} diff --git a/build/buildfile b/build/buildfile deleted file mode 100644 index 936524e..0000000 --- a/build/buildfile +++ /dev/null @@ -1,64 +0,0 @@ -# file : build/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 -# -# cli --generate-specifier --cli-namespace cl --include-with-brackets --include-prefix build --guard-prefix BUILD --hxx-suffix "" options.cli diff --git a/build/cli/module b/build/cli/module deleted file mode 100644 index 221e6a0..0000000 --- a/build/cli/module +++ /dev/null @@ -1,23 +0,0 @@ -// file : build/cli/module -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD_CLI_MODULE -#define BUILD_CLI_MODULE - -#include <build/types> -#include <build/utility> - -#include <build/module> - -namespace build -{ - namespace cli - { - extern "C" bool - cli_init ( - scope&, scope&, const location&, unique_ptr<module>&, bool, bool); - } -} - -#endif // BUILD_CLI_MODULE diff --git a/build/cli/module.cxx b/build/cli/module.cxx deleted file mode 100644 index de7d576..0000000 --- a/build/cli/module.cxx +++ /dev/null @@ -1,244 +0,0 @@ -// file : build/cli/module.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <build/cli/module> - -#include <butl/process> -#include <butl/fdstream> - -#include <build/scope> -#include <build/target> -#include <build/variable> -#include <build/diagnostics> - -#include <build/cxx/target> - -#include <build/config/utility> - -#include <build/cli/target> -#include <build/cli/rule> - -using namespace std; -using namespace butl; - -namespace build -{ - namespace cli - { - static compile compile_; - - extern "C" bool - cli_init (scope& root, - scope& base, - const location& loc, - std::unique_ptr<module>&, - bool first, - bool optional) - { - tracer trace ("cli::init"); - level5 ([&]{trace << "for " << base.out_path ();}); - - // Make sure the cxx module has been loaded since we need its - // targets types (?xx{}). Note that we don't try to load it - // ourselves because of the non-trivial variable merging - // semantics. So it is better to let the user load cxx - // explicitly. - // - { - auto l (base["cxx.loaded"]); - - if (!l || !as<bool> (*l)) - fail (loc) << "cxx module must be loaded before cli"; - } - - // Enter module variables. - // - if (first) - { - auto& v (var_pool); - - v.find ("config.cli.configured", bool_type); - - v.find ("config.cli", string_type); //@@ VAR type - - v.find ("config.cli.options", strings_type); - v.find ("cli.options", strings_type); - } - - // Register target types. - // - { - auto& t (base.target_types); - - t.insert<cli> (); - t.insert<cli_cxx> (); - } - - // Configure. - // - // The plan is as follows: try to configure the module. If this fails, - // we are using default values, and the module is optional, leave it - // unconfigured. - // - - // We will only honor optional if the user didn't specify any cli - // configuration explicitly. - // - optional = optional && !config::specified (root, "config.cli"); - - // Don't re-run tests if the configuration says we are unconfigured. - // - if (optional) - { - auto l (root["config.cli.configured"]); - - if (l && !as<bool> (*l)) - return false; - } - - // config.cli - // - if (first) - { - // Return version or empty string if unable to execute (e.g., - // the cli executable is not found). - // - auto test = [optional] (const char* cli) -> string - { - const char* args[] = {cli, "--version", nullptr}; - - if (verb >= 2) - print_process (args); - else if (verb) - text << "test " << cli; - - string ver; - try - { - process pr (args, 0, -1); // Open pipe to stdout. - ifdstream is (pr.in_ofd); - - // The version should be the last word on the first line. - // - getline (is, ver); - auto p (ver.rfind (' ')); - if (p != string::npos) - ver = string (ver, p + 1); - - is.close (); // Don't block the other end. - - if (!pr.wait ()) - return string (); // Not found. - - if (ver.empty ()) - fail << "unexpected output from " << cli; - - return ver; - } - catch (const process_error& e) - { - // In some cases this is not enough (e.g., the runtime linker - // will print scary errors if some shared libraries are not - // found. So it would be good to redirect child's STDERR. - // - if (!optional) - error << "unable to execute " << cli << ": " << e.what (); - - if (e.child ()) - exit (1); - - throw failed (); - } - }; - - string ver; - const char* cli ("cli"); // Default. - - if (optional) - { - // Test the default value before setting any config.cli.* values - // so that if we fail to configure, nothing will be written to - // config.build. - // - ver = test (cli); - - if (ver.empty ()) - { - // Note that we are unconfigured so that we don't keep re-testing - // this on each run. - // - root.assign ("config.cli.configured") = false; - - if (verb >= 2) - text << cli << " not found, leaving cli module unconfigured"; - - return false; - } - else - { - auto p (config::required (root, "config.cli", cli)); - assert (p.second && as<string> (p.first) == cli); - } - } - else - { - auto p (config::required (root, "config.cli", cli)); - - // If we actually set a new value, test it by trying to execute. - // - if (p.second) - { - cli = as<string> (p.first).c_str (); - ver = test (cli); - - if (ver.empty ()) - throw failed (); - } - } - - // Clear the unconfigured flag, if any. - // - root.assign ("config.cli.configured") = true; - - if (!ver.empty () && verb >= 2) - text << cli << " " << ver; - } - - // config.cli.options - // - // This one is optional. We also merge it into the corresponding - // cli.* variables. See the cxx module for more information on - // this merging semantics and some of its tricky aspects. - // - if (const value& v = config::optional (root, "config.cli.options")) - base.assign ("cli.options") += as<strings> (v); - - // Register our rules. - // - { - auto& r (base.rules); - - r.insert<cli_cxx> (perform_update_id, "cli.compile", compile_); - r.insert<cli_cxx> (perform_clean_id, "cli.compile", compile_); - - r.insert<cxx::hxx> (perform_update_id, "cli.compile", compile_); - r.insert<cxx::hxx> (perform_clean_id, "cli.compile", compile_); - - r.insert<cxx::cxx> (perform_update_id, "cli.compile", compile_); - r.insert<cxx::cxx> (perform_clean_id, "cli.compile", compile_); - - r.insert<cxx::ixx> (perform_update_id, "cli.compile", compile_); - r.insert<cxx::ixx> (perform_clean_id, "cli.compile", compile_); - - // Other rules (e.g., cxx::compile) may need to have the group - // members resolved. Looks like a general pattern: groups should - // resolve on configure(update). - // - r.insert<cli_cxx> (configure_update_id, "cli.compile", compile_); - } - - return true; - } - } -} diff --git a/build/cli/rule b/build/cli/rule deleted file mode 100644 index 21fcf8a..0000000 --- a/build/cli/rule +++ /dev/null @@ -1,32 +0,0 @@ -// file : build/cli/rule -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD_CLI_RULE -#define BUILD_CLI_RULE - -#include <build/rule> - -namespace build -{ - 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 // BUILD_CLI_RULE diff --git a/build/cli/rule.cxx b/build/cli/rule.cxx deleted file mode 100644 index 274f193..0000000 --- a/build/cli/rule.cxx +++ /dev/null @@ -1,305 +0,0 @@ -// file : build/cli/rule.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <build/cli/rule> - -#include <butl/process> - -#include <build/types> -#include <build/scope> -#include <build/target> -#include <build/context> -#include <build/algorithm> -#include <build/diagnostics> - -#include <build/cli/target> - -#include <build/config/utility> - -using namespace std; -using namespace butl; - -namespace build -{ - namespace cli - { - match_result compile:: - match (action a, target& xt, const std::string&) const - { - tracer trace ("cli::compile::match"); - - if (cli_cxx* pt = xt.is_a<cli_cxx> ()) - { - // The cli.cxx{} group. - // - cli_cxx& t (*pt); - - // See if we have a .cli source file. - // - match_result r; - for (prerequisite_member p: group_prerequisite_members (a, t)) - { - if (p.is_a<cli> ()) - { - // Check that the stems match. - // - if (t.name != p.name ()) - { - level4 ([&]{trace << ".cli file stem '" << p.name () << "' " - << "doesn't match target " << t;}); - return r; - } - - r = p; - break; - } - } - - if (!r) - { - level4 ([&]{trace << "no .cli source file for target " << t;}); - return r; - } - - // If we still haven't figured out the member list, we can do - // that now. Specifically, at this stage, no further changes to - // cli.options are possible and we can determine whether the - // --suppress-inline option is present. - // - if (t.h == nullptr) - { - t.h = &search<cxx::hxx> (t.dir, t.name, nullptr, nullptr); - t.h->group = &t; - - t.c = &search<cxx::cxx> (t.dir, t.name, nullptr, nullptr); - t.c->group = &t; - - if (!config::find_option ("--suppress-inline", t, "cli.options")) - { - t.i = &search<cxx::ixx> (t.dir, t.name, nullptr, nullptr); - t.i->group = &t; - } - } - - return r; - } - else - { - // One of the ?xx{} members. - // - target& t (xt); - - // First see if we are already linked-up to the cli.cxx{} group. - // If it is some other group, then we are definitely not a match. - // - if (t.group != nullptr) - return t.group->is_a<cli_cxx> (); - - // Then check if there is a corresponding cli.cxx{} group. - // - cli_cxx* g (targets.find<cli_cxx> (t.dir, t.name)); - - // If not or if it has no prerequisites (happens when we use it to - // set cli.options) and this target has a cli{} prerequisite, then - // synthesize the group. - // - if (g == nullptr || !g->has_prerequisites ()) - { - for (prerequisite_member p: group_prerequisite_members (a, t)) - { - if (p.is_a<cli> ()) - { - // Check that the stems match. - // - if (t.name == p.name ()) - { - if (g == nullptr) - g = &targets.insert<cli_cxx> (t.dir, t.name, trace); - - g->prerequisites.emplace_back (p.as_prerequisite (trace)); - } - else - level4 ([&]{trace << ".cli file stem '" << p.name () << "' " - << "doesn't match target " << t;}); - break; - } - } - } - - if (g != nullptr) - { - // Resolve the group's members. This should link us up to - // the group. - // - resolve_group_members (a, *g); - - // For ixx{}, verify it is part of the group. - // - if (t.is_a<cxx::ixx> () && g->i == nullptr) - { - level4 ([&]{trace << "generation of inline file " << t - << " is disabled with --suppress-inline";}); - g = nullptr; - } - } - - assert (t.group == g); - return g; - } - } - - recipe compile:: - apply (action a, target& xt, const match_result& mr) const - { - if (cli_cxx* pt = xt.is_a<cli_cxx> ()) - { - cli_cxx& t (*pt); - - // Derive file names for the members. - // - t.h->derive_path (); - t.c->derive_path (); - if (t.i != nullptr) - t.i->derive_path (); - - // Inject dependency on the output directory. - // - inject_parent_fsdir (a, t); - - // Search and match prerequisite members. - // - search_and_match_prerequisite_members (a, t); - - switch (a) - { - case perform_update_id: return &perform_update; - case perform_clean_id: return &perform_clean; - default: return noop_recipe; // Configure update. - } - } - else - { - cli_cxx& g (*static_cast<cli_cxx*> (mr.target)); - build::match (a, g); - return group_recipe; // Execute the group's recipe. - } - } - - static void - append_extension (cstrings& args, - path_target& t, - const char* option, - const char* default_extension) - { - assert (t.ext != nullptr); // Should have been figured out in apply(). - - if (*t.ext != default_extension) - { - // CLI needs the extension with the leading dot (unless it is empty) - // while we store the extension without. But if there is an extension, - // then we can get it (with the dot) from the file name. - // - args.push_back (option); - args.push_back (t.ext->empty () - ? t.ext->c_str () - : t.path ().extension () - 1); - } - } - - target_state compile:: - perform_update (action a, target& xt) - { - cli_cxx& t (static_cast<cli_cxx&> (xt)); - - // Execute our prerequsites and check if we are out of date. - // - cli* s (execute_prerequisites<cli> (a, t, t.mtime ())); - - if (s == nullptr) - return target_state::unchanged; - - // Translate paths to relative (to working directory). This - // results in easier to read diagnostics. - // - path relo (relative (t.dir)); - path rels (relative (s->path ())); - - scope& rs (t.root_scope ()); - const string& cli (as<string> (*rs["config.cli"])); - - cstrings args {cli.c_str ()}; - - // See if we need to pass any --?xx-suffix options. - // - append_extension (args, *t.h, "--hxx-suffix", "hxx"); - append_extension (args, *t.c, "--cxx-suffix", "cxx"); - if (t.i != nullptr) - append_extension (args, *t.i, "--ixx-suffix", "ixx"); - - config::append_options (args, t, "cli.options"); - - if (!relo.empty ()) - { - args.push_back ("-o"); - args.push_back (relo.string ().c_str ()); - } - - args.push_back (rels.string ().c_str ()); - args.push_back (nullptr); - - if (verb >= 2) - print_process (args); - else if (verb) - text << "cli " << *s; - - try - { - process pr (args.data ()); - - if (!pr.wait ()) - throw failed (); - - t.mtime (system_clock::now ()); - } - catch (const process_error& e) - { - error << "unable to execute " << args[0] << ": " << e.what (); - - if (e.child ()) - exit (1); - - throw failed (); - } - - return target_state::changed; - } - - target_state compile:: - perform_clean (action a, target& xt) - { - cli_cxx& t (static_cast<cli_cxx&> (xt)); - - // The reverse order of update: first delete the files, then clean - // prerequisites. Also update timestamp in case there are operations - // after us that could use the information. - // - bool r (false); - - if (t.i != nullptr) - r = rmfile (t.i->path (), *t.i) || r; - r = rmfile (t.c->path (), *t.c) || r; - r = rmfile (t.h->path (), *t.h) || r; - - t.mtime (timestamp_nonexistent); - - target_state ts (r ? target_state::changed : target_state::unchanged); - - // Clean prerequisites. - // - ts |= reverse_execute_prerequisites (a, t); - - return ts; - } - } -} diff --git a/build/cli/target b/build/cli/target deleted file mode 100644 index dbb05bd..0000000 --- a/build/cli/target +++ /dev/null @@ -1,62 +0,0 @@ -// file : build/cli/target -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD_CLI_TARGET -#define BUILD_CLI_TARGET - -#include <build/target> - -#include <build/cxx/target> - -namespace build -{ - 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 // BUILD_CLI_TARGET diff --git a/build/cli/target.cxx b/build/cli/target.cxx deleted file mode 100644 index 6eef99b..0000000 --- a/build/cli/target.cxx +++ /dev/null @@ -1,77 +0,0 @@ -// file : build/cli/target.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <build/cli/target> - -#include <butl/filesystem> - -using namespace std; -using namespace butl; - -namespace build -{ - namespace cli - { - // cli - // - constexpr const char cli_ext_var[] = "extension"; - constexpr const char cli_ext_def[] = "cli"; - - const target_type cli::static_type - { - "cli", - &file::static_type, - &target_factory<cli>, - &target_extension_var<cli_ext_var, cli_ext_def>, - &search_file, - false - }; - - // cli.cxx - // - group_view cli_cxx:: - group_members (action_type) const - { - return h != nullptr - ? group_view {m, (i != nullptr ? 3U : 2U)} - : group_view {nullptr, 0}; - } - - timestamp cli_cxx:: - load_mtime () const - { - // The rule has been matched which means the members should - // be resolved and paths assigned. - // - return file_mtime (h->path ()); - } - - static target* - cli_cxx_factory (const target_type&, dir_path d, string n, const string* e) - { - tracer trace ("cli::cli_cxx_factory"); - - // Pre-enter (potential) members as targets. The main purpose - // of doing this is to avoid searching for existing files in - // src_base if the buildfile mentions some of them explicitly - // as prerequisites. - // - targets.insert<cxx::hxx> (d, n, trace); - targets.insert<cxx::cxx> (d, n, trace); - targets.insert<cxx::ixx> (d, n, trace); - - return new cli_cxx (move (d), move (n), e); - } - - const target_type cli_cxx::static_type - { - "cli.cxx", - &mtime_target::static_type, - &cli_cxx_factory, - nullptr, - &search_target, - true // "See through" default iteration mode. - }; - } -} diff --git a/build/config/module b/build/config/module deleted file mode 100644 index 3e43c7b..0000000 --- a/build/config/module +++ /dev/null @@ -1,26 +0,0 @@ -// file : build/config/module -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD_CONFIG_MODULE -#define BUILD_CONFIG_MODULE - -#include <build/types> -#include <build/utility> - -#include <build/module> - -namespace build -{ - namespace config - { - extern "C" void - config_boot (scope&, const location&, unique_ptr<module>&); - - extern "C" bool - config_init ( - scope&, scope&, const location&, unique_ptr<module>&, bool, bool); - } -} - -#endif // BUILD_CONFIG_MODULE diff --git a/build/config/module.cxx b/build/config/module.cxx deleted file mode 100644 index c9139ed..0000000 --- a/build/config/module.cxx +++ /dev/null @@ -1,90 +0,0 @@ -// file : build/config/module.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <build/config/module> - -#include <butl/filesystem> - -#include <build/file> -#include <build/rule> -#include <build/scope> -#include <build/diagnostics> - -#include <build/config/operation> - -using namespace std; -using namespace butl; - -namespace build -{ - namespace config - { - //@@ Same as in operation.cxx - // - static const path config_file ("build/config.build"); - - extern "C" void - config_boot (scope& root, const location&, unique_ptr<module>&) - { - tracer trace ("config::boot"); - - const dir_path& out_root (root.out_path ()); - level5 ([&]{trace << "for " << out_root;}); - - // Register meta-operations. - // - root.meta_operations.insert (configure_id, configure); - root.meta_operations.insert (disfigure_id, disfigure); - - // Load config.build if one exists. - // - // Note that we have to do this during bootstrap since the order in - // which the modules will be initialized is unspecified. So it is - // possible that some module which needs the configuration will get - // called first. - // - path f (out_root / config_file); - - if (file_exists (f)) - source (f, root, root); - } - - extern "C" bool - config_init (scope& root, - scope&, - const location& l, - std::unique_ptr<module>&, - bool first, - bool) - { - tracer trace ("config::init"); - - if (!first) - { - warn (l) << "multiple config module initializations"; - return true; - } - - level5 ([&]{trace << "for " << root.out_path ();}); - - // Register alias and fallback rule for the configure meta-operation. - // - { - // We need this rule for out-of-any-project dependencies (e.g., - // libraries imported from /usr/lib). - // - global_scope->rules.insert<file> ( - configure_id, 0, "config.file", file_rule::instance); - - auto& r (root.rules); - - r.insert<target> (configure_id, 0, "config", fallback_rule::instance); - r.insert<file> (configure_id, 0, "config.file", fallback_rule::instance); - r.insert<alias> (configure_id, 0, "config.alias", alias_rule::instance); - } - - return true; - } - } -} diff --git a/build/config/operation b/build/config/operation deleted file mode 100644 index ee5161d..0000000 --- a/build/config/operation +++ /dev/null @@ -1,19 +0,0 @@ -// file : build/config/operation -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD_CONFIG_OPERATION -#define BUILD_CONFIG_OPERATION - -#include <build/operation> - -namespace build -{ - namespace config - { - extern meta_operation_info configure; - extern meta_operation_info disfigure; - } -} - -#endif // BUILD_CONFIG_OPERATION diff --git a/build/config/operation.cxx b/build/config/operation.cxx deleted file mode 100644 index abe4e22..0000000 --- a/build/config/operation.cxx +++ /dev/null @@ -1,455 +0,0 @@ -// file : build/config/operation.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <build/config/operation> - -#include <fstream> - -#include <butl/filesystem> - -#include <build/file> -#include <build/scope> -#include <build/target> -#include <build/context> -#include <build/algorithm> -#include <build/diagnostics> - -using namespace std; -using namespace butl; - -namespace build -{ - namespace config - { - static const path config_file ("build/config.build"); - - // configure - // - static operation_id - configure_operation_pre (operation_id o) - { - // Don't translate default to update. In our case unspecified - // means configure everything. - // - return o; - } - - static void - save_src_root (const dir_path& out_root, const dir_path& src_root) - { - path f (out_root / src_root_file); - - if (verb) - text << (verb >= 2 ? "config::save_src_root " : "save ") << f; - - try - { - ofstream ofs (f.string ()); - if (!ofs.is_open ()) - fail << "unable to open " << f; - - ofs.exceptions (ofstream::failbit | ofstream::badbit); - - //@@ TODO: quote path - // - ofs << "# Created automatically by the config module." << endl - << "#" << endl - << "src_root = " << src_root << endl; - } - catch (const ofstream::failure&) - { - fail << "unable to write " << f; - } - } - - static void - save_config (scope& root) - { - const dir_path& out_root (root.out_path ()); - path f (out_root / config_file); - - if (verb) - text << (verb >= 2 ? "config::save_config " : "save ") << f; - - try - { - ofstream ofs (f.string ()); - if (!ofs.is_open ()) - fail << "unable to open " << f; - - ofs.exceptions (ofstream::failbit | ofstream::badbit); - - ofs << "# Created automatically by the config module, but" << endl - << "# feel free to edit." << endl - << "#" << endl; - - if (auto l = root.vars["amalgamation"]) - { - const dir_path& d (as<dir_path> (*l)); - - ofs << "# Base configuration inherited from " << d << endl - << "#" << endl; - } - - // Save all the variables in the config namespace that are set - // on the project's root scope. - // - for (auto p (root.vars.find_namespace ("config")); - p.first != p.second; - ++p.first) - { - const variable& var (p.first->first); - const value& val (p.first->second); - const string& n (var.name); - - // Skip special variables. - // - if (n == "config.loaded" || - n == "config.configured") - continue; - - // We will only write config.*.configured if it is false - // (true is implied by its absence). - // - if (n.size () > 11 && - n.compare (n.size () - 11, 11, ".configured") == 0) - { - if (val == nullptr || as<bool> (val)) - continue; - } - - // Warn the user if the value that we are saving differs - // from the one they specified on the command line. - // - auto l ((*global_scope)[var]); - if (l.defined () && *l != val) - { - warn << "variable " << var.name << " configured value " - << "differs from command line value" << - info << "reconfigure the project to use command line value"; - } - - if (val) - { - ofs << var.name << " = " << val.data_ << endl; - //text << var.name << " = " << val.data_; - } - else - { - ofs << var.name << " = #[null]" << endl; // @@ TODO: [null] - //text << var.name << " = [null]"; - } - } - } - catch (const ofstream::failure&) - { - fail << "unable to write " << f; - } - } - - static void - configure_project (action a, scope& root) - { - tracer trace ("configure_project"); - - const dir_path& out_root (root.out_path ()); - const dir_path& src_root (root.src_path ()); - - // Make sure the directories exist. - // - if (out_root != src_root) - { - mkdir_p (out_root); - mkdir (out_root / build_dir); - mkdir (out_root / bootstrap_dir); - } - - // We distinguish between a complete configure and operation- - // specific. - // - if (a.operation () == default_id) - { - level5 ([&]{trace << "completely configuring " << out_root;}); - - // Save src-root.build unless out_root is the same as src. - // - if (out_root != src_root) - save_src_root (out_root, src_root); - - // Save config.build. - // - save_config (root); - } - else - { - } - - // Configure subprojects that have been loaded. - // - if (auto l = root.vars["subprojects"]) - { - for (auto p: as<subprojects> (*l)) - { - const dir_path& pd (p.second); - dir_path out_nroot (out_root / pd); - scope& nroot (scopes.find (out_nroot)); - - // @@ Strictly speaking we need to check whether the config - // module was loaded for this subproject. - // - if (nroot.out_path () != out_nroot) // This subproject not loaded. - continue; - - configure_project (a, nroot); - } - } - } - - static void - configure_match (action, action_targets&) - { - // Don't match anything -- see execute (). - } - - static void - configure_execute (action a, const action_targets& ts, bool) - { - // Match rules to configure every operation supported by each - // project. Note that we are not calling operation_pre/post() - // callbacks here since the meta operation is configure and we - // know what we are doing. - // - for (void* v: ts) - { - target& t (*static_cast<target*> (v)); - scope* rs (t.base_scope ().root_scope ()); - - if (rs == nullptr) - fail << "out of project target " << t; - - for (operations::size_type id (default_id + 1); // Skip default_id - id < rs->operations.size (); - ++id) - { - const operation_info* oi (rs->operations[id]); - if (oi == nullptr) - continue; - - current_inner_oif = oi; - current_outer_oif = nullptr; - current_mode = oi->mode; - dependency_count = 0; - - match (action (configure_id, id), t); - } - - configure_project (a, *rs); - } - } - - meta_operation_info configure { - "configure", - "configure", - "configuring", - "is configured", - nullptr, // meta-operation pre - &configure_operation_pre, - &load, // normal load - &search, // normal search - &configure_match, - &configure_execute, - nullptr, // operation post - nullptr // meta-operation post - }; - - // disfigure - // - static operation_id - disfigure_operation_pre (operation_id o) - { - // Don't translate default to update. In our case unspecified - // means disfigure everything. - // - return o; - } - - static void - disfigure_load (const path& bf, - scope&, - const dir_path&, - const dir_path&, - const location&) - { - tracer trace ("disfigure_load"); - level6 ([&]{trace << "skipping " << bf;}); - } - - static void - disfigure_search (scope& root, - const target_key&, - const location&, - action_targets& ts) - { - tracer trace ("disfigure_search"); - level6 ([&]{trace << "collecting " << root.out_path ();}); - ts.push_back (&root); - } - - static void - disfigure_match (action, action_targets&) {} - - static bool - disfigure_project (action a, scope& root) - { - tracer trace ("disfigure_project"); - - bool m (false); // Keep track of whether we actually did anything. - - const dir_path& out_root (root.out_path ()); - const dir_path& src_root (root.src_path ()); - - // Disfigure subprojects. Since we don't load buildfiles during - // disfigure, we do it for all known subprojects. - // - if (auto l = root.vars["subprojects"]) - { - for (auto p: as<subprojects> (*l)) - { - const dir_path& pd (p.second); - - // Create and bootstrap subproject's root scope. - // - dir_path out_nroot (out_root / pd); - - // The same logic for src_root as in create_bootstrap_inner(). - // - scope& nroot (create_root (out_nroot, dir_path ())); - bootstrap_out (nroot); - - value& val (nroot.assign ("src_root")); - - if (!val) - val = is_src_root (out_nroot) ? out_nroot : (src_root / pd); - - setup_root (nroot); - - bootstrap_src (nroot); - - m = disfigure_project (a, nroot) || m; - - // We use mkdir_p() to create the out_root of a subproject - // which means there could be empty parent directories left - // behind. Clean them up. - // - if (!pd.simple () && out_root != src_root) - { - for (dir_path d (pd.directory ()); - !d.empty (); - d = d.directory ()) - { - rmdir_status s (rmdir (out_root / d)); - - if (s == rmdir_status::not_empty) - break; // No use trying do remove parent ones. - - m = (s == rmdir_status::success) || m; - } - } - } - } - - // We distinguish between a complete disfigure and operation- - // specific. - // - if (a.operation () == default_id) - { - level5 ([&]{trace << "completely disfiguring " << out_root;}); - - m = rmfile (out_root / config_file) || m; - - if (out_root != src_root) - { - m = rmfile (out_root / src_root_file) || m; - - // Clean up the directories. - // - m = rmdir (out_root / bootstrap_dir) || m; - m = rmdir (out_root / build_dir) || m; - - switch (rmdir (out_root)) - { - case rmdir_status::not_empty: - { - warn << "directory " << out_root << " is " - << (out_root == work - ? "current working directory" - : "not empty") << ", not removing"; - break; - } - case rmdir_status::success: - m = true; - default: - break; - } - } - } - else - { - } - - return m; - } - - static void - disfigure_execute (action a, const action_targets& ts, bool quiet) - { - tracer trace ("disfigure_execute"); - - for (void* v: ts) - { - scope& root (*static_cast<scope*> (v)); - - if (!disfigure_project (a, root)) - { - // Create a dir{$out_root/} target to signify the project's - // root in diagnostics. Not very clean but seems harmless. - // - target& t ( - targets.insert ( - dir::static_type, root.out_path (), "", nullptr, trace).first); - - if (!quiet) - info << diag_done (a, t); - } - } - } - - static void - disfigure_meta_operation_post () - { - tracer trace ("disfigure_meta_operation_post"); - - // Reset the dependency state since anything that could have been - // loaded earlier using a previous configuration is now invalid. - // - level6 ([&]{trace << "resetting dependency state";}); - reset (); - } - - meta_operation_info disfigure { - "disfigure", - "disfigure", - "disfiguring", - "is disfigured", - nullptr, // meta-operation pre - &disfigure_operation_pre, - &disfigure_load, - &disfigure_search, - &disfigure_match, - &disfigure_execute, - nullptr, // operation post - &disfigure_meta_operation_post - }; - } -} diff --git a/build/config/utility b/build/config/utility deleted file mode 100644 index ee5f50b..0000000 --- a/build/config/utility +++ /dev/null @@ -1,128 +0,0 @@ -// file : build/config/utility -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD_CONFIG_UTILITY -#define BUILD_CONFIG_UTILITY - -#include <string> -#include <utility> // pair -#include <functional> // reference_wrapper - -#include <build/types> -#include <build/variable> -#include <build/diagnostics> - -namespace build -{ - class scope; - - namespace config - { - // Set, if necessary, a required config.* variable. - // - // If override is true and the variable doesn't come from this root - // scope or from the command line, then its value is "overridden" - // for this root scope. - // - // Return the reference to the value as well as the indication of - // whether the variable has actually been set. - // - template <typename T> - std::pair<std::reference_wrapper<const value>, bool> - required (scope& root, - const variable&, - const T& default_value, - bool override = false); - - template <typename T> - inline std::pair<std::reference_wrapper<const value>, bool> - required (scope& root, - const std::string& name, - const T& default_value, - bool override = false) - { - return required (root, var_pool.find (name), default_value, override); - } - - inline std::pair<std::reference_wrapper<const value>, bool> - required (scope& root, - const std::string& name, - const char* default_value, - bool override = false) - { - return required (root, name, std::string (default_value), override); - } - - // Set, if necessary, an optional config.* variable. In particular, - // an unspecified variable is set to NULL which is used to distinguish - // between the "configured as unspecified" and "not yet configured" - // cases. - // - // Return the value, which can be NULL. - // - const value& - optional (scope& root, const variable&); - - inline const value& - optional (scope& root, const std::string& var) - { - return optional (root, var_pool.find (var)); - } - - // As above but assumes the value is dir_path and makes it - // absolute if the value specified on the command line is - // relative. - // - const value& - optional_absolute (scope& root, const variable&); - - inline const value& - optional_absolute (scope& root, const std::string& var) - { - return optional_absolute (root, var_pool.find (var)); - } - - // Check whether there are any variables specified from the config - // namespace. The idea is that we can check if there are any, say, - // config.install.* values. If there are none, then we can assume - // this functionality is not (yet) used and omit writing a whole - // bunch of NULL config.install.* values to the config.build file. - // We call it omitted/delayed configuration. - // - // Note that this function detects and ignores the special - // config.*.configured variable which may be used by a module to - // "remember" that it is unconfigured. - // - bool - specified (scope& root, const std::string& ns); - - // @@ Why are these here? - // - - // Add all the values from a variable to the C-string list. T is - // either target or scope. The variable is expected to be of type - // strings. - // - template <typename T> - void - append_options (cstrings& args, T& s, const char* var); - - // As above but from the strings value directly. - // - void - append_options (cstrings& args, const const_strings_value&); - - // Check if a specified option is present in the variable value. - // T is either target or scope. - // - template <typename T> - bool - find_option (const char* option, T& s, const char* var); - } -} - -#include <build/config/utility.txx> -#include <build/config/utility.ixx> - -#endif // BUILD_CONFIG_UTILITY diff --git a/build/config/utility.cxx b/build/config/utility.cxx deleted file mode 100644 index b81cb04..0000000 --- a/build/config/utility.cxx +++ /dev/null @@ -1,92 +0,0 @@ -// file : build/config/utility.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <build/config/utility> - -#include <build/context> - -using namespace std; - -namespace build -{ - namespace config - { - const value& - optional (scope& root, const variable& var) - { - auto l (root[var]); - - return l.defined () - ? l.belongs (*global_scope) ? (root.assign (var) = *l) : *l - : root.assign (var); // NULL - } - - const value& - optional_absolute (scope& root, const variable& var) - { - auto l (root[var]); - - if (!l.defined ()) - return root.assign (var); // NULL - - if (!l.belongs (*global_scope)) // Value from (some) root scope. - return *l; - - // Make the command-line value absolute. This is necessary to avoid - // a warning issued by the config module about global/root scope - // value mismatch. - // - value& v (const_cast<value&> (*l)); - - if (v && !v.empty ()) - { - dir_path& d (as<dir_path> (v)); - - if (d.relative ()) - { - d = work / d; - d.normalize (); - } - } - - return root.assign (var) = v; - } - - bool - specified (scope& r, const string& ns) - { - // Search all outer scopes for any value in this namespace. - // - for (scope* s (&r); s != nullptr; s = s->parent_scope ()) - { - for (auto p (s->vars.find_namespace (ns)); - p.first != p.second; - ++p.first) - { - const variable& var (p.first->first); - - // Ignore config.*.configured. - // - if (var.name.size () < 11 || - var.name.compare (var.name.size () - 11, 11, ".configured") != 0) - return true; - } - } - - return false; - } - - void - append_options (cstrings& args, const const_strings_value& sv) - { - if (!sv.empty ()) - { - args.reserve (args.size () + sv.size ()); - - for (const string& s: sv) - args.push_back (s.c_str ()); - } - } - } -} diff --git a/build/config/utility.ixx b/build/config/utility.ixx deleted file mode 100644 index 4e32119..0000000 --- a/build/config/utility.ixx +++ /dev/null @@ -1,17 +0,0 @@ -// file : build/config/utility.ixx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -namespace build -{ - namespace config - { - template <typename T> - inline void - append_options (cstrings& args, T& s, const char* var) - { - if (auto l = s[var]) - append_options (args, as<strings> (*l)); - } - } -} diff --git a/build/config/utility.txx b/build/config/utility.txx deleted file mode 100644 index 06cb2eb..0000000 --- a/build/config/utility.txx +++ /dev/null @@ -1,45 +0,0 @@ -// file : build/config/utility.txx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <build/scope> - -namespace build -{ - namespace config - { - template <typename T> - std::pair<std::reference_wrapper<const value>, bool> - required (scope& root, const variable& var, const T& def_value, bool ovr) - { - using result = std::pair<std::reference_wrapper<const value>, bool>; - - if (auto l = root[var]) - { - if (l.belongs (*global_scope)) - return result (root.assign (var) = *l, true); - - if (!ovr || l.belongs (root)) - return result (*l, false); - } - - return result (root.assign (var) = def_value, true); - } - - template <typename T> - bool - find_option (const char* option, T& s, const char* var) - { - if (auto l = s[var]) - { - for (const std::string& s: as<strings> (*l)) - { - if (s == option) - return true; - } - } - - return false; - } - } -} diff --git a/build/context b/build/context deleted file mode 100644 index b0ac6dd..0000000 --- a/build/context +++ /dev/null @@ -1,165 +0,0 @@ -// file : build/context -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD_CONTEXT -#define BUILD_CONTEXT - -#include <string> -#include <ostream> -#include <cstdint> // uint64_t - -#include <butl/filesystem> - -#include <build/types> -#include <build/utility> -#include <build/operation> - -namespace build -{ - class scope; - class file; - - extern dir_path work; - extern dir_path home; - - extern string_pool path_pool; - extern string_pool extension_pool; - extern string_pool project_name_pool; - - // Current action (meta/operation). - // - extern const meta_operation_info* current_mif; - extern const operation_info* current_inner_oif; - extern const operation_info* current_outer_oif; - - extern execution_mode current_mode; - - // Total number of dependency relationships in the current action. - // Together with the target::dependents count it is incremented - // during the rule search & match phase and is decremented during - // execution with the expectation of it reaching 0. Used as a sanity - // check. - // - extern std::uint64_t dependency_count; - - // Reset the dependency state. In particular, this removes all the - // targets, scopes, and variable names. - // - void - reset (); - - // The dual interface wrapper for the {mk,rm}{file,dir}() functions - // below that allows you to use it as a true/false return or a more - // detailed enum from <butl/filesystem> - // - template <typename T> - struct fs_status - { - T v; - fs_status (T v): v (v) {}; - operator T () const {return v;} - explicit operator bool () const {return v == T::success;} - }; - - // Create the directory and print the standard diagnostics. Note that - // this implementation is not suitable if it is expected that the - // directory will exist in the majority of case and performance is - // important. See the fsdir{} rule for details. - // - fs_status<butl::mkdir_status> - mkdir (const dir_path&); - - fs_status<butl::mkdir_status> - mkdir_p (const dir_path&); - - // Remove the file and print the standard diagnostics. The second - // argument is only used in diagnostics, to print the target name. - // Passing the path for target will result in the relative path - // being printed. - // - template <typename T> - fs_status<butl::rmfile_status> - rmfile (const path&, const T& target); - - inline fs_status<butl::rmfile_status> - rmfile (const path& f) {return rmfile (f, f);} - - // Similar to rmfile() but for directories. - // - template <typename T> - fs_status<butl::rmdir_status> - rmdir (const dir_path&, const T& target); - - inline fs_status<butl::rmdir_status> - rmdir (const dir_path& d) {return rmdir (d, d);} - - // Note that this function returns not_empty if we try to remove - // a working directory. - // - fs_status<butl::rmdir_status> - rmdir_r (const dir_path&); - - // Return the src/out directory corresponding to the given out/src. The - // passed directory should be a sub-directory of out/src_root. - // - dir_path - src_out (const dir_path& out, scope&); - - dir_path - src_out (const dir_path& out, - const dir_path& out_root, const dir_path& src_root); - - dir_path - out_src (const dir_path& src, scope&); - - dir_path - out_src (const dir_path& src, - const dir_path& out_root, const dir_path& src_root); - - // If possible and beneficial, translate an absolute, normalized path - // into relative to the relative_base directory, which is normally - // work. Note that if the passed path is the same as relative_base, - // then this function returns empty path. - // - template <typename K> - basic_path<char, K> - relative (const basic_path<char, K>&); - - // By default this points to work. Setting this to something else - // should only be done in tightly controlled, non-parallel - // situations (see dump). If base is empty, then relative() - // returns the original path. - // - extern const dir_path* relative_base; - - // In addition to calling relative(), this function also uses shorter - // notations such as '~/'. - // - std::string - diag_relative (const path&); - - // As above but also adds trailing '/'. If the path is the same as - // base, returns "./" if current is true and empty string otherwise. - // - std::string - diag_relative (const dir_path&, bool current = true); - - // Action phrases, e.g., "configure update exe{foo}", "updating exe{foo}", - // and "updating exe{foo} is configured". - // - class target; - - std::string - diag_do (const action&, const target&); - - std::string - diag_doing (const action&, const target&); - - std::string - diag_done (const action&, const target&); -} - -#include <build/context.txx> - -#endif // BUILD_CONTEXT diff --git a/build/context.cxx b/build/context.cxx deleted file mode 100644 index a4815fb..0000000 --- a/build/context.cxx +++ /dev/null @@ -1,391 +0,0 @@ -// file : build/context.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <build/context> - -#include <ostream> -#include <sstream> -#include <cassert> -#include <system_error> - -#include <build/scope> -#include <build/target> -#include <build/rule> -#include <build/diagnostics> - -using namespace std; -using namespace butl; - -namespace build -{ - 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 <build/operation>. - // - meta_operation_table.clear (); - meta_operation_table.insert ("perform"); - meta_operation_table.insert ("configure"); - meta_operation_table.insert ("disfigure"); - meta_operation_table.insert ("dist"); - - operation_table.clear (); - operation_table.insert ("default"); - operation_table.insert ("update"); - operation_table.insert ("clean"); - operation_table.insert ("test"); - operation_table.insert ("install"); - - // Enter builtin variables. - // - { - auto& v (var_pool); - - v.find ("work", dir_path_type); - v.find ("home", dir_path_type); - - v.find ("src_root", dir_path_type); - v.find ("out_root", dir_path_type); - v.find ("src_base", dir_path_type); - v.find ("out_base", dir_path_type); - - v.find ("project", string_type); - v.find ("amalgamation", dir_path_type); - - // Shouldn't be typed since the value requires pre-processing. - // - v.find ("subprojects", nullptr, '='); - - v.find ("extension", string_type); - } - - // Create global scope. For Win32 this is not a "real" root path. - // On POSIX, however, this is a real path. See the comment in - // <build/path-map> for details. - // - global_scope = scopes.insert ( - dir_path ("/"), nullptr, true, false)->second; - - global_scope->assign ("work") = work; - global_scope->assign ("home") = home; - - // Register builtin target types. - // - { - target_type_map& t (global_scope->target_types); - - t.insert<file> (); - t.insert<alias> (); - t.insert<dir> (); - t.insert<fsdir> (); - t.insert<doc> (); - t.insert<man> (); - t.insert<man1> (); - } - - // Register builtin rules. - // - { - rule_map& r (global_scope->rules); - - r.insert<alias> (perform_id, 0, "alias", alias_rule::instance); - - r.insert<fsdir> (perform_update_id, "fsdir", fsdir_rule::instance); - r.insert<fsdir> (perform_clean_id, "fsdir", fsdir_rule::instance); - - r.insert<file> (perform_update_id, "file", file_rule::instance); - r.insert<file> (perform_clean_id, "file", file_rule::instance); - } - } - - fs_status<mkdir_status> - mkdir (const dir_path& d) - { - // We don't want to print the command if the directory already - // exists. This makes the below code a bit ugly. - // - mkdir_status ms; - - try - { - ms = try_mkdir (d); - } - catch (const system_error& e) - { - if (verb) - text << "mkdir " << d; - - fail << "unable to create directory " << d << ": " << e.what (); - } - - if (ms == mkdir_status::success) - { - if (verb) - text << "mkdir " << d; - } - - return ms; - } - - fs_status<mkdir_status> - mkdir_p (const dir_path& d) - { - // We don't want to print the command if the directory already - // exists. This makes the below code a bit ugly. - // - mkdir_status ms; - - try - { - ms = try_mkdir_p (d); - } - catch (const system_error& e) - { - if (verb) - text << "mkdir -p " << d; - - fail << "unable to create directory " << d << ": " << e.what (); - } - - if (ms == mkdir_status::success) - { - if (verb) - text << "mkdir -p " << d; - } - - return ms; - } - - fs_status<butl::rmdir_status> - rmdir_r (const dir_path& d) - { - using namespace butl; - - if (work.sub (d)) // Don't try to remove working directory. - return rmdir_status::not_empty; - - if (!dir_exists (d)) - return rmdir_status::not_exist; - - if (verb) - text << "rmdir -r " << d; - - try - { - butl::rmdir_r (d); - } - catch (const std::system_error& e) - { - fail << "unable to remove directory " << d << ": " << e.what (); - } - - return rmdir_status::success; - } - - dir_path - src_out (const dir_path& out, scope& s) - { - scope& rs (*s.root_scope ()); - return src_out (out, rs.out_path (), rs.src_path ()); - } - - dir_path - out_src (const dir_path& src, scope& s) - { - scope& rs (*s.root_scope ()); - return out_src (src, rs.out_path (), rs.src_path ()); - } - - dir_path - src_out (const dir_path& o, - const dir_path& out_root, const dir_path& src_root) - { - assert (o.sub (out_root)); - return src_root / o.leaf (out_root); - } - - dir_path - out_src (const dir_path& s, - const dir_path& out_root, const dir_path& src_root) - { - assert (s.sub (src_root)); - return out_root / s.leaf (src_root); - } - - // relative() - // - const dir_path* relative_base = &work; - - string - diag_relative (const path& p) - { - const path& b (*relative_base); - - if (p.absolute ()) - { - if (p == b) - return "."; - -#ifndef _WIN32 - if (p == home) - return "~"; -#endif - - path rb (relative (p)); - -#ifndef _WIN32 - if (rb.relative ()) - { - // See if the original path with the ~/ shortcut is better - // that the relative to base. - // - if (p.sub (home)) - { - path rh (p.leaf (home)); - if (rb.string ().size () > rh.string ().size () + 2) // 2 for '~/' - return "~/" + rh.string (); - } - } - else if (rb.sub (home)) - return "~/" + rb.leaf (home).string (); -#endif - - return rb.string (); - } - - return p.string (); - } - - string - diag_relative (const dir_path& d, bool cur) - { - string r (diag_relative (static_cast<const path&> (d))); - - // Translate "." to empty. - // - if (!cur && d.absolute () && r == ".") - r.clear (); - - // Add trailing '/'. - // - if (!r.empty () && !dir_path::traits::is_separator (r.back ())) - r += '/'; - - return r; - } - - // diag_do(), etc. - // - string - diag_do (const action&, const target& t) - { - const meta_operation_info& m (*current_mif); - const operation_info& io (*current_inner_oif); - const operation_info* oo (current_outer_oif); - - ostringstream os; - - // perform(update(x)) -> "update x" - // configure(update(x)) -> "configure updating x" - // - if (m.name_do.empty ()) - os << io.name_do << ' '; - else - { - os << m.name_do << ' '; - - if (!io.name_doing.empty ()) - os << io.name_doing << ' '; - } - - if (oo != nullptr) - os << "(for " << oo->name << ") "; - - os << t; - return os.str (); - } - - string - diag_doing (const action&, const target& t) - { - const meta_operation_info& m (*current_mif); - const operation_info& io (*current_inner_oif); - const operation_info* oo (current_outer_oif); - - ostringstream os; - - // perform(update(x)) -> "updating x" - // configure(update(x)) -> "configuring updating x" - // - if (!m.name_doing.empty ()) - os << m.name_doing << ' '; - - if (!io.name_doing.empty ()) - os << io.name_doing << ' '; - - if (oo != nullptr) - os << "(for " << oo->name << ") "; - - os << t; - return os.str (); - } - - string - diag_done (const action&, const target& t) - { - const meta_operation_info& m (*current_mif); - const operation_info& io (*current_inner_oif); - const operation_info* oo (current_outer_oif); - - ostringstream os; - - // perform(update(x)) -> "x is up to date" - // configure(update(x)) -> "updating x is configured" - // - if (m.name_done.empty ()) - { - os << t; - - if (!io.name_done.empty ()) - os << " " << io.name_done; - - if (oo != nullptr) - os << "(for " << oo->name << ") "; - } - else - { - if (!io.name_doing.empty ()) - os << io.name_doing << ' '; - - if (oo != nullptr) - os << "(for " << oo->name << ") "; - - os << t << " " << m.name_done; - } - - return os.str (); - } -} diff --git a/build/context.txx b/build/context.txx deleted file mode 100644 index a0660f9..0000000 --- a/build/context.txx +++ /dev/null @@ -1,141 +0,0 @@ -// file : build/context.txx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <system_error> - -#include <build/diagnostics> - -namespace build -{ - template <typename T> - fs_status<butl::rmfile_status> - rmfile (const path& f, const T& t) - { - using namespace butl; - - // We don't want to print the command if we couldn't remove the - // file because it does not exist (just like we don't print the - // update command if the file is up to date). This makes the - // below code a bit ugly. - // - rmfile_status rs; - - try - { - rs = try_rmfile (f); - } - catch (const std::system_error& e) - { - if (verb >= 2) - text << "rm " << f; - else if (verb) - text << "rm " << t; - - fail << "unable to remove file " << f << ": " << e.what (); - } - - if (rs == rmfile_status::success) - { - if (verb >= 2) - text << "rm " << f; - else if (verb) - text << "rm " << t; - } - - return rs; - } - - template <typename T> - fs_status<butl::rmdir_status> - rmdir (const dir_path& d, const T& t) - { - using namespace butl; - - bool w (work.sub (d)); // Don't try to remove working directory. - rmdir_status rs; - - // We don't want to print the command if we couldn't remove the - // directory because it does not exist (just like we don't print - // mkdir if it already exists) or if it is not empty. This makes - // the below code a bit ugly. - // - try - { - rs = !w ? try_rmdir (d) : rmdir_status::not_empty; - } - catch (const std::system_error& e) - { - if (verb >= 2) - text << "rmdir " << d; - else if (verb) - text << "rmdir " << t; - - fail << "unable to remove directory " << d << ": " << e.what (); - } - - switch (rs) - { - case rmdir_status::success: - { - if (verb >= 2) - text << "rmdir " << d; - else if (verb) - text << "rmdir " << t; - - break; - } - case rmdir_status::not_empty: - { - if (verb >= 2) - text << "directory " << d << " is " - << (w ? "current working directory" : "not empty") - << ", not removing"; - - break; - } - case rmdir_status::not_exist: - break; - } - - return rs; - } - - template <typename K> - basic_path<char, K> - relative (const basic_path<char, K>& p) - { - typedef basic_path<char, K> path; - - const dir_path& b (*relative_base); - - if (b.empty ()) - return p; - - if (p.sub (b)) - return p.leaf (b); - - // If base is a sub-path of {src,out}_root and this path is also a - // sub-path of it, then use '..' to form a relative path. - // - // Don't think this is a good heuristic. For example, why shouldn't - // we display paths from imported projects as relative if they are - // more readable than absolute? - // - /* - if ((work.sub (src_root) && p.sub (src_root)) || - (work.sub (out_root) && p.sub (out_root))) - return p.relative (work); - */ - - if (p.root_directory () == b.root_directory ()) - { - path r (p.relative (b)); - - if (r.string ().size () < p.string ().size ()) - return r; - } - - return p; - } -} diff --git a/build/cxx/compile b/build/cxx/compile deleted file mode 100644 index cb44829..0000000 --- a/build/cxx/compile +++ /dev/null @@ -1,32 +0,0 @@ -// file : build/cxx/compile -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD_CXX_COMPILE -#define BUILD_CXX_COMPILE - -#include <build/types> -#include <build/rule> - -namespace build -{ - 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 // BUILD_CXX_COMPILE diff --git a/build/cxx/compile.cxx b/build/cxx/compile.cxx deleted file mode 100644 index 2481b62..0000000 --- a/build/cxx/compile.cxx +++ /dev/null @@ -1,794 +0,0 @@ -// file : build/cxx/compile.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <build/cxx/compile> - -#include <map> -#include <string> -#include <cstddef> // size_t -#include <cstdlib> // exit() -#include <utility> // move() - -#include <butl/process> -#include <butl/utility> // reverse_iterate -#include <butl/fdstream> -#include <butl/path-map> - -#include <build/types> -#include <build/scope> -#include <build/variable> -#include <build/algorithm> -#include <build/diagnostics> -#include <build/context> - -#include <build/bin/target> -#include <build/cxx/target> - -#include <build/cxx/utility> -#include <build/cxx/link> - -using namespace std; -using namespace butl; - -namespace build -{ - namespace cxx - { - using namespace bin; - - match_result compile:: - match (action a, target& t, const string&) const - { - tracer trace ("cxx::compile::match"); - - // @@ TODO: - // - // - check prerequisites: single source file - // - if path already assigned, verify extension? - // - - // See if we have a C++ source file. Iterate in reverse so that - // a source file specified for an obj*{} member overrides the one - // specified for the group. Also "see through" groups. - // - for (prerequisite_member p: reverse_group_prerequisite_members (a, t)) - { - if (p.is_a<cxx> ()) - return p; - } - - level4 ([&]{trace << "no c++ source file for target " << t;}); - return nullptr; - } - - static void - inject_prerequisites (action, target&, cxx&, scope&); - - recipe compile:: - apply (action a, target& xt, const match_result& mr) const - { - path_target& t (static_cast<path_target&> (xt)); - - // Derive file name from target name. - // - if (t.path ().empty ()) - t.derive_path ("o", nullptr, (t.is_a<objso> () ? "-so" : nullptr)); - - // Inject dependency on the output directory. - // - inject_parent_fsdir (a, t); - - // Search and match all the existing prerequisites. The injection - // code (below) takes care of the ones it is adding. - // - // When cleaning, ignore prerequisites that are not in the same - // or a subdirectory of our strong amalgamation. - // - const dir_path* amlg ( - a.operation () != clean_id - ? nullptr - : &t.strong_scope ().out_path ()); - - link::search_paths_cache lib_paths; // Extract lazily. - - for (prerequisite_member p: group_prerequisite_members (a, t)) - { - // A dependency on a library is there so that we can get its - // cxx.export.poptions. In particular, making sure it is - // executed before us will only restrict parallelism. But we - // do need to pre-match it in order to get its - // prerequisite_targets populated. This is the "library - // meta-information protocol". See also append_lib_options() - // above. - // - if (p.is_a<lib> () || p.is_a<liba> () || p.is_a<libso> ()) - { - if (a.operation () == update_id) - { - // Handle imported libraries. We know that for such libraries - // we don't need to do match() in order to get options (if - // any, they would be set by search_library()). - // - if (p.proj () == nullptr || - link::search_library (lib_paths, p.prerequisite) == nullptr) - { - match_only (a, p.search ()); - } - } - - continue; - } - - target& pt (p.search ()); - - if (a.operation () == clean_id && !pt.dir.sub (*amlg)) - continue; - - build::match (a, pt); - t.prerequisite_targets.push_back (&pt); - } - - // Inject additional prerequisites. We only do it when - // performing update since chances are we will have to - // update some of our prerequisites in the process (auto- - // generated source code). - // - if (a == perform_update_id) - { - // The cached prerequisite target should be the same as what - // is in t.prerequisite_targets since we used standard - // search() and match() above. - // - // @@ Ugly. - // - cxx& st ( - dynamic_cast<cxx&> ( - mr.target != nullptr ? *mr.target : *mr.prerequisite->target)); - inject_prerequisites (a, t, st, mr.prerequisite->scope); - } - - switch (a) - { - case perform_update_id: return &perform_update; - case perform_clean_id: return &perform_clean; - default: return noop_recipe; // Configure update. - } - } - - // Reverse-lookup target type from extension. - // - static const target_type* - map_extension (scope& s, const string& n, const string& e) - { - // We will just have to try all of the possible ones, in the - // "most likely to match" order. - // - const variable& var (var_pool.find ("extension")); - - auto test = [&s, &n, &e, &var] (const target_type& tt) - -> const target_type* - { - if (auto l = s.lookup (tt, n, var)) - if (as<string> (*l) == e) - return &tt; - - return nullptr; - }; - - if (auto r = test (hxx::static_type)) return r; - if (auto r = test (h::static_type)) return r; - if (auto r = test (ixx::static_type)) return r; - if (auto r = test (txx::static_type)) return r; - if (auto r = test (cxx::static_type)) return r; - if (auto r = test (c::static_type)) return r; - - return nullptr; - } - - // Mapping of include prefixes (e.g., foo in <foo/bar>) for auto- - // generated headers to directories where they will be generated. - // - // We are using a prefix map of directories (dir_path_map) instead - // of just a map in order also cover sub-paths (e.g., <foo/more/bar> - // if we continue with the example). Specifically, we need to make - // sure we don't treat foobar as a sub-directory of foo. - // - // @@ The keys should be canonicalized. - // - using prefix_map = dir_path_map<dir_path>; - - static void - append_prefixes (prefix_map& m, target& t, const char* var) - { - tracer trace ("cxx::append_prefixes"); - - // If this target does not belong to any project (e.g, an - // "imported as installed" library), then it can't possibly - // generate any headers for us. - // - scope* rs (t.base_scope ().root_scope ()); - if (rs == nullptr) - return; - - const dir_path& out_base (t.dir); - const dir_path& out_root (rs->out_path ()); - - if (auto l = t[var]) - { - const auto& v (as<strings> (*l)); - - for (auto i (v.begin ()), e (v.end ()); i != e; ++i) - { - // -I can either be in the "-Ifoo" or "-I foo" form. - // - dir_path d; - if (*i == "-I") - { - if (++i == e) - break; // Let the compiler complain. - - d = dir_path (*i); - } - else if (i->compare (0, 2, "-I") == 0) - d = dir_path (*i, 2, string::npos); - else - continue; - - level6 ([&]{trace << "-I '" << d << "'";}); - - // If we are relative or not inside our project root, then - // ignore. - // - if (d.relative () || !d.sub (out_root)) - continue; - - // If the target directory is a sub-directory of the include - // directory, then the prefix is the difference between the - // two. Otherwise, leave it empty. - // - // The idea here is to make this "canonical" setup work auto- - // magically: - // - // 1. We include all files with a prefix, e.g., <foo/bar>. - // 2. The library target is in the foo/ sub-directory, e.g., - // /tmp/foo/. - // 3. The poptions variable contains -I/tmp. - // - dir_path p (out_base.sub (d) ? out_base.leaf (d) : dir_path ()); - - auto j (m.find (p)); - - if (j != m.end ()) - { - if (j->second != d) - { - // We used to reject duplicates but it seems this can - // be reasonably expected to work according to the order - // of the -I options. - // - if (verb >= 4) - trace << "overriding dependency prefix '" << p << "'\n" - << " old mapping to " << j->second << "\n" - << " new mapping to " << d; - - j->second = d; - } - } - else - { - level6 ([&]{trace << "'" << p << "' = '" << d << "'";}); - m.emplace (move (p), move (d)); - } - } - } - } - - // Append library prefixes based on the cxx.export.poptions variables - // recursively, prerequisite libraries first. - // - static void - append_lib_prefixes (prefix_map& m, target& l) - { - for (target* t: l.prerequisite_targets) - { - if (t == nullptr) - continue; - - if (t->is_a<lib> () || t->is_a<liba> () || t->is_a<libso> ()) - append_lib_prefixes (m, *t); - } - - append_prefixes (m, l, "cxx.export.poptions"); - } - - static prefix_map - build_prefix_map (target& t) - { - prefix_map m; - - // First process the include directories from prerequisite - // libraries. Note that here we don't need to see group - // members (see apply()). - // - for (prerequisite& p: group_prerequisites (t)) - { - target& pt (*p.target); // Already searched and matched. - - if (pt.is_a<lib> () || pt.is_a<liba> () || pt.is_a<libso> ()) - append_lib_prefixes (m, pt); - } - - // Then process our own. - // - append_prefixes (m, t, "cxx.poptions"); - - return m; - } - - // Return the next make prerequisite starting from the specified - // position and update position to point to the start of the - // following prerequisite or l.size() if there are none left. - // - static string - next (const string& l, size_t& p) - { - size_t n (l.size ()); - - // Skip leading spaces. - // - for (; p != n && l[p] == ' '; p++) ; - - // Lines containing multiple prerequisites are 80 characters max. - // - string r; - r.reserve (n); - - // Scan the next prerequisite while watching out for escape sequences. - // - for (; p != n && l[p] != ' '; p++) - { - char c (l[p]); - - if (c == '\\') - c = l[++p]; - - r += c; - } - - // Skip trailing spaces. - // - for (; p != n && l[p] == ' '; p++) ; - - // Skip final '\'. - // - if (p == n - 1 && l[p] == '\\') - p++; - - return r; - } - - static void - inject_prerequisites (action a, target& t, cxx& s, scope& ds) - { - tracer trace ("cxx::compile::inject_prerequisites"); - - scope& rs (t.root_scope ()); - const string& cxx (as<string> (*rs["config.cxx"])); - - cstrings args {cxx.c_str ()}; - - // Add cxx.export.poptions from prerequisite libraries. Note - // that here we don't need to see group members (see apply()). - // - for (prerequisite& p: group_prerequisites (t)) - { - target& pt (*p.target); // Already searched and matched. - - if (pt.is_a<lib> () || pt.is_a<liba> () || pt.is_a<libso> ()) - append_lib_options (args, pt, "cxx.export.poptions"); - } - - append_options (args, t, "cxx.poptions"); - - // @@ Some C++ options (e.g., -std, -m) affect the preprocessor. - // Or maybe they are not C++ options? Common options? - // - append_options (args, t, "cxx.coptions"); - - string std; // Storage. - append_std (args, t, std); - - if (t.is_a<objso> ()) - args.push_back ("-fPIC"); - - args.push_back ("-M"); // Note: -MM -MG skips missing <>-included. - args.push_back ("-MG"); // Treat missing headers as generated. - args.push_back ("-MQ"); // Quoted target name. - args.push_back ("*"); // Old versions can't handle empty target name. - - // We are using absolute source file path in order to get absolute - // paths in the result. Any relative paths in the result are non- - // existent, potentially auto-generated headers. - // - // @@ We will also have to use absolute -I paths to guarantee - // that. Or just detect relative paths and error out? - // - args.push_back (s.path ().string ().c_str ()); - args.push_back (nullptr); - - level6 ([&]{trace << "target: " << t;}); - - // Build the prefix map lazily only if we have non-existent files. - // Also reuse it over restarts since it doesn't change. - // - prefix_map pm; - - // If any prerequisites that we have extracted changed, then we - // have to redo the whole thing. The reason for this is auto- - // generated headers: the updated header may now include a yet- - // non-existent header. Unless we discover this and generate it - // (which, BTW, will trigger another restart since that header, - // in turn, can also include auto-generated headers), we will - // end up with an error during compilation proper. - // - // One complication with this restart logic is that we will see - // a "prefix" of prerequisites that we have already processed - // (i.e., they are already in our prerequisite_targets list) and - // we don't want to keep redoing this over and over again. One - // thing to note, however, is that the prefix that we have seen - // on the previous run must appear exactly the same in the - // subsequent run. The reason for this is that none of the files - // that it can possibly be based on have changed and thus it - // should be exactly the same. To put it another way, the - // presence or absence of a file in the dependency output can - // only depend on the previous files (assuming the compiler - // outputs them as it encounters them and it is hard to think - // of a reason why would someone do otherwise). And we have - // already made sure that all those files are up to date. And - // here is the way we are going to exploit this: we are going - // to keep track of how many prerequisites we have processed so - // far and on restart skip right to the next one. - // - // Also, before we do all that, make sure the source file itself - // if up to date. - // - execute_direct (a, s); - - size_t skip_count (0); - for (bool restart (true); restart; ) - { - restart = false; - - if (verb >= 3) - print_process (args); - - try - { - process pr (args.data (), 0, -1); // Open pipe to stdout. - ifdstream is (pr.in_ofd); - - size_t skip (skip_count); - for (bool first (true), second (true); !(restart || is.eof ()); ) - { - string l; - getline (is, l); - - if (is.fail () && !is.eof ()) - fail << "error reading C++ compiler -M output"; - - size_t pos (0); - - if (first) - { - // Empty output should mean the wait() call below will return - // false. - // - if (l.empty ()) - break; - - assert (l[0] == '*' && l[1] == ':' && l[2] == ' '); - - first = false; - - // While normally we would have the source file on the - // first line, if too long, it will be moved to the next - // line and all we will have on this line is "*: \". - // - if (l.size () == 4 && l[3] == '\\') - continue; - else - pos = 3; // Skip "*: ". - - // Fall through to the 'second' block. - } - - if (second) - { - second = false; - next (l, pos); // Skip the source file. - } - - // If things go wrong (and they often do in this area), give - // the user a bit extra context. - // - auto g ( - make_exception_guard ( - [&s]() - { - info << "while extracting dependencies from " << s; - })); - - while (pos != l.size ()) - { - string fs (next (l, pos)); - - // Skip until where we left off. - // - if (skip != 0) - { - skip--; - continue; - } - - path f (move (fs)); - f.normalize (); - - if (!f.absolute ()) - { - // This is probably as often an error as an auto-generated - // file, so trace at level 4. - // - level4 ([&]{trace << "non-existent header '" << f << "'";}); - - // If we already did it and build_prefix_map() returned empty, - // then we would have failed below. - // - if (pm.empty ()) - pm = build_prefix_map (t); - - // First try the whole file. Then just the directory. - // - // @@ Has to be a separate map since the prefix can be - // the same as the file name. - // - // auto i (pm.find (f)); - - // Find the most qualified prefix of which we are a - // sub-path. - // - auto i (pm.end ()); - - if (!pm.empty ()) - { - const dir_path& d (f.directory ()); - i = pm.upper_bound (d); - - // Get the greatest less than, if any. We might - // still not be a sub. Note also that we still - // have to check the last element is upper_bound() - // returned end(). - // - if (i == pm.begin () || !d.sub ((--i)->first)) - i = pm.end (); - } - - if (i == pm.end ()) - fail << "unable to map presumably auto-generated header '" - << f << "' to a project"; - - f = i->second / f; - } - - level6 ([&]{trace << "injecting " << f;}); - - // Split the name into its directory part, the name part, and - // extension. Here we can assume the name part is a valid - // filesystem name. - // - // Note that if the file has no extension, we record an empty - // extension rather than NULL (which would signify that the - // default extension should be added). - // - dir_path d (f.directory ()); - string n (f.leaf ().base ().string ()); - const char* es (f.extension ()); - const string* e (&extension_pool.find (es != nullptr ? es : "")); - - // Determine the target type. - // - const target_type* tt (nullptr); - - // See if this directory is part of any project out_root - // hierarchy. Note that this will miss all the headers - // that come from src_root (so they will be treated as - // generic C headers below). Generally, we don't have - // the ability to determine that some file belongs to - // src_root of some project. But that's not a problem - // for our purposes: it is only important for us to - // accurately determine target types for headers that - // could be auto-generated. - // - scope& b (scopes.find (d)); - if (b.root_scope () != nullptr) - tt = map_extension (b, n, *e); - - // If it is outside any project, or the project doesn't have - // such an extension, assume it is a plain old C header. - // - if (tt == nullptr) - tt = &h::static_type; - - // Find or insert target. - // - path_target& pt ( - static_cast<path_target&> (search (*tt, d, n, e, &ds))); - - // Assign path. - // - if (pt.path ().empty ()) - pt.path (move (f)); - - // Match to a rule. - // - build::match (a, pt); - - // Update it. - // - // There would normally be a lot of headers for every source - // file (think all the system headers) and this can get - // expensive. At the same time, most of these headers are - // existing files that we will never be updated (again, - // system headers, for example) and the rule that will match - // them is fallback file_rule. That rule has an optimization - // in that it returns noop_recipe (which causes the target - // state to be automatically set to unchanged) if the file - // is known to be up to date. - // - if (pt.state () != target_state::unchanged) - { - // We only want to restart if our call to execute() actually - // caused an update. In particular, the target could already - // have been in target_state::changed because of a dependency - // extraction run for some other source file. - // - target_state os (pt.state ()); - target_state ns (execute_direct (a, pt)); - - if (ns != os && ns != target_state::unchanged) - { - level6 ([&]{trace << "updated " << pt << ", restarting";}); - restart = true; - } - } - - // Add to our prerequisite target list. - // - t.prerequisite_targets.push_back (&pt); - skip_count++; - } - } - - // We may not have read all the output (e.g., due to a restart), - // so close the file descriptor before waiting to avoid blocking - // the other end. - // - is.close (); - - // We assume the child process issued some diagnostics. - // - if (!pr.wait ()) - { - // In case of a restarts, we closed our end of the pipe early - // which might have caused the other end to fail. So far we - // experienced this on Fedora 23 with GCC 5.3.1 and there were - // no diagnostics issued, just the non-zero exit status. If we - // do get diagnostics, then we will have to read and discard the - // output until eof. - // - if (!restart) - throw failed (); - } - } - catch (const process_error& e) - { - error << "unable to execute " << args[0] << ": " << e.what (); - - // In a multi-threaded program that fork()'ed but did not exec(), - // it is unwise to try to do any kind of cleanup (like unwinding - // the stack and running destructors). - // - if (e.child ()) - exit (1); - - throw failed (); - } - } - } - - target_state compile:: - perform_update (action a, target& xt) - { - path_target& t (static_cast<path_target&> (xt)); - cxx* s (execute_prerequisites<cxx> (a, t, t.mtime ())); - - if (s == nullptr) - return target_state::unchanged; - - // Translate paths to relative (to working directory) ones. This - // results in easier to read diagnostics. - // - path relo (relative (t.path ())); - path rels (relative (s->path ())); - - scope& rs (t.root_scope ()); - const string& cxx (as<string> (*rs["config.cxx"])); - - cstrings args {cxx.c_str ()}; - - // Add cxx.export.poptions from prerequisite libraries. Note that - // here we don't need to see group members (see apply()). - // - for (prerequisite& p: group_prerequisites (t)) - { - target& pt (*p.target); // Already searched and matched. - - if (pt.is_a<lib> () || pt.is_a<liba> () || pt.is_a<libso> ()) - append_lib_options (args, pt, "cxx.export.poptions"); - } - - append_options (args, t, "cxx.poptions"); - append_options (args, t, "cxx.coptions"); - - string std; // Storage. - append_std (args, t, std); - - if (t.is_a<objso> ()) - args.push_back ("-fPIC"); - - args.push_back ("-o"); - args.push_back (relo.string ().c_str ()); - - args.push_back ("-c"); - args.push_back (rels.string ().c_str ()); - - args.push_back (nullptr); - - if (verb >= 2) - print_process (args); - else if (verb) - text << "c++ " << *s; - - try - { - process pr (args.data ()); - - if (!pr.wait ()) - throw failed (); - - // Should we go to the filesystem and get the new mtime? We - // know the file has been modified, so instead just use the - // current clock time. It has the advantage of having the - // subseconds precision. - // - t.mtime (system_clock::now ()); - return target_state::changed; - } - catch (const process_error& e) - { - error << "unable to execute " << args[0] << ": " << e.what (); - - // In a multi-threaded program that fork()'ed but did not exec(), - // it is unwise to try to do any kind of cleanup (like unwinding - // the stack and running destructors). - // - if (e.child ()) - exit (1); - - throw failed (); - } - } - - compile compile::instance; - } -} diff --git a/build/cxx/install b/build/cxx/install deleted file mode 100644 index 9df8408..0000000 --- a/build/cxx/install +++ /dev/null @@ -1,29 +0,0 @@ -// file : build/cxx/install -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD_CXX_INSTALL -#define BUILD_CXX_INSTALL - -#include <build/types> -#include <build/install/rule> - -namespace build -{ - namespace cxx - { - class install: public build::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 // BUILD_CXX_INSTALL diff --git a/build/cxx/install.cxx b/build/cxx/install.cxx deleted file mode 100644 index f8c2c06..0000000 --- a/build/cxx/install.cxx +++ /dev/null @@ -1,66 +0,0 @@ -// file : build/cxx/install.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <build/cxx/install> - -#include <build/bin/target> - -#include <build/cxx/target> -#include <build/cxx/link> - -using namespace std; - -namespace build -{ - namespace cxx - { - using namespace bin; - - target* install:: - filter (action a, target& t, prerequisite_member p) const - { - if (t.is_a<exe> ()) - { - // Don't install executable's prerequisite headers. - // - if (p.is_a<hxx> () || p.is_a<ixx> () || p.is_a<txx> () || p.is_a<h> ()) - return nullptr; - } - - // If this is a shared library prerequisite, install it as long as it - // is in the same amalgamation as we are. - // - if ((t.is_a<exe> () || t.is_a<libso> ()) && - (p.is_a<lib> () || p.is_a<libso> ())) - { - target* pt (&p.search ()); - - // If this is the lib{} group, pick a member which we would link. - // - if (lib* l = pt->is_a<lib> ()) - pt = &link::link_member (*l, link::link_order (t)); - - if (pt->is_a<libso> ()) // Can be liba{}. - return pt->in (t.weak_scope ()) ? pt : nullptr; - } - - return file_rule::filter (a, t, p); - } - - match_result install:: - match (action a, target& t, const std::string& hint) const - { - // @@ How do we split the hint between the two? - // - - // We only want to handle installation if we are also the - // ones building this target. So first run link's match(). - // - match_result r (link::instance.match (a, t, hint)); - return r ? install::file_rule::match (a, t, "") : r; - } - - install install::instance; - } -} diff --git a/build/cxx/link b/build/cxx/link deleted file mode 100644 index 5a5aafa..0000000 --- a/build/cxx/link +++ /dev/null @@ -1,70 +0,0 @@ -// file : build/cxx/link -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD_CXX_LINK -#define BUILD_CXX_LINK - -#include <vector> - -#include <butl/optional> - -#include <build/types> -#include <build/rule> - -#include <build/bin/target> - -namespace build -{ - namespace cxx - { - class link: public rule - { - public: - virtual match_result - match (action, target&, const std::string& hint) const; - - virtual recipe - apply (action, target&, const match_result&) const; - - static target_state - perform_update (action, target&); - - static link instance; - - public: - enum class type {e, a, so}; - enum class order {a, so, a_so, so_a}; - - static type - link_type (target& t) - { - return t.is_a<bin::exe> () - ? type::e - : (t.is_a<bin::liba> () ? type::a : type::so); - } - - static order - link_order (target&); - - // Determine the library member (liba or libso) to link. - // - static target& - link_member (bin::lib&, order); - - private: - friend class compile; - - using search_paths = std::vector<dir_path>; - using search_paths_cache = butl::optional<search_paths>; - - static target* - search_library (search_paths_cache&, prerequisite&); - - static search_paths - extract_library_paths (scope&); - }; - } -} - -#endif // BUILD_CXX_LINK diff --git a/build/cxx/link.cxx b/build/cxx/link.cxx deleted file mode 100644 index 81e6c0b..0000000 --- a/build/cxx/link.cxx +++ /dev/null @@ -1,875 +0,0 @@ -// file : build/cxx/link.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <build/cxx/link> - -#include <vector> -#include <string> -#include <cstddef> // size_t -#include <cstdlib> // exit() -#include <utility> // move() - -#include <butl/process> -#include <butl/utility> // reverse_iterate -#include <butl/fdstream> -#include <butl/optional> -#include <butl/path-map> -#include <butl/filesystem> - -#include <build/types> -#include <build/scope> -#include <build/variable> -#include <build/algorithm> -#include <build/diagnostics> -#include <build/context> - -#include <build/bin/target> -#include <build/cxx/target> - -#include <build/cxx/utility> - -using namespace std; -using namespace butl; - -namespace build -{ - namespace cxx - { - using namespace bin; - - link::order link:: - link_order (target& t) - { - const char* var; - - switch (link_type (t)) - { - case type::e: var = "bin.exe.lib"; break; - case type::a: var = "bin.liba.lib"; break; - case type::so: var = "bin.libso.lib"; break; - } - - const auto& v (as<strings> (*t[var])); - return v[0] == "shared" - ? v.size () > 1 && v[1] == "static" ? order::so_a : order::so - : v.size () > 1 && v[1] == "shared" ? order::a_so : order::a; - } - - target& link:: - link_member (bin::lib& l, order lo) - { - bool lso (true); - const string& at (as<string> (*l["bin.lib"])); // Available types. - - switch (lo) - { - case order::a: - case order::a_so: - lso = false; // Fall through. - case order::so: - case order::so_a: - { - if (lso ? at == "static" : at == "shared") - { - if (lo == order::a_so || lo == order::so_a) - lso = !lso; - else - fail << (lso ? "shared" : "static") << " build of " << l - << " is not available"; - } - } - } - - target* r (lso ? static_cast<target*> (l.so) : l.a); - - if (r == nullptr) - r = &search (lso ? libso::static_type : liba::static_type, - prerequisite_key {nullptr, l.key (), nullptr}); - - return *r; - } - - link::search_paths link:: - extract_library_paths (scope& bs) - { - search_paths r; - scope& rs (*bs.root_scope ()); - - // Extract user-supplied search paths (i.e., -L). - // - if (auto l = bs["cxx.loptions"]) - { - const auto& v (as<strings> (*l)); - - for (auto i (v.begin ()), e (v.end ()); i != e; ++i) - { - // -L can either be in the "-Lfoo" or "-L foo" form. - // - dir_path d; - if (*i == "-L") - { - if (++i == e) - break; // Let the compiler complain. - - d = dir_path (*i); - } - else if (i->compare (0, 2, "-L") == 0) - d = dir_path (*i, 2, string::npos); - else - continue; - - // Ignore relative paths. Or maybe we should warn? - // - if (!d.relative ()) - r.push_back (move (d)); - } - } - - // Extract system search paths. - // - cstrings args; - string std_storage; - - args.push_back (as<string> (*rs["config.cxx"]).c_str ()); - append_options (args, bs, "cxx.coptions"); - append_std (args, bs, std_storage); - append_options (args, bs, "cxx.loptions"); - args.push_back ("-print-search-dirs"); - args.push_back (nullptr); - - if (verb >= 3) - print_process (args); - - string l; - try - { - process pr (args.data (), 0, -1); // Open pipe to stdout. - ifdstream is (pr.in_ofd); - - while (!is.eof ()) - { - string s; - getline (is, s); - - if (is.fail () && !is.eof ()) - fail << "error reading C++ compiler -print-search-dirs output"; - - if (s.compare (0, 12, "libraries: =") == 0) - { - l.assign (s, 12, string::npos); - break; - } - } - - is.close (); // Don't block. - - if (!pr.wait ()) - throw failed (); - } - catch (const process_error& e) - { - error << "unable to execute " << args[0] << ": " << e.what (); - - if (e.child ()) - exit (1); - - throw failed (); - } - - if (l.empty ()) - fail << "unable to extract C++ compiler system library paths"; - - // Now the fun part: figuring out which delimiter is used. - // Normally it is ':' but on Windows it is ';' (or can be; - // who knows for sure). Also note that these paths are - // absolute (or should be). So here is what we are going - // to do: first look for ';'. If found, then that's the - // delimiter. If not found, then there are two cases: - // it is either a single Windows path or the delimiter - // is ':'. To distinguish these two cases we check if - // the path starts with a Windows drive. - // - char d (';'); - string::size_type e (l.find (d)); - - if (e == string::npos && - (l.size () < 2 || l[0] == '/' || l[1] != ':')) - { - d = ':'; - e = l.find (d); - } - - // Now chop it up. We already have the position of the - // first delimiter (if any). - // - for (string::size_type b (0);; e = l.find (d, (b = e + 1))) - { - r.emplace_back (l, b, (e != string::npos ? e - b : e)); - r.back ().normalize (); - - if (e == string::npos) - break; - } - - return r; - } - - target* link:: - search_library (search_paths_cache& spc, prerequisite& p) - { - tracer trace ("cxx::link::search_library"); - - // First check the cache. - // - if (p.target != nullptr) - return p.target; - - bool l (p.is_a<lib> ()); - const string* ext (l ? nullptr : p.ext); // Only for liba/libso. - - // Then figure out what we need to search for. - // - - // liba - // - path an; - const string* ae; - - if (l || p.is_a<liba> ()) - { - an = path ("lib" + p.name); - - // Note that p.scope should be the same as the target's for - // which we are looking for this library. The idea here is - // that we have to use the same "extension configuration" as - // the target's. - // - ae = ext == nullptr - ? &liba::static_type.extension (p.key ().tk, p.scope) - : ext; - - if (!ae->empty ()) - { - an += '.'; - an += *ae; - } - } - - // libso - // - path sn; - const string* se; - - if (l || p.is_a<libso> ()) - { - sn = path ("lib" + p.name); - se = ext == nullptr - ? &libso::static_type.extension (p.key ().tk, p.scope) - : ext; - - if (!se->empty ()) - { - sn += '.'; - sn += *se; - } - } - - // Now search. - // - if (!spc) - spc = extract_library_paths (p.scope); - - liba* a (nullptr); - libso* s (nullptr); - - path f; // Reuse the buffer. - const dir_path* pd; - for (const dir_path& d: *spc) - { - timestamp mt; - - // liba - // - if (!an.empty ()) - { - f = d; - f /= an; - - if ((mt = file_mtime (f)) != timestamp_nonexistent) - { - // Enter the target. Note that because the search paths are - // normalized, the result is automatically normalized as well. - // - a = &targets.insert<liba> (d, p.name, ae, trace); - - if (a->path ().empty ()) - a->path (move (f)); - - a->mtime (mt); - } - } - - // libso - // - if (!sn.empty ()) - { - f = d; - f /= sn; - - if ((mt = file_mtime (f)) != timestamp_nonexistent) - { - s = &targets.insert<libso> (d, p.name, se, trace); - - if (s->path ().empty ()) - s->path (move (f)); - - s->mtime (mt); - } - } - - if (a != nullptr || s != nullptr) - { - pd = &d; - break; - } - } - - if (a == nullptr && s == nullptr) - return nullptr; - - if (l) - { - // Enter the target group. - // - lib& l (targets.insert<lib> (*pd, p.name, p.ext, trace)); - - // It should automatically link-up to the members we have found. - // - assert (l.a == a); - assert (l.so == s); - - // Set the bin.lib variable to indicate what's available. - // - const char* bl (a != nullptr - ? (s != nullptr ? "both" : "static") - : "shared"); - l.assign ("bin.lib") = bl; - - p.target = &l; - } - else - p.target = p.is_a<liba> () ? static_cast<target*> (a) : s; - - return p.target; - } - - match_result link:: - match (action a, target& t, const string& hint) const - { - tracer trace ("cxx::link::match"); - - // @@ TODO: - // - // - if path already assigned, verify extension? - // - // @@ Q: - // - // - if there is no .o, are we going to check if the one derived - // from target exist or can be built? A: No. - // What if there is a library. Probably ok if .a, not if .so. - // (i.e., a utility library). - // - - type lt (link_type (t)); - - // Scan prerequisites and see if we can work with what we've got. - // - bool seen_cxx (false), seen_c (false), seen_obj (false), - seen_lib (false); - - for (prerequisite_member p: group_prerequisite_members (a, t)) - { - if (p.is_a<cxx> ()) - { - seen_cxx = seen_cxx || true; - } - else if (p.is_a<c> ()) - { - seen_c = seen_c || true; - } - else if (p.is_a<obja> ()) - { - if (lt == type::so) - fail << "shared library " << t << " prerequisite " << p - << " is static object"; - - seen_obj = seen_obj || true; - } - else if (p.is_a<objso> () || - p.is_a<obj> ()) - { - seen_obj = seen_obj || true; - } - else if (p.is_a<liba> () || - p.is_a<libso> () || - p.is_a<lib> ()) - { - seen_lib = seen_lib || true; - } - } - - // We will only chain a C source if there is also a C++ source or we - // were explicitly told to. - // - if (seen_c && !seen_cxx && hint < "cxx") - { - level4 ([&]{trace << "c prerequisite(s) without c++ or hint";}); - return nullptr; - } - - // If we have any prerequisite libraries (which also means that - // we match), search/import and pre-match them to implement the - // "library meta-information protocol". Don't do this if we are - // called from the install rule just to check if we would match. - // - if (seen_lib && lt != type::e && - a.operation () != install_id && a.outer_operation () != install_id) - { - if (t.group != nullptr) - t.group->prerequisite_targets.clear (); // lib{}'s - - search_paths_cache lib_paths; // Extract lazily. - - for (prerequisite_member p: group_prerequisite_members (a, t)) - { - if (p.is_a<lib> () || p.is_a<liba> () || p.is_a<libso> ()) - { - target* pt (nullptr); - - // Handle imported libraries. - // - if (p.proj () != nullptr) - pt = search_library (lib_paths, p.prerequisite); - - if (pt == nullptr) - { - pt = &p.search (); - match_only (a, *pt); - } - - // If the prerequisite came from the lib{} group, then also - // add it to lib's prerequisite_targets. - // - if (!p.prerequisite.belongs (t)) - t.group->prerequisite_targets.push_back (pt); - - t.prerequisite_targets.push_back (pt); - } - } - } - - return seen_cxx || seen_c || seen_obj || seen_lib ? &t : nullptr; - } - - recipe link:: - apply (action a, target& xt, const match_result&) const - { - tracer trace ("cxx::link::apply"); - - path_target& t (static_cast<path_target&> (xt)); - - type lt (link_type (t)); - bool so (lt == type::so); - order lo (link_order (t)); - - // Derive file name from target name. - // - if (t.path ().empty ()) - { - auto l (t["extension"]); - const char* e (l ? as<string> (*l).c_str () : nullptr); - - switch (lt) - { - case type::e: - { - t.derive_path (e != nullptr ? e : ""); - break; - } - case type::a: - case type::so: - { - auto l (t["bin.libprefix"]); - t.derive_path (e != nullptr ? e : (lt == type::a ? "a" : "so"), - l ? as<string> (*l).c_str () : "lib"); - break; - } - } - } - - t.prerequisite_targets.clear (); // See lib pre-match in match() above. - - // Inject dependency on the output directory. - // - inject_parent_fsdir (a, t); - - // We may need the project roots for rule chaining (see below). - // We will resolve them lazily only if needed. - // - scope* root (nullptr); - const dir_path* out_root (nullptr); - const dir_path* src_root (nullptr); - - search_paths_cache lib_paths; // Extract lazily. - - // Process prerequisites: do rule chaining for C and C++ source - // files as well as search and match. - // - // When cleaning, ignore prerequisites that are not in the same - // or a subdirectory of our strong amalgamation. - // - const dir_path* amlg ( - a.operation () != clean_id - ? nullptr - : &t.strong_scope ().out_path ()); - - for (prerequisite_member p: group_prerequisite_members (a, t)) - { - bool group (!p.prerequisite.belongs (t)); // Group's prerequisite. - target* pt (nullptr); - - if (!p.is_a<c> () && !p.is_a<cxx> ()) - { - // Handle imported libraries. - // - if (p.proj () != nullptr) - pt = search_library (lib_paths, p.prerequisite); - - // The rest is the same basic logic as in search_and_match(). - // - if (pt == nullptr) - pt = &p.search (); - - if (a.operation () == clean_id && !pt->dir.sub (*amlg)) - continue; // Skip. - - // If this is the obj{} or lib{} target group, then pick the - // appropriate member and make sure it is searched and matched. - // - if (obj* o = pt->is_a<obj> ()) - { - pt = so ? static_cast<target*> (o->so) : o->a; - - if (pt == nullptr) - pt = &search (so ? objso::static_type : obja::static_type, - p.key ()); - } - else if (lib* l = pt->is_a<lib> ()) - { - pt = &link_member (*l, lo); - } - - build::match (a, *pt); - t.prerequisite_targets.push_back (pt); - continue; - } - - if (root == nullptr) - { - // Which scope shall we use to resolve the root? Unlikely, - // but possible, the prerequisite is from a different project - // altogether. So we are going to use the target's project. - // - root = &t.root_scope (); - out_root = &root->out_path (); - src_root = &root->src_path (); - } - - const prerequisite_key& cp (p.key ()); // c(xx){} prerequisite key. - const target_type& o_type ( - group - ? obj::static_type - : (so ? objso::static_type : obja::static_type)); - - // Come up with the obj*{} target. The c(xx){} prerequisite - // directory can be relative (to the scope) or absolute. If it is - // relative, then use it as is. If it is absolute, then translate - // it to the corresponding directory under out_root. While the - // c(xx){} directory is most likely under src_root, it is also - // possible it is under out_root (e.g., generated source). - // - dir_path d; - { - const dir_path& cpd (*cp.tk.dir); - - if (cpd.relative () || cpd.sub (*out_root)) - d = cpd; - else - { - if (!cpd.sub (*src_root)) - fail << "out of project prerequisite " << cp << - info << "specify corresponding " << o_type.name << "{} " - << "target explicitly"; - - d = *out_root / cpd.leaf (*src_root); - } - } - - target& ot (search (o_type, d, *cp.tk.name, nullptr, cp.scope)); - - // If we are cleaning, check that this target is in the same or - // a subdirectory of our strong amalgamation. - // - if (a.operation () == clean_id && !ot.dir.sub (*amlg)) - { - // If we shouldn't clean obj{}, then it is fair to assume - // we shouldn't clean cxx{} either (generated source will - // be in the same directory as obj{} and if not, well, go - // find yourself another build system ;-)). - // - continue; // Skip. - } - - // If we have created the obj{} target group, pick one of its - // members; the rest would be primarily concerned with it. - // - if (group) - { - obj& o (static_cast<obj&> (ot)); - pt = so ? static_cast<target*> (o.so) : o.a; - - if (pt == nullptr) - pt = &search (so ? objso::static_type : obja::static_type, - o.dir, o.name, o.ext, nullptr); - } - else - pt = &ot; - - // If this obj*{} target already exists, then it needs to be - // "compatible" with what we are doing here. - // - // This gets a bit tricky. We need to make sure the source files - // are the same which we can only do by comparing the targets to - // which they resolve. But we cannot search the ot's prerequisites - // -- only the rule that matches can. Note, however, that if all - // this works out, then our next step is to match the obj*{} - // target. If things don't work out, then we fail, in which case - // searching and matching speculatively doesn't really hurt. - // - bool found (false); - for (prerequisite_member p1: - reverse_group_prerequisite_members (a, *pt)) - { - // Ignore some known target types (fsdir, headers, libraries). - // - if (p1.is_a<fsdir> () || - p1.is_a<h> () || - (p.is_a<cxx> () && (p1.is_a<hxx> () || - p1.is_a<ixx> () || - p1.is_a<txx> ())) || - p1.is_a<lib> () || - p1.is_a<liba> () || - p1.is_a<libso> ()) - { - continue; - } - - if (!p1.is_a<cxx> ()) - fail << "synthesized target for prerequisite " << cp - << " would be incompatible with existing target " << *pt << - info << "unexpected existing prerequisite type " << p1 << - info << "specify corresponding obj{} target explicitly"; - - if (!found) - { - build::match (a, *pt); // Now p1 should be resolved. - - // Searching our own prerequisite is ok. - // - if (&p.search () != &p1.search ()) - fail << "synthesized target for prerequisite " << cp << " would " - << "be incompatible with existing target " << *pt << - info << "existing prerequisite " << p1 << " does not match " - << cp << - info << "specify corresponding " << o_type.name << "{} target " - << "explicitly"; - - found = true; - // Check the rest of the prerequisites. - } - } - - if (!found) - { - // Note: add the source to the group, not the member. - // - ot.prerequisites.emplace_back (p.as_prerequisite (trace)); - - // Add our lib*{} prerequisites to the object file (see - // cxx.export.poptions above for details). Note: no need - // to go into group members. - // - // Initially, we were only adding imported libraries, but - // there is a problem with this approach: the non-imported - // library might depend on the imported one(s) which we will - // never "see" unless we start with this library. - // - for (prerequisite& p: group_prerequisites (t)) - { - if (p.is_a<lib> () || p.is_a<liba> () || p.is_a<libso> ()) - ot.prerequisites.emplace_back (p); - } - - build::match (a, *pt); - } - - t.prerequisite_targets.push_back (pt); - } - - switch (a) - { - case perform_update_id: return &perform_update; - case perform_clean_id: return &perform_clean; - default: return noop_recipe; // Configure update. - } - } - - target_state link:: - perform_update (action a, target& xt) - { - path_target& t (static_cast<path_target&> (xt)); - - type lt (link_type (t)); - bool so (lt == type::so); - - if (!execute_prerequisites (a, t, t.mtime ())) - return target_state::unchanged; - - // Translate paths to relative (to working directory) ones. This - // results in easier to read diagnostics. - // - path relt (relative (t.path ())); - - scope& rs (t.root_scope ()); - cstrings args; - - // Storage. - // - string std; - string soname; - strings sargs; - - if (lt == type::a) - { - //@@ ranlib - // - args.push_back ("ar"); - args.push_back ("-rc"); - args.push_back (relt.string ().c_str ()); - } - else - { - args.push_back (as<string> (*rs["config.cxx"]).c_str ()); - append_options (args, t, "cxx.coptions"); - append_std (args, t, std); - - if (so) - args.push_back ("-shared"); - - args.push_back ("-o"); - args.push_back (relt.string ().c_str ()); - - // Set soname. - // - if (so) - { - soname = "-Wl,-soname," + relt.leaf ().string (); - args.push_back (soname.c_str ()); - } - - // Add rpaths. First the ones specified by the user so that they - // take precedence. - // - if (auto l = t["bin.rpath"]) - for (const string& p: as<strings> (*l)) - sargs.push_back ("-Wl,-rpath," + p); - - // Then the paths of the shared libraries we are linking to. - // - for (target* pt: t.prerequisite_targets) - { - if (libso* ls = pt->is_a<libso> ()) - sargs.push_back ( - "-Wl,-rpath," + ls->path ().directory ().string ()); - } - } - - size_t oend (sargs.size ()); // Note the end of options. - - for (target* pt: t.prerequisite_targets) - { - path_target* ppt; - - if ((ppt = pt->is_a<obja> ()) || - (ppt = pt->is_a<objso> ()) || - (ppt = pt->is_a<liba> ()) || - (ppt = pt->is_a<libso> ())) - { - sargs.push_back (relative (ppt->path ()).string ()); // string()&& - } - } - - // Finish assembling args from sargs. - // - for (size_t i (0); i != sargs.size (); ++i) - { - if (lt != type::a && i == oend) - append_options (args, t, "cxx.loptions"); - - args.push_back (sargs[i].c_str ()); - } - - if (lt != type::a) - append_options (args, t, "cxx.libs"); - - args.push_back (nullptr); - - if (verb >= 2) - print_process (args); - else if (verb) - text << "ld " << t; - - try - { - process pr (args.data ()); - - if (!pr.wait ()) - throw failed (); - - // Should we go to the filesystem and get the new mtime? We - // know the file has been modified, so instead just use the - // current clock time. It has the advantage of having the - // subseconds precision. - // - t.mtime (system_clock::now ()); - return target_state::changed; - } - catch (const process_error& e) - { - error << "unable to execute " << args[0] << ": " << e.what (); - - // In a multi-threaded program that fork()'ed but did not exec(), - // it is unwise to try to do any kind of cleanup (like unwinding - // the stack and running destructors). - // - if (e.child ()) - exit (1); - - throw failed (); - } - } - - link link::instance; - } -} diff --git a/build/cxx/module b/build/cxx/module deleted file mode 100644 index a3bd436..0000000 --- a/build/cxx/module +++ /dev/null @@ -1,23 +0,0 @@ -// file : build/cxx/module -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD_CXX_MODULE -#define BUILD_CXX_MODULE - -#include <build/types> -#include <build/utility> - -#include <build/module> - -namespace build -{ - namespace cxx - { - extern "C" bool - cxx_init ( - scope&, scope&, const location&, unique_ptr<module>&, bool, bool); - } -} - -#endif // BUILD_CXX_MODULE diff --git a/build/cxx/module.cxx b/build/cxx/module.cxx deleted file mode 100644 index c979328..0000000 --- a/build/cxx/module.cxx +++ /dev/null @@ -1,230 +0,0 @@ -// file : build/cxx/module.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <build/cxx/module> - -#include <butl/process> -#include <butl/fdstream> - -#include <build/scope> -#include <build/diagnostics> - -#include <build/config/utility> -#include <build/install/utility> - -#include <build/bin/target> - -#include <build/cxx/target> -#include <build/cxx/compile> -#include <build/cxx/link> -#include <build/cxx/install> - -using namespace std; -using namespace butl; - -namespace build -{ - namespace cxx - { - extern "C" bool - cxx_init (scope& r, - scope& b, - const location& loc, - std::unique_ptr<module>&, - bool first, - bool) - { - tracer trace ("cxx::init"); - level5 ([&]{trace << "for " << b.out_path ();}); - - // Initialize the bin module. Only do this if it hasn't already - // been loaded so that we don't overwrite user's bin.* settings. - // - { - auto l (b["bin.loaded"]); - - if (!l || !as<bool> (*l)) - load_module (false, "bin", r, b, loc); - } - - // Enter module variables. - // - // @@ Probably should only be done on load; make sure reset() unloads - // modules. - // - // @@ Should probably cache the variable pointers so we don't have - // to keep looking them up. - // - if (first) - { - auto& v (var_pool); - - v.find ("config.cxx", string_type); //@@ VAR type - - v.find ("config.cxx.poptions", strings_type); - v.find ("config.cxx.coptions", strings_type); - v.find ("config.cxx.loptions", strings_type); - v.find ("config.cxx.libs", strings_type); - - v.find ("cxx.poptions", strings_type); - v.find ("cxx.coptions", strings_type); - v.find ("cxx.loptions", strings_type); - v.find ("cxx.libs", strings_type); - - v.find ("cxx.export.poptions", strings_type); - v.find ("cxx.export.coptions", strings_type); - v.find ("cxx.export.loptions", strings_type); - v.find ("cxx.export.libs", strings_type); - - v.find ("cxx.std", string_type); - } - - // Register target types. - // - { - auto& t (b.target_types); - - t.insert<h> (); - t.insert<c> (); - - t.insert<cxx> (); - t.insert<hxx> (); - t.insert<ixx> (); - t.insert<txx> (); - } - - // Register rules. - // - { - using namespace bin; - - auto& r (b.rules); - - r.insert<obja> (perform_update_id, "cxx.compile", compile::instance); - - r.insert<obja> (perform_update_id, "cxx.compile", compile::instance); - r.insert<obja> (perform_clean_id, "cxx.compile", compile::instance); - - r.insert<objso> (perform_update_id, "cxx.compile", compile::instance); - r.insert<objso> (perform_clean_id, "cxx.compile", compile::instance); - - r.insert<exe> (perform_update_id, "cxx.link", link::instance); - r.insert<exe> (perform_clean_id, "cxx.link", link::instance); - - r.insert<liba> (perform_update_id, "cxx.link", link::instance); - r.insert<liba> (perform_clean_id, "cxx.link", link::instance); - - r.insert<libso> (perform_update_id, "cxx.link", link::instance); - r.insert<libso> (perform_clean_id, "cxx.link", link::instance); - - // Register for configure so that we detect unresolved imports - // during configuration rather that later, e.g., during update. - // - r.insert<obja> (configure_update_id, "cxx.compile", compile::instance); - r.insert<objso> (configure_update_id, "cxx.compile", compile::instance); - - r.insert<exe> (configure_update_id, "cxx.link", link::instance); - r.insert<liba> (configure_update_id, "cxx.link", link::instance); - r.insert<libso> (configure_update_id, "cxx.link", link::instance); - - //@@ Should we check if install module was loaded (see bin)? - // - r.insert<exe> (perform_install_id, "cxx.install", install::instance); - r.insert<liba> (perform_install_id, "cxx.install", install::instance); - r.insert<libso> (perform_install_id, "cxx.install", install::instance); - } - - // Configure. - // - - // config.cxx - // - if (first) - { - auto p (config::required (r, "config.cxx", "g++")); - - // If we actually set a new value, test it by trying to execute. - // - if (p.second) - { - const string& cxx (as<string> (p.first)); - const char* args[] = {cxx.c_str (), "-dumpversion", nullptr}; - - if (verb >= 2) - print_process (args); - else if (verb) - text << "test " << cxx; - - string ver; - try - { - process pr (args, 0, -1); // Open pipe to stdout. - ifdstream is (pr.in_ofd); - - bool r (getline (is, ver)); - - if (!r) - fail << "unexpected output from " << cxx; - - if (!pr.wait ()) - throw failed (); - } - catch (const process_error& e) - { - error << "unable to execute " << cxx << ": " << e.what (); - - if (e.child ()) - exit (1); - - throw failed (); - } - - if (verb >= 2) - text << cxx << " " << ver; - } - } - - // config.cxx.{p,c,l}options - // config.cxx.libs - // - // These are optional. We also merge them into the corresponding - // cxx.* variables. - // - // The merging part gets a bit tricky if this module has already - // been loaded in one of the outer scopes. By doing the straight - // append we would just be repeating the same options over and - // over. So what we are going to do is only append to a value if - // it came from this scope. Then the usage for merging becomes: - // - // cxx.coptions = <overridable options> # Note: '='. - // using cxx - // cxx.coptions += <overriding options> # Note: '+='. - // - if (const value& v = config::optional (r, "config.cxx.poptions")) - b.assign ("cxx.poptions") += as<strings> (v); - - if (const value& v = config::optional (r, "config.cxx.coptions")) - b.assign ("cxx.coptions") += as<strings> (v); - - if (const value& v = config::optional (r, "config.cxx.loptions")) - b.assign ("cxx.loptions") += as<strings> (v); - - if (const value& v = config::optional (r, "config.cxx.libs")) - b.assign ("cxx.libs") += as<strings> (v); - - // Configure "installability" of our target types. - // - { - using build::install::path; - - path<hxx> (b, dir_path ("include")); // Install into install.include. - path<ixx> (b, dir_path ("include")); - path<txx> (b, dir_path ("include")); - path<h> (b, dir_path ("include")); - } - - return true; - } - } -} diff --git a/build/cxx/target b/build/cxx/target deleted file mode 100644 index fe27c79..0000000 --- a/build/cxx/target +++ /dev/null @@ -1,78 +0,0 @@ -// file : build/cxx/target -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD_CXX_TARGET -#define BUILD_CXX_TARGET - -#include <build/target> - -namespace build -{ - 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 // BUILD_CXX_TARGET diff --git a/build/cxx/target.cxx b/build/cxx/target.cxx deleted file mode 100644 index 314f758..0000000 --- a/build/cxx/target.cxx +++ /dev/null @@ -1,81 +0,0 @@ -// file : build/cxx/target.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <build/cxx/target> - -using namespace std; - -namespace build -{ - namespace cxx - { - constexpr const char ext_var[] = "extension"; - - constexpr const char hxx_ext_def[] = "hxx"; - const target_type hxx::static_type - { - "hxx", - &file::static_type, - &target_factory<hxx>, - &target_extension_var<ext_var, hxx_ext_def>, - &search_file, - false - }; - - constexpr const char ixx_ext_def[] = "ixx"; - const target_type ixx::static_type - { - "ixx", - &file::static_type, - &target_factory<ixx>, - &target_extension_var<ext_var, ixx_ext_def>, - &search_file, - false - }; - - constexpr const char txx_ext_def[] = "txx"; - const target_type txx::static_type - { - "txx", - &file::static_type, - &target_factory<txx>, - &target_extension_var<ext_var, txx_ext_def>, - &search_file, - false - }; - - constexpr const char cxx_ext_def[] = "cxx"; - const target_type cxx::static_type - { - "cxx", - &file::static_type, - &target_factory<cxx>, - &target_extension_var<ext_var, cxx_ext_def>, - &search_file, - false - }; - - constexpr const char h_ext_def[] = "h"; - const target_type h::static_type - { - "h", - &file::static_type, - &target_factory<h>, - &target_extension_var<ext_var, h_ext_def>, - &search_file, - false - }; - - constexpr const char c_ext_def[] = "c"; - const target_type c::static_type - { - "c", - &file::static_type, - &target_factory<c>, - &target_extension_var<ext_var, c_ext_def>, - &search_file, - false - }; - } -} diff --git a/build/cxx/utility b/build/cxx/utility deleted file mode 100644 index 53e80e1..0000000 --- a/build/cxx/utility +++ /dev/null @@ -1,37 +0,0 @@ -// file : build/cxx/utility -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD_CXX_UTILITY -#define BUILD_CXX_UTILITY - -#include <string> - -#include <build/types> -#include <build/target> - -#include <build/config/utility> - -namespace build -{ - namespace cxx - { - using config::append_options; - - // T is either target or scope. - // - template <typename T> - void - append_std (cstrings& args, T&, std::string& storage); - - // Append library options from one of the cxx.export.* variables - // recursively, prerequisite libraries first. - // - void - append_lib_options (cstrings& args, target&, const char* variable); - } -} - -#include <build/cxx/utility.txx> - -#endif // BUILD_CXX_UTILITY diff --git a/build/cxx/utility.cxx b/build/cxx/utility.cxx deleted file mode 100644 index 830ee76..0000000 --- a/build/cxx/utility.cxx +++ /dev/null @@ -1,29 +0,0 @@ -// file : build/cxx/utility.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <build/cxx/utility> - -#include <build/bin/target> - -using namespace std; - -namespace build -{ - namespace cxx - { - void - append_lib_options (cstrings& args, target& l, const char* var) - { - using namespace bin; - - for (target* t: l.prerequisite_targets) - { - if (t->is_a<lib> () || t->is_a<liba> () || t->is_a<libso> ()) - append_lib_options (args, *t, var); - } - - append_options (args, l, var); - } - } -} diff --git a/build/cxx/utility.txx b/build/cxx/utility.txx deleted file mode 100644 index 561a54e..0000000 --- a/build/cxx/utility.txx +++ /dev/null @@ -1,35 +0,0 @@ -// file : build/cxx/utility.txx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -using namespace std; - -namespace build -{ - namespace cxx - { - template <typename T> - void - append_std (cstrings& args, T& t, std::string& s) - { - if (auto l = t["cxx.std"]) - { - const std::string& v (as<string> (*l)); - - // Translate 11 to 0x and 14 to 1y for compatibility with - // older versions of the compiler. - // - s = "-std=c++"; - - if (v == "11") - s += "0x"; - else if (v == "14") - s += "1y"; - else - s += v; - - args.push_back (s.c_str ()); - } - } - } -} diff --git a/build/diagnostics b/build/diagnostics deleted file mode 100644 index d5b0b3d..0000000 --- a/build/diagnostics +++ /dev/null @@ -1,402 +0,0 @@ -// file : build/diagnostics -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD_DIAGNOSTICS -#define BUILD_DIAGNOSTICS - -#include <cstddef> // size_t -#include <cstdint> -#include <utility> -#include <cassert> -#include <sstream> -#include <ostream> -#include <exception> -#include <type_traits> - -#include <butl/path> - -#include <build/types> -#include <build/path-io> - -namespace build -{ - struct diag_record; - - // Throw this exception to terminate the build. The handler should - // assume that the diagnostics has already been issued. - // - class failed: public std::exception {}; - - // Flag that indicates whether paths should be inserted relative - // into this stream. - // - extern const int relative_index; - - inline bool - relative (std::ostream& os) {return os.iword (relative_index);} - - inline void - relative (std::ostream& os, bool v) {os.iword (relative_index) = v ? 1 : 0;} - - // Print process commmand line. If the number of elements is specified - // (or the second version is used), then it will print the piped multi- - // process command line, if present. In this case, the expected format - // is as follows: - // - // name1 arg arg ... nullptr - // name2 arg arg ... nullptr - // ... - // nameN arg arg ... nullptr nullptr - // - void - print_process (diag_record&, const char* const* args, std::size_t n = 0); - - void - print_process (const char* const* args, std::size_t n = 0); - - inline void - print_process (diag_record& dr, const cstrings& args) - { - print_process (dr, args.data (), args.size ()); - } - - inline void - print_process (const cstrings& args) - { - print_process (args.data (), args.size ()); - } - - // Verbosity level. - // - // 0 - disabled - // 1 - high-level information messages - // 2 - essential underlying commands that are being executed - // 3 - all underlying commands that are being executed - // 4 - information helpful to the user (e.g., why a rule did not match) - // 5 - information helpful to the developer - // 6 - even more detailed information - // - // While uint8 is more than enough, use uint16 for the ease of printing. - // - extern std::uint16_t verb; - - template <typename F> inline void level1 (const F& f) {if (verb >= 1) f ();} - template <typename F> inline void level2 (const F& f) {if (verb >= 2) f ();} - template <typename F> inline void level3 (const F& f) {if (verb >= 3) f ();} - template <typename F> inline void level4 (const F& f) {if (verb >= 4) f ();} - template <typename F> inline void level5 (const F& f) {if (verb >= 5) f ();} - template <typename F> inline void level6 (const F& f) {if (verb >= 6) f ();} - - // Diagnostic facility, base infrastructure (potentially reusable). - // - extern std::ostream* diag_stream; - - template <typename> struct diag_prologue; - template <typename> struct diag_mark; - - typedef void (*diag_epilogue) (const diag_record&); - - struct diag_record - { - template <typename T> - friend const diag_record& - operator<< (const diag_record& r, const T& x) - { - r.os_ << x; - return r; - } - - diag_record (): empty_ (true), epilogue_ (nullptr) {} - - template <typename B> - explicit - diag_record (const diag_prologue<B>& p) - : empty_ (true), epilogue_ (nullptr) { *this << p;} - - template <typename B> - explicit - diag_record (const diag_mark<B>& m) - : empty_ (true), epilogue_ (nullptr) { *this << m;} - - ~diag_record () noexcept(false); - - void - append (diag_epilogue e) const - { - if (e != nullptr) - { - assert (epilogue_ == nullptr); // No multiple epilogues support. - epilogue_ = e; - } - - if (empty_) - empty_ = false; - else - os_ << "\n "; - } - - // Move constructible-only type. - // - /* - @@ libstdc++ doesn't yet have the ostringstream move support. - - diag_record (diag_record&& r) - : os_ (std::move (r.os_)) - { - empty_ = r.empty_; - r.empty_ = true; - - epilogue_ = r.epilogue_; - r.epilogue_ = nullptr; - } - */ - - diag_record (diag_record&& r) - { - empty_ = r.empty_; - epilogue_ = r.epilogue_; - - if (!empty_) - { - assert (false); //@@ Relative flag will not be transferred. - os_ << r.os_.str (); - - r.empty_ = true; - r.epilogue_ = nullptr; - } - } - - diag_record& operator= (diag_record&&) = delete; - - diag_record (const diag_record&) = delete; - diag_record& operator= (const diag_record&) = delete; - - public: - mutable std::ostringstream os_; - - private: - mutable bool empty_; - mutable diag_epilogue epilogue_; - }; - - template <typename B> - struct diag_prologue: B - { - diag_prologue (diag_epilogue e = nullptr): B (), epilogue_ (e) {} - - template <typename... A> - diag_prologue (A&&... a) - : B (std::forward<A> (a)...), epilogue_ (nullptr) {} - - template <typename... A> - diag_prologue (diag_epilogue e, A&&... a) - : B (std::forward<A> (a)...), epilogue_ (e) {} - - template <typename T> - diag_record - operator<< (const T& x) const - { - diag_record r; - r.append (epilogue_); - B::operator() (r); - r << x; - return r; - } - - friend const diag_record& - operator<< (const diag_record& r, const diag_prologue& p) - { - r.append (p.epilogue_); - p (r); - return r; - } - - private: - diag_epilogue epilogue_; - }; - - template <typename B> - struct diag_mark: B - { - diag_mark (): B () {} - - template <typename... A> - diag_mark (A&&... a): B (std::forward<A> (a)...) {} - - template <typename T> - diag_record - operator<< (const T& x) const - { - return B::operator() () << x; - } - - friend const diag_record& - operator<< (const diag_record& r, const diag_mark& m) - { - return r << m (); - } - }; - - // Diagnostic facility, project specifics. - // - struct simple_prologue_base - { - explicit - simple_prologue_base (const char* type, const char* name, bool rel) - : type_ (type), name_ (name), relative_ (rel) {} - - void - operator() (const diag_record& r) const; - - private: - const char* type_; - const char* name_; - const bool relative_; - }; - typedef diag_prologue<simple_prologue_base> simple_prologue; - - class location - { - public: - location () {} - location (const char* f, std::uint64_t l, std::uint64_t c) - : file (f), line (l), column (c) {} - - const char* file; - std::uint64_t line; - std::uint64_t column; - }; - - struct location_prologue_base - { - location_prologue_base (const char* type, - const char* name, - const location& l, - bool rel) - : type_ (type), name_ (name), loc_ (l), relative_ (rel) {} - - void - operator() (const diag_record& r) const; - - private: - const char* type_; - const char* name_; - const location loc_; - const bool relative_; - }; - typedef diag_prologue<location_prologue_base> location_prologue; - - // Here is the absolute/relative path rationale: we want it absolute - // in the error/warning/info streams to give the user the complete - // picture. But in the text stream (e.g., command lines), we print - // relative unless verbosity is greater than 1. - // - struct basic_mark_base - { - explicit - basic_mark_base (const char* type, - const char* name = nullptr, - const void* data = nullptr) - : type_ (type), name_ (name), data_ (data) {} - - simple_prologue - operator() () const - { - return simple_prologue (type_, name_, false); - } - - location_prologue - operator() (const location& l) const - { - return location_prologue (type_, name_, l, false); - } - - template <typename L> - location_prologue - operator() (const L& l) const - { - return location_prologue (type_, name_, get_location (l, data_), false); - } - - protected: - const char* type_; - const char* name_; - const void* data_; - }; - typedef diag_mark<basic_mark_base> basic_mark; - - extern const basic_mark error; - extern const basic_mark warn; - extern const basic_mark info; - - // text - // - struct text_mark_base: basic_mark_base - { - text_mark_base (): basic_mark_base (nullptr) {} - - simple_prologue - operator() () const - { - return simple_prologue (type_, name_, verb <= 1); - } - }; - typedef diag_mark<text_mark_base> text_mark; - - extern const text_mark text; - - // trace - // - struct trace_mark_base: basic_mark_base - { - explicit - trace_mark_base (const char* name, const void* data = nullptr) - : basic_mark_base ("trace", name, data) {} - }; - typedef diag_mark<trace_mark_base> trace_mark; - - typedef trace_mark tracer; - - // fail - // - template <typename E> - struct fail_mark_base - { - explicit - fail_mark_base (const void* data = nullptr): data_ (data) {} - - simple_prologue - operator() () const - { - return simple_prologue (&epilogue, "error", nullptr, false); - } - - location_prologue - operator() (const location& l) const - { - return location_prologue (&epilogue, "error", nullptr, l, false); - } - - template <typename L> - location_prologue - operator() (const L& l) const - { - return location_prologue ( - &epilogue, "error", nullptr, get_location (l, data_), false); - } - - static void - epilogue (const diag_record&) {throw E ();} - - private: - const void* data_; - }; - - template <typename E> - using fail_mark = diag_mark<fail_mark_base<E>>; - - extern const fail_mark<failed> fail; -} - -#endif // BUILD_DIAGNOSTICS diff --git a/build/diagnostics.cxx b/build/diagnostics.cxx deleted file mode 100644 index a450301..0000000 --- a/build/diagnostics.cxx +++ /dev/null @@ -1,125 +0,0 @@ -// file : build/diagnostics.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <build/diagnostics> - -#include <cstring> // strchr() -#include <iostream> - -#include <build/utility> - -using namespace std; - -namespace build -{ - // Relative stream. - // - const int relative_index = ostream::xalloc (); - - void - print_process (const char* const* args, size_t n) - { - diag_record r (text); - print_process (r, args, n); - } - - void - print_process (diag_record& r, const char* const* args, size_t n) - { - size_t m (0); - const char* const* p (args); - do - { - if (m != 0) - r << " |"; // Trailing space will be added inside the loop. - - for (m++; *p != nullptr; p++, m++) - { - if (p != args) - r << ' '; - - // Quote if empty or contains spaces. - // - bool q (**p == '\0' || strchr (*p, ' ') != nullptr); - - if (q) - r << '"'; - - r << *p; - - if (q) - r << '"'; - } - - if (m < n) // Can we examine the next element? - { - p++; - m++; - } - - } while (*p != nullptr); - } - - // Diagnostics verbosity level. - // - uint16_t verb; - - // Diagnostic facility, base infrastructure. - // - ostream* diag_stream = &cerr; - - diag_record:: - ~diag_record () noexcept(false) - { - // Don't flush the record if this destructor was called as part of - // the stack unwinding. Right now this means we cannot use this - // mechanism in destructors, which is not a big deal, except for - // one place: exception_guard. So for now we are going to have - // this ugly special check which we will be able to get rid of - // once C++17 uncaught_exceptions() becomes available. - // - if (!empty_ && (!std::uncaught_exception () || exception_unwinding_dtor)) - { - *diag_stream << os_.str () << std::endl; - - if (epilogue_ != nullptr) - epilogue_ (*this); // Can throw. - } - } - - // Diagnostic facility, project specifics. - // - - void simple_prologue_base:: - operator() (const diag_record& r) const - { - relative (r.os_, relative_); - - if (type_ != nullptr) - r << type_ << ": "; - - if (name_ != nullptr) - r << name_ << ": "; - } - - void location_prologue_base:: - operator() (const diag_record& r) const - { - relative (r.os_, relative_); - - r << loc_.file << ':' << loc_.line << ':' << loc_.column << ": "; - - if (type_ != nullptr) - r << type_ << ": "; - - if (name_ != nullptr) - r << name_ << ": "; - } - - const basic_mark error ("error"); - const basic_mark warn ("warning"); - const basic_mark info ("info"); - const text_mark text; - const fail_mark<failed> fail; -} diff --git a/build/dist/module b/build/dist/module deleted file mode 100644 index f2e024a..0000000 --- a/build/dist/module +++ /dev/null @@ -1,26 +0,0 @@ -// file : build/dist/module -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD_DIST_MODULE -#define BUILD_DIST_MODULE - -#include <build/types> -#include <build/utility> - -#include <build/module> - -namespace build -{ - namespace dist - { - extern "C" void - dist_boot (scope&, const location&, unique_ptr<module>&); - - extern "C" bool - dist_init ( - scope&, scope&, const location&, unique_ptr<module>&, bool, bool); - } -} - -#endif // BUILD_DIST_MODULE diff --git a/build/dist/module.cxx b/build/dist/module.cxx deleted file mode 100644 index f39ef18..0000000 --- a/build/dist/module.cxx +++ /dev/null @@ -1,142 +0,0 @@ -// file : build/dist/module.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <build/dist/module> - -#include <build/scope> -#include <build/file> -#include <build/diagnostics> - -#include <build/config/utility> - -#include <build/dist/rule> -#include <build/dist/operation> - -using namespace std; -using namespace butl; - -namespace build -{ - namespace dist - { - static rule rule_; - - extern "C" void - dist_boot (scope& r, const location&, unique_ptr<module>&) - { - tracer trace ("dist::boot"); - - level5 ([&]{trace << "for " << r.out_path ();}); - - // Register meta-operation. - // - r.meta_operations.insert (dist_id, dist); - } - - extern "C" bool - dist_init (scope& r, - scope&, - const location& l, - unique_ptr<module>&, - bool first, - bool) - { - tracer trace ("dist::init"); - - if (!first) - { - warn (l) << "multiple dist module initializations"; - return true; - } - - const dir_path& out_root (r.out_path ()); - level5 ([&]{trace << "for " << out_root;}); - - // Enter module variables. - // - if (first) - { - auto& v (var_pool); - - v.find ("dist", bool_type); - - v.find ("dist.package", string_type); - - v.find ("dist.root", dir_path_type); - v.find ("config.dist.root", dir_path_type); - - //@@ VAR type - // - v.find ("dist.cmd", string_type); - v.find ("config.dist.cmd", string_type); - - v.find ("dist.archives", strings_type); - v.find ("config.dist.archives", strings_type); - } - - // Register our wildcard rule. Do it explicitly for the alias - // to prevent something like insert<target>(dist_id, test_id) - // taking precedence. - // - r.rules.insert<target> (dist_id, 0, "dist", rule_); - r.rules.insert<alias> (dist_id, 0, "dist.alias", rule_); - - // Configuration. - // - // Note that we don't use any defaults for root -- the location - // must be explicitly specified or we will complain if and when - // we try to dist. - // - using namespace config; - - bool s (specified (r, "config.dist")); - - // dist.root - // - { - value& v (r.assign ("dist.root")); - - if (s) - { - const value& cv (optional_absolute (r, "config.dist.root")); - - if (cv && !cv.empty ()) - v = cv; - } - } - - // dist.cmd - // - { - value& v (r.assign ("dist.cmd")); - - if (s) - { - const value& cv (required (r, "config.dist.cmd", "install").first); - - if (cv && !cv.empty ()) - v = cv; - } - else - v = "install"; - } - - // dist.archives - // - { - value& v (r.assign ("dist.archives")); - - if (s) - { - const value& cv (optional (r, "config.dist.archives")); - - if (cv && !cv.empty ()) - v = cv; - } - } - - return true; - } - } -} diff --git a/build/dist/operation b/build/dist/operation deleted file mode 100644 index c7aa014..0000000 --- a/build/dist/operation +++ /dev/null @@ -1,18 +0,0 @@ -// file : build/dist/operation -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD_DIST_OPERATION -#define BUILD_DIST_OPERATION - -#include <build/operation> - -namespace build -{ - namespace dist - { - extern meta_operation_info dist; - } -} - -#endif // BUILD_DIST_OPERATION diff --git a/build/dist/operation.cxx b/build/dist/operation.cxx deleted file mode 100644 index 28a64fa..0000000 --- a/build/dist/operation.cxx +++ /dev/null @@ -1,459 +0,0 @@ -// file : build/dist/operation.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <build/dist/operation> - -#include <cassert> - -#include <butl/process> -#include <butl/filesystem> - -#include <build/file> -#include <build/dump> -#include <build/scope> -#include <build/target> -#include <build/context> -#include <build/algorithm> -#include <build/diagnostics> - -using namespace std; -using namespace butl; - -namespace build -{ - namespace dist - { - static void - dist_meta_operation_pre () - { - // Reset the dependency state so that we don't end up with stray - // files from previous batches. - // - // @@ This is called too late, after we have bootstrapped the - // project. - // - //reset (); - } - - static operation_id - dist_operation_pre (operation_id o) - { - if (o != default_id) - fail << "explicit operation specified for dist meta-operation"; - - return o; - } - - static void - dist_match (action, action_targets&) - { - // Don't match anything -- see execute (). - } - - // install -d <dir> - // - static void - install (const string& cmd, const dir_path&); - - // install <file> <dir> - // - static void - install (const string& cmd, file&, const dir_path&); - - // cd <root> && tar|zip ... <pkg>.<ext> <pkg> - // - static void - archive (const dir_path& root, const string& pkg, const string& ext); - - static void - dist_execute (action, const action_targets& ts, bool) - { - tracer trace ("dist_execute"); - - // For now we assume all the targets are from the same project. - // - target& t (*static_cast<target*> (ts[0])); - scope* rs (t.base_scope ().root_scope ()); - - if (rs == nullptr) - fail << "out of project target " << t; - - const dir_path& out_root (rs->out_path ()); - const dir_path& src_root (rs->src_path ()); - - if (out_root == src_root) - fail << "in-tree distribution of target " << t << - info << "distribution requires out-of-tree build"; - - // Make sure we have the necessary configuration before - // we get down to business. - // - auto l (rs->vars["dist.root"]); - - if (!l || l->empty ()) - fail << "unknown root distribution directory" << - info << "did you forget to specify config.dist.root?"; - - const dir_path& dist_root (as<dir_path> (*l)); - - if (!dir_exists (dist_root)) - fail << "root distribution directory " << dist_root - << " does not exist"; - - l = rs->vars["dist.package"]; - - if (!l || l->empty ()) - fail << "unknown distribution package name" << - info << "did you forget to set dist.package?"; - - const string& dist_package (as<string> (*l)); - const string& dist_cmd (as<string> (*rs->vars["dist.cmd"])); - - // Get the list of operations supported by this project. Skip - // default_id. - // - for (operations::size_type id (default_id + 1); - id < rs->operations.size (); - ++id) - { - const operation_info* oi (rs->operations[id]); - if (oi == nullptr) - continue; - - // Note that we are not calling operation_pre/post() callbacks - // here since the meta operation is dist and we know what we - // are doing. - // - current_inner_oif = oi; - current_outer_oif = nullptr; - current_mode = oi->mode; - dependency_count = 0; - - action a (dist_id, id); - - if (verb >= 6) - dump (a); - - for (void* v: ts) - { - target& t (*static_cast<target*> (v)); - - if (rs != t.base_scope ().root_scope ()) - fail << "out of project target " << t; - - level5 ([&]{trace << diag_doing (a, t);}); - - match (a, t); - } - - if (verb >= 6) - dump (a); - } - - // Add buildfiles that are not normally loaded as part of the - // project, for example, the export stub. They will still be - // ignored on the next step if the user explicitly marked them - // nodist. - // - auto add_adhoc = [&src_root, &trace] (const dir_path& d, const char* f) - { - path p (d / path (f)); - if (file_exists (p)) - { - const char* e (p.extension ()); - targets.insert<buildfile> ( - p.directory (), - p.leaf ().base ().string (), - &extension_pool.find (e == nullptr ? "" : e), // Specified. - trace); - } - }; - - add_adhoc (src_root, "build/export.build"); - - // The same for subprojects that have been loaded. - // - if (auto l = rs->vars["subprojects"]) - { - for (auto p: as<subprojects> (*l)) - { - const dir_path& pd (p.second); - dir_path out_nroot (out_root / pd); - scope& nrs (scopes.find (out_nroot)); - - if (nrs.out_path () != out_nroot) // This subproject not loaded. - continue; - - const dir_path& src_nroot (nrs.src_path ()); - - if (!src_nroot.sub (src_root)) // Not a strong amalgamation. - continue; - - add_adhoc (src_nroot, "build/export.build"); - } - } - - // Collect the files. We want to take the snapshot of targets - // since updating some of them may result in more targets being - // entered. - // - action_targets files; - const variable& dist_var (var_pool.find ("dist")); - - for (const auto& pt: targets) - { - file* ft (pt->is_a<file> ()); - - if (ft == nullptr) // Not a file. - continue; - - if (ft->dir.sub (src_root)) - { - // Include unless explicitly excluded. - // - auto l ((*ft)[dist_var]); - - if (l && !as<bool> (*l)) - level5 ([&]{trace << "excluding " << *ft;}); - else - files.push_back (ft); - - continue; - } - - if (ft->dir.sub (out_root)) - { - // Exclude unless explicitly included. - // - auto l ((*ft)[dist_var]); - - if (l && as<bool> (*l)) - { - level5 ([&]{trace << "including " << *ft;}); - files.push_back (ft); - } - - continue; - } - } - - // Make sure what we need to distribute is up to date. - // - { - if (perform.meta_operation_pre != nullptr) - perform.meta_operation_pre (); - - current_mif = &perform; - - if (perform.operation_pre != nullptr) - perform.operation_pre (update_id); - - current_inner_oif = &update; - current_outer_oif = nullptr; - current_mode = update.mode; - dependency_count = 0; - - action a (perform_id, update_id); - - perform.match (a, files); - perform.execute (a, files, true); // Run quiet. - - if (perform.operation_post != nullptr) - perform.operation_post (update_id); - - if (perform.meta_operation_post != nullptr) - perform.meta_operation_post (); - } - - dir_path td (dist_root / dir_path (dist_package)); - - // Clean up the target directory. - // - // @@ Not for incremental dist? - // - if (build::rmdir_r (td) == rmdir_status::not_empty) - fail << "unable to clean target directory " << td; - - install (dist_cmd, td); - - // Copy over all the files. - // - for (void* v: files) - { - file& t (*static_cast<file*> (v)); - - // Figure out where this file is inside the target directory. - // - dir_path d (td); - d /= t.dir.sub (src_root) - ? t.dir.leaf (src_root) - : t.dir.leaf (out_root); - - if (!dir_exists (d)) - install (dist_cmd, d); - - install (dist_cmd, t, d); - } - - // Archive if requested. - // - if (auto l = rs->vars["dist.archives"]) - { - for (const string& e: as<strings> (*l)) - archive (dist_root, dist_package, e); - } - } - - // install -d <dir> - // - static void - install (const string& cmd, const dir_path& d) - { - path reld (relative (d)); - - cstrings args {cmd.c_str (), "-d"}; - - args.push_back ("-m"); - args.push_back ("755"); - args.push_back (reld.string ().c_str ()); - args.push_back (nullptr); - - if (verb >= 2) - print_process (args); - else if (verb) - text << "dist -d " << d; - - try - { - process pr (args.data ()); - - if (!pr.wait ()) - throw failed (); - } - catch (const process_error& e) - { - error << "unable to execute " << args[0] << ": " << e.what (); - - if (e.child ()) - exit (1); - - throw failed (); - } - } - - // install <file> <dir> - // - static void - install (const string& cmd, file& t, const dir_path& d) - { - path reld (relative (d)); - path relf (relative (t.path ())); - - cstrings args {cmd.c_str ()}; - - // Preserve timestamps. This could becomes important if, for - // example, we have pre-generated sources. Note that the - // install-sh script doesn't support this option, while both - // Linux and BSD install's do. - // - args.push_back ("-p"); - - // Assume the file is executable if the owner has execute - // permission, in which case we make it executable for - // everyone. - // - args.push_back ("-m"); - args.push_back ( - (path_permissions (t.path ()) & permissions::xu) == permissions::xu - ? "755" - : "644"); - - args.push_back (relf.string ().c_str ()); - args.push_back (reld.string ().c_str ()); - args.push_back (nullptr); - - if (verb >= 2) - print_process (args); - else if (verb) - text << "dist " << t; - - try - { - process pr (args.data ()); - - if (!pr.wait ()) - throw failed (); - } - catch (const process_error& e) - { - error << "unable to execute " << args[0] << ": " << e.what (); - - if (e.child ()) - exit (1); - - throw failed (); - } - } - - static void - archive (const dir_path& root, const string& pkg, const string& e) - { - string a (pkg + '.' + e); - - // Delete old archive for good measure. - // - path ap (root / path (a)); - if (file_exists (ap)) - rmfile (ap); - - // Use zip for .zip archives. Everything else goes to tar in the - // auto-compress mode (-a). - // - cstrings args; - if (e == "zip") - args = {"zip", "-rq", a.c_str (), pkg.c_str (), nullptr}; - else - args = {"tar", "-a", "-cf", a.c_str (), pkg.c_str (), nullptr}; - - if (verb >= 2) - print_process (args); - else if (verb) - text << args[0] << " " << ap; - - try - { - // Change child's working directory to dist_root. - // - process pr (root.string ().c_str (), args.data ()); - - if (!pr.wait ()) - throw failed (); - } - catch (const process_error& e) - { - error << "unable to execute " << args[0] << ": " << e.what (); - - if (e.child ()) - exit (1); - - throw failed (); - } - } - - meta_operation_info dist { - "dist", - "distribute", - "distributing", - "has nothing to distribute", // We cannot "be distributed". - &dist_meta_operation_pre, - &dist_operation_pre, - &load, // normal load - &search, // normal search - &dist_match, - &dist_execute, - nullptr, // operation post - nullptr // meta-operation post - }; - } -} diff --git a/build/dist/rule b/build/dist/rule deleted file mode 100644 index ffb2227..0000000 --- a/build/dist/rule +++ /dev/null @@ -1,29 +0,0 @@ -// file : build/dist/rule -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD_DIST_RULE -#define BUILD_DIST_RULE - -#include <build/rule> -#include <build/types> -#include <build/target> -#include <build/operation> - -namespace build -{ - namespace dist - { - class rule: public build::rule - { - public: - virtual match_result - match (action, target&, const std::string&) const; - - virtual recipe - apply (action, target&, const match_result&) const; - }; - } -} - -#endif // BUILD_DIST_RULE diff --git a/build/dist/rule.cxx b/build/dist/rule.cxx deleted file mode 100644 index 80e7b05..0000000 --- a/build/dist/rule.cxx +++ /dev/null @@ -1,55 +0,0 @@ -// file : build/dist/rule.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <build/dist/rule> - -#include <build/scope> -#include <build/target> -#include <build/algorithm> -#include <build/diagnostics> - -using namespace std; - -namespace build -{ - 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)) - build::match (a, pt); - } - - return noop_recipe; // We will never be executed. - } - } -} diff --git a/build/dump b/build/dump deleted file mode 100644 index 8bc548e..0000000 --- a/build/dump +++ /dev/null @@ -1,18 +0,0 @@ -// file : build/dump -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD_DUMP -#define BUILD_DUMP - -#include <build/operation> - -namespace build -{ - // Dump the state pertaining to the specified action. - // - void - dump (action); -} - -#endif // BUILD_DUMP diff --git a/build/dump.cxx b/build/dump.cxx deleted file mode 100644 index 62e032b..0000000 --- a/build/dump.cxx +++ /dev/null @@ -1,253 +0,0 @@ -// file : build/dump.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <build/dump> - -#include <string> -#include <cassert> - -#include <build/scope> -#include <build/target> -#include <build/variable> -#include <build/context> -#include <build/diagnostics> - -using namespace std; - -namespace build -{ - 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/build/file b/build/file deleted file mode 100644 index c2b2535..0000000 --- a/build/file +++ /dev/null @@ -1,144 +0,0 @@ -// file : build/file -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD_FILE -#define BUILD_FILE - -#include <map> -#include <string> - -#include <build/types> -#include <build/scope> -#include <build/variable> // list_value - -namespace build -{ - class target; - class location; - class prerequisite_key; - - using subprojects = std::map<std::string, dir_path>; - - extern const dir_path build_dir; // build - extern const dir_path bootstrap_dir; // build/bootstrap - - extern const path root_file; // build/root.build - extern const path bootstrap_file; // build/bootstrap.build - extern const path src_root_file; // build/bootstrap/src-root.build - - bool - is_src_root (const dir_path&); - - bool - is_out_root (const dir_path&); - - // Given an src_base directory, look for a project's src_root - // based on the presence of known special files. Return empty - // path if not found. - // - dir_path - find_src_root (const dir_path&); - - // The same as above but for project's out. Note that we also - // check whether a directory happens to be src_root, in case - // this is an in-tree build. The second argument is the out - // flag that is set to true if this is src_root. - // - dir_path - find_out_root (const dir_path&, bool* src = nullptr); - - void - source (const path& buildfile, scope& root, scope& base); - - // As above but first check if this buildfile has already been - // sourced for the base scope. - // - void - source_once (const path& buildfile, scope& root, scope& base); - - // As above but checks against the specified scope rather than base. - // - void - source_once (const path& buildfile, scope& root, scope& base, scope& once); - - // Create project's root scope. Only set the src_root variable if the - // passed src_root value is not empty. - // - scope& - create_root (const dir_path& out_root, const dir_path& src_root); - - // Setup root scope. Note that it assume the src_root variable - // has already been set. - // - void - setup_root (scope&); - - // Setup the base scope (set *_base variables, etc). - // - scope& - setup_base (scope_map::iterator, - const dir_path& out_base, - const dir_path& src_base); - - // Bootstrap the project's root scope, the out part. - // - void - bootstrap_out (scope& root); - - // Bootstrap the project's root scope, the src part. Return true if - // we loaded anything (which confirms the src_root is not bogus). - // - bool - bootstrap_src (scope& root); - - // Create and bootstrap outer root scopes, if any. Loading is - // done by load_root_pre() below. - // - void - create_bootstrap_outer (scope& root); - - // Create and bootstrap inner root scopes between root and base, - // if any. Return the innermost created root scope or root if - // none were created. Loading is done by load_root_pre() below. - // - scope& - create_bootstrap_inner (scope& root, const dir_path& out_base); - - // Load project's root[-pre].build unless already loaded. Also - // make sure all outer root scopes are loaded prior to loading - // this root scope. - // - void - load_root_pre (scope& root); - - // Import has two phases: the first is triggered by the import - // directive in the buildfile. It will try to find and load the - // project. Failed that, it will return the project-qualified - // name of the target which will be used to create a project- - // qualified prerequisite. This gives the rule that will be - // searching this prerequisite a chance to do some target-type - // specific search. For example, a C++ link rule can search - // for lib{} prerequisites in the C++ compiler default library - // search paths (so that we end up with functionality identical - // to -lfoo). If, however, the rule didn't do any of that (or - // failed to find anything usable), it calls the standard - // prerequisite search() function which sees this is a project- - // qualified prerequisite and goes straight to the second phase - // of import. Here, currently, we simply fail but in the future - // this will be the place where we can call custom "last resort" - // import hooks. For example, we can hook a package manager that - // will say, "Hey, I see you are trying to import foo and I see - // there is a package foo available in repository bar. Wanna - // download and use it?" - // - names - import (scope& base, name, const location&); - - target& - import (const prerequisite_key&); -} - -#include <build/file.ixx> - -#endif // BUILD_FILE diff --git a/build/file.cxx b/build/file.cxx deleted file mode 100644 index b4b1b77..0000000 --- a/build/file.cxx +++ /dev/null @@ -1,980 +0,0 @@ -// file : build/file.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <build/file> - -#include <fstream> -#include <utility> // move() -#include <system_error> - -#include <butl/filesystem> - -#include <build/scope> -#include <build/context> -#include <build/parser> -#include <build/prerequisite> -#include <build/diagnostics> - -#include <build/token> -#include <build/lexer> - -using namespace std; -using namespace butl; - -namespace build -{ - const dir_path build_dir ("build"); - const dir_path bootstrap_dir ("build/bootstrap"); - - const path root_file ("build/root.build"); - const path bootstrap_file ("build/bootstrap.build"); - const path src_root_file ("build/bootstrap/src-root.build"); - - bool - is_src_root (const dir_path& d) - { - // @@ Can we have root without bootstrap? I don't think so. - // - return file_exists (d / bootstrap_file) || file_exists (d / root_file); - } - - bool - is_out_root (const dir_path& d) - { - return file_exists (d / src_root_file); - } - - dir_path - find_src_root (const dir_path& b) - { - for (dir_path d (b); !d.root () && d != home; d = d.directory ()) - { - if (is_src_root (d)) - return d; - } - - return dir_path (); - } - - dir_path - find_out_root (const dir_path& b, bool* src) - { - for (dir_path d (b); !d.root () && d != home; d = d.directory ()) - { - bool s (false); - if ((s = is_src_root (d)) || is_out_root (d)) // Order is important! - { - if (src != nullptr) - *src = s; - - return d; - } - } - - return dir_path (); - } - - static void - source (const path& bf, scope& root, scope& base, bool boot) - { - tracer trace ("source"); - - try - { - ifstream ifs (bf.string ()); - if (!ifs.is_open ()) - fail << "unable to open " << bf; - - ifs.exceptions (ifstream::failbit | ifstream::badbit); - - level5 ([&]{trace << "sourcing " << bf;}); - - parser p (boot); - p.parse_buildfile (ifs, bf, root, base); - } - catch (const ifstream::failure&) - { - fail << "unable to read buildfile " << bf; - } - } - - void - source (const path& bf, scope& root, scope& base) - { - return source (bf, root, base, false); - } - - void - source_once (const path& bf, scope& root, scope& base, scope& once) - { - tracer trace ("source_once"); - - if (!once.buildfiles.insert (bf).second) - { - level5 ([&]{trace << "skipping already sourced " << bf;}); - return; - } - - source (bf, root, base); - } - - scope& - create_root (const dir_path& out_root, const dir_path& src_root) - { - auto i (scopes.insert (out_root, nullptr, true, true)); - scope& rs (*i->second); - - // Set out_path. src_path is set in setup_root() below. - // - if (rs.out_path_ != &i->first) - { - assert (rs.out_path_ == nullptr); - rs.out_path_ = &i->first; - } - - // Enter built-in meta-operation and operation names. Loading of - // modules (via the src bootstrap; see below) can result in - // additional meta/operations being added. - // - if (rs.meta_operations.empty ()) - { - rs.meta_operations.insert (perform_id, perform); - - rs.operations.insert (default_id, default_); - rs.operations.insert (update_id, update); - rs.operations.insert (clean_id, clean); - } - - // If this is already a root scope, verify that things are - // consistent. - // - { - value& v (rs.assign ("out_root")); - - if (!v) - v = out_root; - else - { - const dir_path& p (as<dir_path> (v)); - - if (p != out_root) - fail << "new out_root " << out_root << " does not match " - << "existing " << p; - } - } - - if (!src_root.empty ()) - { - value& v (rs.assign ("src_root")); - - if (!v) - v = src_root; - else - { - const dir_path& p (as<dir_path> (v)); - - if (p != src_root) - fail << "new src_root " << src_root << " does not match " - << "existing " << p; - } - } - - return rs; - } - - void - setup_root (scope& s) - { - value& v (s.assign ("src_root")); - assert (v); - - // Register and set src_path. - // - if (s.src_path_ == nullptr) - s.src_path_ = &scopes.insert (as<dir_path> (v), &s, false, true)->first; - } - - scope& - setup_base (scope_map::iterator i, - const dir_path& out_base, - const dir_path& src_base) - { - scope& s (*i->second); - - // Set src/out_path. The key (i->first) can be either out_base - // or src_base. - // - if (s.out_path_ == nullptr) - { - s.out_path_ = - i->first == out_base - ? &i->first - : &scopes.insert (out_base, &s, true, false)->first; - } - - if (s.src_path_ == nullptr) - { - s.src_path_ = - i->first == src_base - ? &i->first - : &scopes.insert (src_base, &s, false, false)->first; - } - - // Set src/out_base variables. - // - { - value& v (s.assign ("out_base")); - - if (!v) - v = out_base; - else - assert (as<dir_path> (v) == out_base); - } - - { - value& v (s.assign ("src_base")); - - if (!v) - v = src_base; - else - assert (as<dir_path> (v) == src_base); - } - - return s; - } - - void - bootstrap_out (scope& root) - { - path bf (root.out_path () / path ("build/bootstrap/src-root.build")); - - if (!file_exists (bf)) - return; - - //@@ TODO: if bootstrap files can source other bootstrap files - // (the way to express dependecies), then we need a way to - // prevent multiple sourcing. We handle it here but we still - // need something like source_once (once [scope] source). - // - source_once (bf, root, root); - } - - // Extract the specified variable value from a buildfile. It is - // expected to be the first non-comment line and not to rely on - // any variable expansion other than those from the global scope. - // - static value - extract_variable (const path& bf, const char* var) - { - try - { - ifstream ifs (bf.string ()); - if (!ifs.is_open ()) - fail << "unable to open " << bf; - - ifs.exceptions (ifstream::failbit | ifstream::badbit); - - path rbf (diag_relative (bf)); - - lexer lex (ifs, rbf.string ()); - token t (lex.next ()); - token_type tt; - - if (t.type != token_type::name || t.value != var || - ((tt = lex.next ().type) != token_type::equal && - tt != token_type::equal_plus && - tt != token_type::plus_equal)) - { - error << "variable '" << var << "' expected as first line in " << rbf; - throw failed (); // Suppress "used uninitialized" warning. - } - - parser p; - temp_scope tmp (*global_scope); - p.parse_variable (lex, tmp, t.value, tt); - - auto l (tmp.vars[var]); - assert (l.defined ()); - value& v (*l); - return move (v); // Steal the value, the scope is going away. - } - catch (const ifstream::failure&) - { - fail << "unable to read buildfile " << bf; - } - - return value (); // Never reaches. - } - - // Extract the project name from bootstrap.build. - // - static string - find_project_name (const dir_path& out_root, - const dir_path& fallback_src_root, - bool* src_hint = nullptr) - { - tracer trace ("find_project_name"); - - // Load the project name. If this subdirectory is the subproject's - // src_root, then we can get directly to that. Otherwise, we first - // have to discover its src_root. - // - const dir_path* src_root; - value src_root_v; // Need it to live until the end. - - if (src_hint != nullptr ? *src_hint : is_src_root (out_root)) - src_root = &out_root; - else - { - path f (out_root / src_root_file); - - if (!fallback_src_root.empty () && !file_exists (f)) - src_root = &fallback_src_root; - else - { - src_root_v = extract_variable (f, "src_root"); - src_root = &as<dir_path> (src_root_v); - level5 ([&]{trace << "extracted src_root " << *src_root << " for " - << out_root;}); - } - } - - string name; - { - value v (extract_variable (*src_root / bootstrap_file, "project")); - name = move (as<string> (v)); - } - - level5 ([&]{trace << "extracted project name '" << name << "' for " - << *src_root;}); - return name; - } - - // Scan the specified directory for any subprojects. If a subdirectory - // is a subproject, then enter it into the map, handling the duplicates. - // Otherwise, scan the subdirectory recursively. - // - static void - find_subprojects (subprojects& sps, - const dir_path& d, - const dir_path& root, - bool out) - { - tracer trace ("find_subprojects"); - - for (const dir_entry& de: dir_iterator (d)) - { - // If this is a link, then type() will try to stat() it. And if - // the link is dangling or points to something inaccessible, it - // will fail. - // - try - { - if (de.type () != entry_type::directory) - continue; - } - catch (const system_error& e) - { - continue; - } - - dir_path sd (d / path_cast<dir_path> (de.path ())); - - bool src (false); - if (!((out && is_out_root (sd)) || (src = is_src_root (sd)))) - { - find_subprojects (sps, sd, root, out); - continue; - } - - // Calculate relative subdirectory for this subproject. - // - dir_path dir (sd.leaf (root)); - level5 ([&]{trace << "subproject " << sd << " as " << dir;}); - - // Load its name. Note that here we don't use fallback src_root - // since this function is used to scan both out_root and src_root. - // - string name (find_project_name (sd, dir_path (), &src)); - - // If the name is empty, then is is an unnamed project. While the - // 'project' variable stays empty, here we come up with a surrogate - // name for a key. The idea is that such a key should never conflict - // with a real project name. We ensure this by using the project's - // sub-directory and appending trailing '/' to it. - // - if (name.empty ()) - name = dir.posix_string () + '/'; - - // @@ Can't use move() because we may need the values in diagnostics - // below. Looks like C++17 try_emplace() is what we need. - // - auto rp (sps.emplace (name, dir)); - - // Handle duplicates. - // - if (!rp.second) - { - const dir_path& dir1 (rp.first->second); - - if (dir != dir1) - fail << "inconsistent subproject directories for " << name << - info << "first alternative: " << dir1 << - info << "second alternative: " << dir; - - level6 ([&]{trace << "skipping duplicate";}); - } - } - } - - bool - bootstrap_src (scope& root) - { - tracer trace ("bootstrap_src"); - - bool r (false); - - const dir_path& out_root (root.out_path ()); - const dir_path& src_root (root.src_path ()); - - path bf (src_root / path ("build/bootstrap.build")); - - if (file_exists (bf)) - { - // We assume that bootstrap out cannot load this file explicitly. It - // feels wrong to allow this since that makes the whole bootstrap - // process hard to reason about. But we may try to bootstrap the - // same root scope multiple time. - // - if (root.buildfiles.insert (bf).second) - source (bf, root, root, true); - else - level5 ([&]{trace << "skipping already sourced " << bf;}); - - r = true; - } - - // See if we are a part of an amalgamation. There are two key - // players: the outer root scope which may already be present - // (i.e., we were loaded as part of an amalgamation) and the - // amalgamation variable that may or may not be set by the - // user (in bootstrap.build) or by an earlier call to this - // function for the same scope. When set by the user, the - // empty special value means that the project shall not be - // amalgamated (and which we convert to NULL below). When - // calculated, the NULL value indicates that we are not - // amalgamated. - // - { - auto rp (root.vars.assign ("amalgamation")); // Set NULL by default. - value& v (rp.first); - - if (v && v.empty ()) // Convert empty to NULL. - v = nullptr; - - if (scope* aroot = root.parent_scope ()->root_scope ()) - { - const dir_path& ad (aroot->out_path ()); - dir_path rd (ad.relative (out_root)); - - // If we already have the amalgamation variable set, verify - // that aroot matches its value. - // - if (!rp.second) - { - if (!v) - { - fail << out_root << " cannot be amalgamated" << - info << "amalgamated by " << ad; - } - else - { - const dir_path& vd (as<dir_path> (v)); - - if (vd != rd) - { - fail << "inconsistent amalgamation of " << out_root << - info << "specified: " << vd << - info << "actual: " << rd << " by " << ad; - } - } - } - else - { - // Otherwise, use the outer root as our amalgamation. - // - level5 ([&]{trace << out_root << " amalgamated as " << rd;}); - v = move (rd); - } - } - else if (rp.second) - { - // If there is no outer root and the amalgamation variable - // hasn't been set, then we need to check if any of the - // outer directories is a project's out_root. If so, then - // that's our amalgamation. - // - const dir_path& ad (find_out_root (out_root.directory ())); - - if (!ad.empty ()) - { - dir_path rd (ad.relative (out_root)); - level5 ([&]{trace << out_root << " amalgamated as " << rd;}); - v = move (rd); - } - } - } - - // See if we have any subprojects. In a sense, this is the other - // side/direction of the amalgamation logic above. Here, the - // subprojects variable may or may not be set by the user (in - // bootstrap.build) or by an earlier call to this function for - // the same scope. When set by the user, the empty special value - // means that there are no subproject and none should be searched - // for (and which we convert to NULL below). Otherwise, it is a - // list of directory[=project] pairs. The directory must be - // relative to our out_root. If the project name is not specified, - // then we have to figure it out. When subprojects are calculated, - // the NULL value indicates that we found no subprojects. - // - { - const variable& var (var_pool.find ("subprojects")); - auto rp (root.vars.assign(var)); // Set NULL by default. - value& v (rp.first); - - if (rp.second) - { - // No subprojects set so we need to figure out if there are any. - // - // First we are going to scan our out_root and find all the - // pre-configured subprojects. Then, if out_root != src_root, - // we are going to do the same for src_root. Here, however, - // we need to watch out for duplicates. - // - subprojects sps; - - if (dir_exists (out_root)) - find_subprojects (sps, out_root, out_root, true); - - if (out_root != src_root) - find_subprojects (sps, src_root, src_root, false); - - if (!sps.empty ()) // Keep it NULL if no subprojects. - v = move (sps); - } - else if (v) - { - // Convert empty to NULL. - // - if (v.empty ()) - v = nullptr; - else - { - // Pre-scan the value and convert it to the "canonical" form, - // that is, a list of simple=dir pairs. - // - for (auto i (v.data_.begin ()); i != v.data_.end (); ++i) - { - bool p (i->pair != '\0'); - - if (p) - { - // Project name. - // - if (!assign<string> (*i) || as<string> (*i).empty ()) - fail << "expected project name instead of '" << *i << "' in " - << "the subprojects variable"; - - ++i; // Got to have the second half of the pair. - } - - if (!assign<dir_path> (*i)) - fail << "expected directory instead of '" << *i << "' in the " - << "subprojects variable"; - - auto& d (as<dir_path> (*i)); - - // Figure out the project name if the user didn't specify one. - // - if (!p) - { - // Pass fallback src_root since this is a subproject that - // was specified by the user so it is most likely in our - // src. - // - string n (find_project_name (out_root / d, src_root / d)); - - // See find_subprojects() for details on unnamed projects. - // - if (n.empty ()) - n = d.posix_string () + '/'; - - i = v.data_.emplace (i, move (n)); - - i->pair = '='; - ++i; - } - } - - // Make it of the map type. - // - assign<subprojects> (v, var); - } - } - } - - return r; - } - - void - create_bootstrap_outer (scope& root) - { - auto l (root.vars["amalgamation"]); - - if (!l) - return; - - const dir_path& d (as<dir_path> (*l)); - dir_path out_root (root.out_path () / d); - out_root.normalize (); - - // src_root is a bit more complicated. Here we have three cases: - // - // 1. Amalgamation's src_root is "parallel" to the sub-project's. - // 2. Amalgamation's src_root is the same as its out_root. - // 3. Some other pre-configured (via src-root.build) src_root. - // - // So we need to try all these cases in some sensible order. - // #3 should probably be tried first since that src_root was - // explicitly configured by the user. After that, #2 followed - // by #1 seems reasonable. - // - scope& rs (create_root (out_root, dir_path ())); - bootstrap_out (rs); // #3 happens here, if at all. - - value& v (rs.assign ("src_root")); - - if (!v) - { - if (is_src_root (out_root)) // #2 - v = out_root; - else // #1 - { - dir_path src_root (root.src_path () / d); - src_root.normalize (); - v = move (src_root); - } - } - - setup_root (rs); - - bootstrap_src (rs); - create_bootstrap_outer (rs); - - // Check if we are strongly amalgamated by this outer root scope. - // - if (root.src_path ().sub (rs.src_path ())) - root.strong_ = rs.strong_scope (); // Itself or some outer scope. - } - - scope& - create_bootstrap_inner (scope& root, const dir_path& out_base) - { - if (auto l = root.vars["subprojects"]) - { - for (const name& n: *l) - { - if (n.pair != '\0') - continue; // Skip project names. - - dir_path out_root (root.out_path () / n.dir); - - if (!out_base.sub (out_root)) - continue; - - // The same logic to src_root as in create_bootstrap_outer(). - // - scope& rs (create_root (out_root, dir_path ())); - bootstrap_out (rs); - - value& v (rs.assign ("src_root")); - - if (!v) - v = is_src_root (out_root) - ? out_root - : (root.src_path () / n.dir); - - setup_root (rs); - - bootstrap_src (rs); - - // Check if we strongly amalgamated this inner root scope. - // - if (rs.src_path ().sub (root.src_path ())) - rs.strong_ = root.strong_scope (); // Itself or some outer scope. - - // See if there are more inner roots. - // - return create_bootstrap_inner (rs, out_base); - } - } - - return root; - } - - void - load_root_pre (scope& root) - { - tracer trace ("root_pre"); - - // First load outer roots, if any. - // - if (scope* rs = root.parent_scope ()->root_scope ()) - load_root_pre (*rs); - - // Finish off loading bootstrapped modules. - // - for (auto& p: root.modules) - { - const string& n (p.first); - module_state& s (p.second); - - if (s.boot) - { - load_module (false, n, root, root, s.loc); - assert (!s.boot); - } - } - - // Load root.build. - // - path bf (root.src_path () / path ("build/root.build")); - - if (file_exists (bf)) - source_once (bf, root, root); - } - - names - import (scope& ibase, name target, const location& loc) - { - tracer trace ("import"); - - // If there is no project specified for this target, then our - // run will be short and sweet: we simply return it as empty- - // project-qualified and let someone else (e.g., a rule) take - // a stab at it. - // - if (target.unqualified ()) - { - target.proj = &project_name_pool.find (""); - return names {move (target)}; - } - - // Otherwise, get the project name and convert the target to - // unqualified. - // - const string& project (*target.proj); - target.proj = nullptr; - - scope& iroot (*ibase.root_scope ()); - - // Figure out this project's out_root. - // - dir_path out_root; - dir_path fallback_src_root; // We have seen this already, haven't we..? - - // First search subprojects, starting with our root and then trying - // outer roots for as long as we are inside an amalgamation. - // - for (scope* r (&iroot);; r = r->parent_scope ()->root_scope ()) - { - // First check the amalgamation itself. - // - if (r != &iroot && as<string> (*r->vars["project"]) == project) - { - out_root = r->out_path (); - fallback_src_root = r->src_path (); - break; - } - - if (auto l = r->vars["subprojects"]) - { - const auto& m (as<subprojects> (*l)); - auto i (m.find (project)); - - if (i != m.end ()) - { - const dir_path& d ((*i).second); - out_root = r->out_path () / d; - fallback_src_root = r->src_path () / d; - break; - } - } - - if (!r->vars["amalgamation"]) - break; - } - - // Then try the config.import.* mechanism. - // - if (out_root.empty ()) - { - const variable& var ( - var_pool.find ("config.import." + project, dir_path_type)); - - if (auto l = iroot[var]) - { - out_root = as<dir_path> (*l); - - if (l.belongs (*global_scope)) // A value from command line. - { - // Process the path by making it absolute and normalized. - // - if (out_root.relative ()) - out_root = work / out_root; - - out_root.normalize (); - - // Set on our root scope (part of our configuration). - // - iroot.assign (var) = out_root; - - // Also update the command-line value. This is necessary to avoid - // a warning issued by the config module about global/root scope - // value mismatch. Not very clean. - // - dir_path& d (as<dir_path> (const_cast<value&> (*l))); - if (d != out_root) - d = out_root; - } - } - else - { - // If we can't find the project, convert it back into qualified - // target and return to let someone else (e.g., a rule) to take - // a stab at it. - // - target.proj = &project; - level5 ([&]{trace << "postponing " << target;}); - return names {move (target)}; - } - } - - // Bootstrap the imported root scope. This is pretty similar to - // what we do in main() except that here we don't try to guess - // src_root. - // - dir_path src_root (is_src_root (out_root) ? out_root : dir_path ()); - scope& root (create_root (out_root, src_root)); - - bootstrap_out (root); - - // Check that the bootstrap process set src_root. - // - if (auto l = root.vars["src_root"]) - { - const dir_path& p (as<dir_path> (*l)); - - if (!src_root.empty () && p != src_root) - fail (loc) << "bootstrapped src_root " << p << " does not match " - << "discovered " << src_root; - } - // Otherwise, use fallback if available. - // - else if (!fallback_src_root.empty ()) - { - value& v (root.assign ("src_root")); - v = move (fallback_src_root); - } - else - fail (loc) << "unable to determine src_root for imported " << project << - info << "consider configuring " << out_root; - - setup_root (root); - - bootstrap_src (root); - - // Bootstrap outer roots if any. Loading will be done by - // load_root_pre() below. - // - create_bootstrap_outer (root); - - // Load the imported root scope. - // - load_root_pre (root); - - // Create a temporary scope so that the export stub does not mess - // up any of our variables. - // - temp_scope ts (ibase); - - // "Pass" the imported project's roots to the stub. - // - ts.assign ("out_root") = move (out_root); - ts.assign ("src_root") = move (src_root); - - // Also pass the target being imported. - // - { - value& v (ts.assign ("target")); - - if (!target.empty ()) // Otherwise leave NULL. - v = move (target); - } - - // Load the export stub. Note that it is loaded in the context - // of the importing project, not the imported one. The export - // stub will normally switch to the imported root scope at some - // point. - // - path es (root.src_path () / path ("build/export.build")); - - try - { - ifstream ifs (es.string ()); - if (!ifs.is_open ()) - fail (loc) << "unable to open " << es; - - ifs.exceptions (ifstream::failbit | ifstream::badbit); - - level5 ([&]{trace << "importing " << es;}); - - // @@ Should we verify these are all unqualified names? Or maybe - // there is a use-case for the export stub to return a qualified - // name? - // - parser p; - return p.parse_export_stub (ifs, es, iroot, ts); - } - catch (const ifstream::failure&) - { - fail (loc) << "unable to read buildfile " << es; - } - - return names (); // Never reached. - } - - target& - import (const prerequisite_key& pk) - { - assert (pk.proj != nullptr); - const string& p (*pk.proj); - - // @@ We no longer have location. This is especially bad for the - // empty case, i.e., where do I need to specify the project - // name)? Looks like the only way to do this is to keep location - // in name and then in prerequisite. Perhaps one day... - // - if (!p.empty ()) - fail << "unable to import target " << pk << - info << "consider explicitly specifying its project out_root via the " - << "config.import." << p << " command line variable"; - else - fail << "unable to import target " << pk << - info << "consider adding its installation location" << - info << "or explicitly specifying its project name"; - - throw failed (); // No return. - } -} diff --git a/build/file.ixx b/build/file.ixx deleted file mode 100644 index 1a11cbe..0000000 --- a/build/file.ixx +++ /dev/null @@ -1,12 +0,0 @@ -// file : build/file.ixx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -namespace build -{ - inline void - source_once (const path& bf, scope& root, scope& base) - { - return source_once (bf, root, base, base); - } -} diff --git a/build/install/module b/build/install/module deleted file mode 100644 index 7a814c8..0000000 --- a/build/install/module +++ /dev/null @@ -1,26 +0,0 @@ -// file : build/install/module -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD_INSTALL_MODULE -#define BUILD_INSTALL_MODULE - -#include <build/types> -#include <build/utility> - -#include <build/module> - -namespace build -{ - namespace install - { - extern "C" void - install_boot (scope&, const location&, unique_ptr<module>&); - - extern "C" bool - install_init ( - scope&, scope&, const location&, unique_ptr<module>&, bool, bool); - } -} - -#endif // BUILD_INSTALL_MODULE diff --git a/build/install/module.cxx b/build/install/module.cxx deleted file mode 100644 index 706f367..0000000 --- a/build/install/module.cxx +++ /dev/null @@ -1,188 +0,0 @@ -// file : build/install/module.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <build/install/module> - -#include <build/scope> -#include <build/target> -#include <build/rule> -#include <build/operation> -#include <build/diagnostics> - -#include <build/config/utility> - -#include <build/install/rule> -#include <build/install/utility> -#include <build/install/operation> - -using namespace std; -using namespace butl; - -namespace build -{ - namespace install - { - // Set install.<name>.* values based on config.install.<name>.* ones - // or the defaults. If none of config.install.* values were specified, - // then we do omitted/delayed configuration. Note that we still need - // to set all the install.* values to defaults, as if we had the - // default configuration. - // - // If override is true, then override values that came from outer - // configurations. We have to do this for paths that contain the - // package name. - // - template <typename T> - static void - set_var (bool spec, - scope& r, - const char* name, - const char* var, - const T* dv, - bool override = false) - { - string vn; - const value* cv (nullptr); - - if (spec) - { - vn = "config.install."; - vn += name; - vn += var; - const variable& vr ( - var_pool.find (move (vn), &value_traits<T>::value_type)); - - cv = dv != nullptr - ? &config::required (r, vr, *dv, override).first.get () - : &config::optional (r, vr); - } - - vn = "install."; - vn += name; - vn += var; - const variable& vr ( - var_pool.find (move (vn), &value_traits<T>::value_type)); - - value& v (r.assign (vr)); - - if (spec) - { - if (*cv && !cv->empty ()) - v = *cv; - } - else - { - if (dv != nullptr) - v = *dv; - } - } - - static void - set_dir (bool s, // specified - scope& r, // root scope - const char* n, // var name - const string& ps, // path (as string) - const string& fm = string (), // file mode - const string& dm = string (), // dir mode - const string& c = string (), // command - bool o = false) // override - { - dir_path p (ps); - set_var (s, r, n, "", p.empty () ? nullptr : &p, o); - set_var (s, r, n, ".mode", fm.empty () ? nullptr : &fm); - set_var (s, r, n, ".dir_mode", dm.empty () ? nullptr : &dm); - set_var<string> (s, r, n, ".sudo", nullptr); - set_var (s, r, n, ".cmd", c.empty () ? nullptr : &c); - set_var<strings> (s, r, n, ".options", nullptr); - } - - static alias_rule alias_; - static file_rule file_; - - extern "C" void - install_boot (scope& r, const location&, unique_ptr<module>&) - { - tracer trace ("install::boot"); - - level5 ([&]{trace << "for " << r.out_path ();}); - - // Register the install operation. - // - r.operations.insert (install_id, install); - } - - extern "C" bool - install_init (scope& r, - scope& b, - const location& l, - unique_ptr<module>&, - bool first, - bool) - { - tracer trace ("install::init"); - - if (!first) - { - warn (l) << "multiple install module initializations"; - return true; - } - - const dir_path& out_root (r.out_path ()); - level5 ([&]{trace << "for " << out_root;}); - - // Enter module variables. - // - // Note that the set_dir() calls below enter some more. - // - if (first) - { - auto& v (var_pool); - - v.find ("install", dir_path_type); - } - - // Register our alias and file installer rule. - // - b.rules.insert<alias> (perform_install_id, "install.alias", alias_); - b.rules.insert<file> (perform_install_id, "install.file", file_); - - // Configuration. - // - // Note that we don't use any defaults for root -- the location - // must be explicitly specified or the installer will complain - // if and when we try to install. - // - if (first) - { - bool s (config::specified (r, "config.install")); - const string& n (as<string> (*r["project"])); - - set_dir (s, r, "root", "", "", "755", "install"); - set_dir (s, r, "data_root", "root", "644"); - set_dir (s, r, "exec_root", "root", "755"); - - set_dir (s, r, "sbin", "exec_root/sbin"); - set_dir (s, r, "bin", "exec_root/bin"); - set_dir (s, r, "lib", "exec_root/lib"); - set_dir (s, r, "libexec", "exec_root/libexec/" + n, "", "", "", true); - - set_dir (s, r, "data", "data_root/share/" + n, "", "", "", true); - set_dir (s, r, "include", "data_root/include"); - - set_dir (s, r, "doc", "data_root/share/doc/" + n, "", "", "", true); - set_dir (s, r, "man", "data_root/share/man"); - - set_dir (s, r, "man1", "man/man1"); - } - - // Configure "installability" for built-in target types. - // - path<doc> (b, dir_path ("doc")); // Install into install.doc. - path<man> (b, dir_path ("man")); // Install into install.man. - path<man1> (b, dir_path ("man1")); // Install into install.man1. - - return true; - } - } -} diff --git a/build/install/operation b/build/install/operation deleted file mode 100644 index 2763d10..0000000 --- a/build/install/operation +++ /dev/null @@ -1,18 +0,0 @@ -// file : build/install/operation -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD_INSTALL_OPERATION -#define BUILD_INSTALL_OPERATION - -#include <build/operation> - -namespace build -{ - namespace install - { - extern operation_info install; - } -} - -#endif // BUILD_INSTALL_OPERATION diff --git a/build/install/operation.cxx b/build/install/operation.cxx deleted file mode 100644 index 1246671..0000000 --- a/build/install/operation.cxx +++ /dev/null @@ -1,32 +0,0 @@ -// file : build/install/operation.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <build/install/operation> - -using namespace std; -using namespace butl; - -namespace build -{ - 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/build/install/rule b/build/install/rule deleted file mode 100644 index 54014e1..0000000 --- a/build/install/rule +++ /dev/null @@ -1,49 +0,0 @@ -// file : build/install/rule -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD_INSTALL_RULE -#define BUILD_INSTALL_RULE - -#include <build/rule> -#include <build/types> -#include <build/target> -#include <build/operation> - -namespace build -{ - 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 // BUILD_INSTALL_RULE diff --git a/build/install/rule.cxx b/build/install/rule.cxx deleted file mode 100644 index fa648b5..0000000 --- a/build/install/rule.cxx +++ /dev/null @@ -1,410 +0,0 @@ -// file : build/install/rule.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <build/install/rule> - -#include <butl/process> -#include <butl/filesystem> - -#include <build/scope> -#include <build/target> -#include <build/algorithm> -#include <build/diagnostics> - -#include <build/config/utility> - -using namespace std; -using namespace butl; - -namespace build -{ - namespace install - { - // Lookup the install or install.* variable. Return NULL if - // not found or if the value is the special 'false' name (which - // means do not install). T is either scope or target. - // - template <typename T> - static const dir_path* - lookup (T& t, const string& var) - { - auto l (t[var]); - - if (!l) - return nullptr; - - const dir_path& r (as<dir_path> (*l)); - return r.simple () && r.string () == "false" ? nullptr : &r; - } - - // alias_rule - // - match_result alias_rule:: - match (action, target& t, const std::string&) const - { - return t; - } - - recipe alias_rule:: - apply (action a, target& t, const match_result&) const - { - tracer trace ("install::alias_rule::apply"); - - for (prerequisite p: group_prerequisites (t)) - { - target& pt (search (p)); - - // Check if this prerequisite is explicitly "not installable", - // that is, there is the 'install' variable and its value is - // false. - // - // At first, this might seem redundand since we could have let - // the file_rule below take care of it. The nuance is this: this - // prerequsite can be in a different subproject that hasn't loaded - // the install module (and therefore has no file_rule registered). - // The typical example would be the 'tests' subproject. - // - auto l (pt["install"]); - - if (l && as<dir_path> (*l).string () == "false") - { - level5 ([&]{trace << "ignoring " << pt;}); - continue; - } - - build::match (a, pt); - t.prerequisite_targets.push_back (&pt); - } - - return default_recipe; - } - - // file_rule - // - - match_result file_rule:: - match (action a, target& t, const std::string&) const - { - // First determine if this target should be installed (called - // "installable" for short). - // - if (lookup (t, "install") == nullptr) - // If this is the update pre-operation, signal that we don't match so - // that some other rule can take care of it. - // - return a.operation () == update_id ? nullptr : match_result (t, false); - - match_result mr (t, true); - - // If this is the update pre-operation, change the recipe action - // to (update, 0) (i.e., "unconditional update"). - // - if (a.operation () == update_id) - mr.recipe_action = action (a.meta_operation (), update_id); - - return mr; - } - - target* file_rule:: - filter (action, target& t, prerequisite_member p) const - { - target& pt (p.search ()); - return pt.in (t.root_scope ()) ? &pt : nullptr; - } - - recipe file_rule:: - apply (action a, target& t, const match_result& mr) const - { - if (!mr.bvalue) // Not installable. - return noop_recipe; - - // Ok, if we are here, then this means: - // - // 1. This target is installable. - // 2. The action is either - // a. (perform, install, 0) or - // b. (*, update, install) - // - // In both cases, the next step is to search, match, and collect - // all the installable prerequisites. - // - // @@ Perhaps if [noinstall] will be handled by the - // group_prerequisite_members machinery, then we can just - // run standard search_and_match()? Will need an indicator - // that it was forced (e.g., [install]) for filter() below. - // - for (prerequisite_member p: group_prerequisite_members (a, t)) - { - // Ignore unresolved targets that are imported from other projects. - // We are definitely not installing those. - // - if (p.proj () != nullptr) - continue; - - // Let a customized rule have its say. - // - target* pt (filter (a, t, p)); - if (pt == nullptr) - continue; - - // See if the user instructed us not to install it. - // - auto l ((*pt)["install"]); - if (l && as<dir_path> (*l).string () == "false") - continue; - - build::match (a, *pt); - - // If the matched rule returned noop_recipe, then the target - // state will be set to unchanged as an optimization. Use this - // knowledge to optimize things on our side as well since this - // will help a lot in case of any static installable content - // (headers, documentation, etc). - // - if (pt->state () != target_state::unchanged) - t.prerequisite_targets.push_back (pt); - else - unmatch (a, *pt); // No intent to execute. - } - - // This is where we diverge depending on the operation. In the - // update pre-operation, we need to make sure that this target - // as well as all its installable prerequisites are up to date. - // - if (a.operation () == update_id) - { - // Save the prerequisite targets that we found since the - // call to match_delegate() below will wipe them out. - // - target::prerequisite_targets_type p; - - if (!t.prerequisite_targets.empty ()) - p.swap (t.prerequisite_targets); - - // Find the "real" update rule, that is, the rule that would - // have been found if we signalled that we do not match from - // match() above. - // - recipe d (match_delegate (a, t).first); - - // If we have no installable prerequisites, then simply redirect - // to it. - // - if (p.empty ()) - return d; - - // Ok, the worst case scenario: we need to cause update of - // prerequisite targets and also delegate to the real update. - // - return [pt = move (p), dr = move (d)] - (action a, target& t) mutable -> target_state - { - // Do the target update first. - // - target_state r (execute_delegate (dr, a, t)); - - // Swap our prerequisite targets back in and execute. - // - t.prerequisite_targets.swap (pt); - r |= execute_prerequisites (a, t); - pt.swap (t.prerequisite_targets); // In case we get re-executed. - - return r; - }; - } - else - return &perform_install; - } - - struct install_dir - { - dir_path dir; - string sudo; - string cmd; //@@ VAR type - const_strings_value options {nullptr}; - string mode; - string dir_mode; - }; - - // install -d <dir> - // - static void - install (const install_dir& base, const dir_path& d) - { - path reld (relative (d)); - - cstrings args; - - if (!base.sudo.empty ()) - args.push_back (base.sudo.c_str ()); - - args.push_back (base.cmd.c_str ()); - args.push_back ("-d"); - - if (base.options.d != nullptr) //@@ VAR - config::append_options (args, base.options); - - args.push_back ("-m"); - args.push_back (base.dir_mode.c_str ()); - args.push_back (reld.string ().c_str ()); - args.push_back (nullptr); - - if (verb >= 2) - print_process (args); - else if (verb) - text << "install " << d; - - try - { - process pr (args.data ()); - - if (!pr.wait ()) - throw failed (); - } - catch (const process_error& e) - { - error << "unable to execute " << args[0] << ": " << e.what (); - - if (e.child ()) - exit (1); - - throw failed (); - } - } - - // install <file> <dir> - // - static void - install (const install_dir& base, file& t) - { - path reld (relative (base.dir)); - path relf (relative (t.path ())); - - cstrings args; - - if (!base.sudo.empty ()) - args.push_back (base.sudo.c_str ()); - - args.push_back (base.cmd.c_str ()); - - if (base.options.d != nullptr) //@@ VAR - config::append_options (args, base.options); - - args.push_back ("-m"); - args.push_back (base.mode.c_str ()); - args.push_back (relf.string ().c_str ()); - args.push_back (reld.string ().c_str ()); - args.push_back (nullptr); - - if (verb >= 2) - print_process (args); - else if (verb) - text << "install " << t; - - try - { - process pr (args.data ()); - - if (!pr.wait ()) - throw failed (); - } - catch (const process_error& e) - { - error << "unable to execute " << args[0] << ": " << e.what (); - - if (e.child ()) - exit (1); - - throw failed (); - } - } - - // Resolve installation directory name to absolute directory path, - // creating leading directories as necessary. - // - static install_dir - resolve (scope& s, dir_path d, const string* var = nullptr) - { - install_dir r; - - if (d.absolute ()) - { - d.normalize (); - - // Make sure it already exists (this will normally be - // install.root with everything else defined in term of it). - // - if (!dir_exists (d)) - fail << "installation directory " << d << " does not exist"; - } - else - { - // If it is relative, then the first component is treated - // as the installation directory name, e.g., bin, sbin, lib, - // etc. Look it up and recurse. - // - const string& sn (*d.begin ()); - const string var ("install." + sn); - if (const dir_path* dn = lookup (s, var)) - { - r = resolve (s, *dn, &var); - d = r.dir / dir_path (++d.begin (), d.end ()); - d.normalize (); - - if (!dir_exists (d)) - install (r, d); // install -d - } - else - fail << "unknown installation directory name " << sn << - info << "did you forget to specify config." << var << "?"; - } - - r.dir = move (d); - - // Override components in install_dir if we have our own. - // - if (var != nullptr) - { - if (auto l = s[*var + ".sudo"]) r.sudo = as<string> (*l); - if (auto l = s[*var + ".cmd"]) r.cmd = as<string> (*l); - if (auto l = s[*var + ".mode"]) r.mode = as<string> (*l); - if (auto l = s[*var + ".dir_mode"]) r.dir_mode = as<string> (*l); - if (auto l = s[*var + ".options"]) r.options = as<strings> (*l); - } - - // Set defaults for unspecified components. - // - if (r.cmd.empty ()) r.cmd = "install"; - if (r.mode.empty ()) r.mode = "644"; - if (r.dir_mode.empty ()) r.dir_mode = "755"; - - return r; - } - - target_state file_rule:: - perform_install (action a, target& t) - { - file& ft (static_cast<file&> (t)); - assert (!ft.path ().empty ()); // Should have been assigned by update. - - // First handle installable prerequisites. - // - target_state r (execute_prerequisites (a, t)); - - // Resolve and, if necessary, create target directory. - // - install_dir d ( - resolve (t.base_scope (), - as<dir_path> (*t["install"]))); // We know it's there. - - // Override mode if one was specified. - // - if (auto l = t["install.mode"]) - d.mode = as<string> (*l); - - install (d, ft); - return (r |= target_state::changed); - } - } -} diff --git a/build/install/utility b/build/install/utility deleted file mode 100644 index 9bc41f1..0000000 --- a/build/install/utility +++ /dev/null @@ -1,40 +0,0 @@ -// file : build/install/utility -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD_INSTALL_UTILITY -#define BUILD_INSTALL_UTILITY - -#include <string> -#include <utility> - -#include <build/scope> -#include <build/types> - -namespace build -{ - namespace install - { - // Set install path, mode for a target type. - // - template <typename T> - inline void - path (scope& s, dir_path d) - { - auto r (s.target_vars[T::static_type]["*"].assign ("install")); - if (r.second) // Already set by the user? - r.first.get () = std::move (d); - } - - template <typename T> - inline void - mode (scope& s, std::string m) - { - auto r (s.target_vars[T::static_type]["*"].assign ("install.mode")); - if (r.second) // Already set by the user? - r.first.get () = std::move (m); - } - } -} - -#endif // BUILD_INSTALL_UTILITY diff --git a/build/lexer b/build/lexer deleted file mode 100644 index 4a50e2a..0000000 --- a/build/lexer +++ /dev/null @@ -1,138 +0,0 @@ -// file : build/lexer -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD_LEXER -#define BUILD_LEXER - -#include <stack> -#include <string> -#include <iosfwd> -#include <cstddef> // size_t -#include <cstdint> // uint64_t -#include <cassert> -#include <exception> - -#include <butl/char-scanner> - -#include <build/types> -#include <build/utility> - -#include <build/token> -#include <build/diagnostics> - -namespace build -{ - // Context-dependent lexing mode. In the value mode we don't treat - // certain characters (e.g., +, =) as special so that we can use - // them in the variable values, e.g., 'foo = g++'. In contrast, in - // the variable mode, we restrict certain character (e.g., /) from - // appearing in the name. The pairs mode is just like value except - // that we split names separated by the pair character. The eval - // mode is used in the evaluation context. - // - // The alternnative modes must be set manually. The value and pairs - // modes are automatically reset after the end of the line. The - // variable mode is reset after the name token. And the eval mode - // is reset after the closing ')'. - // - // Quoted is an internal mode and should not be set explicitly. - // - enum class lexer_mode {normal, variable, value, pairs, eval, quoted}; - - class lexer: protected butl::char_scanner - { - public: - lexer (std::istream& is, - const std::string& name, - void (*processor) (token&, const lexer&) = nullptr) - : char_scanner (is), fail (name), processor_ (processor), sep_ (false) - { - mode_.push (lexer_mode::normal); - } - - const std::string& - name () const {return fail.name_;} - - // Note: sets mode for the next token. If mode is pairs, then - // the second argument specifies the separator character. - // - void - mode (lexer_mode m, char pair_separator = '=') - { - mode_.push (m); - pair_separator_ = pair_separator; - } - - // Expire the current mode early. - // - void - expire_mode () {mode_.pop ();} - - lexer_mode - mode () const {return mode_.top ();} - - char - pair_separator () const {return pair_separator_;} - - // Scanner. - // - token - next (); - - // Peek at the first character of the next token. Return the character - // or 0 if the next token will be eos. Also return an indicator of - // whether the next token will be separated. - // - pair<char, bool> - peek_char (); - - private: - token - next_impl (); - - token - next_eval (); - - token - next_quoted (); - - token - name (bool separated); - - // Return true if we have seen any spaces. Skipped empty lines - // don't count. In other words, we are only interested in spaces - // that are on the same line as the following non-space character. - // - bool - skip_spaces (); - - xchar - escape (); - - // Diagnostics. - // - private: - struct fail_mark_base: build::fail_mark_base<failed> - { - fail_mark_base (const std::string& n): name_ (n) {} - - location_prologue - operator() (const xchar&) const; - - std::string name_; - }; - typedef diag_mark<fail_mark_base> fail_mark; - - private: - fail_mark fail; - - void (*processor_) (token&, const lexer&); - - std::stack<lexer_mode> mode_; - char pair_separator_; - bool sep_; // True if we skipped spaces in peek(). - }; -} - -#endif // BUILD_LEXER diff --git a/build/lexer.cxx b/build/lexer.cxx deleted file mode 100644 index 864fc7b..0000000 --- a/build/lexer.cxx +++ /dev/null @@ -1,431 +0,0 @@ -// file : build/lexer.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <build/lexer> - -using namespace std; - -namespace build -{ - typedef token_type type; - - token lexer:: - next () - { - token t (next_impl ()); - if (processor_ != nullptr) - processor_ (t, *this); - return t; - } - - pair<char, bool> lexer:: - peek_char () - { - // In the quoted mode we don't skip spaces. - // - sep_ = mode_.top () != lexer_mode::quoted && skip_spaces (); - xchar c (peek ()); - return make_pair (eos (c) ? '\0' : char (c), sep_); - } - - token lexer:: - next_impl () - { - lexer_mode m (mode_.top ()); - - // For some modes we have dedicated imlementations of next(). - // - switch (m) - { - case lexer_mode::eval: return next_eval (); - case lexer_mode::quoted: return next_quoted (); - default: break; - } - - bool sep (skip_spaces ()); - - xchar c (get ()); - uint64_t ln (c.line), cn (c.column); - - if (eos (c)) - return token (type::eos, sep, ln, cn); - - switch (c) - { - // NOTE: remember to update name(), next_eval() if adding new - // special characters. - // - case '\n': - { - // Expire value/pairs mode at the end of the line. - // - if (m == lexer_mode::value || m == lexer_mode::pairs) - mode_.pop (); - - return token (type::newline, sep, ln, cn); - } - case '{': return token (type::lcbrace, sep, ln, cn); - case '}': return token (type::rcbrace, sep, ln, cn); - case '$': return token (type::dollar, sep, ln, cn); - case '(': return token (type::lparen, sep, ln, cn); - case ')': return token (type::rparen, sep, ln, cn); - } - - // Handle pair separator. - // - if (m == lexer_mode::pairs && c == pair_separator_) - return token (c, sep, ln, cn); - - // The following characters are not treated as special in the - // value or pairs mode. - // - if (m != lexer_mode::value && m != lexer_mode::pairs) - { - switch (c) - { - // NOTE: remember to update name(), next_eval() if adding new - // special characters. - // - case ':': return token (type::colon, sep, ln, cn); - case '=': - { - if (peek () == '+') - { - get (); - return token (type::equal_plus, sep, ln, cn); - } - else - return token (type::equal, sep, ln, cn); - } - case '+': - { - if (peek () == '=') - { - get (); - return token (type::plus_equal, sep, ln, cn); - } - } - } - } - - // Otherwise it is a name. - // - unget (c); - return name (sep); - } - - token lexer:: - next_eval () - { - bool sep (skip_spaces ()); - xchar c (get ()); - - if (eos (c)) - fail (c) << "unterminated evaluation context"; - - uint64_t ln (c.line), cn (c.column); - - // This mode is quite a bit like the value mode when it comes - // to special characters. - // - switch (c) - { - // NOTE: remember to update name() if adding new special characters. - // - case '\n': fail (c) << "newline in evaluation context"; - case '{': return token (type::lcbrace, sep, ln, cn); - case '}': return token (type::rcbrace, sep, ln, cn); - case '$': return token (type::dollar, sep, ln, cn); - case '(': return token (type::lparen, sep, ln, cn); - case ')': - { - mode_.pop (); // Expire eval mode. - return token (type::rparen, sep, ln, cn); - } - } - - // Otherwise it is a name. - // - unget (c); - return name (sep); - } - - token lexer:: - next_quoted () - { - xchar c (get ()); - - if (eos (c)) - fail (c) << "unterminated double-quoted sequence"; - - uint64_t ln (c.line), cn (c.column); - - switch (c) - { - case '$': return token (type::dollar, false, ln, cn); - case '(': return token (type::lparen, false, ln, cn); - } - - // Otherwise it is a name. - // - unget (c); - return name (false); - } - - token lexer:: - name (bool sep) - { - xchar c (peek ()); - assert (!eos (c)); - - uint64_t ln (c.line), cn (c.column); - string lexeme; - - lexer_mode m (mode_.top ()); - bool quoted (m == lexer_mode::quoted); - - for (; !eos (c); c = peek ()) - { - bool done (false); - - // Handle pair separator. - // - if (m == lexer_mode::pairs && c == pair_separator_) - break; - - // The following characters are not treated as special in the - // value/pairs, eval, and quoted modes. - // - if (m != lexer_mode::value && - m != lexer_mode::pairs && - m != lexer_mode::eval && - m != lexer_mode::quoted) - { - switch (c) - { - case ':': - case '+': - case '=': - { - done = true; - break; - } - } - - if (done) - break; - } - - // While these extra characters are treated as the name end in - // the variable mode. - // - if (m == lexer_mode::variable) - { - switch (c) - { - case '/': - case '-': - { - done = true; - break; - } - } - - if (done) - break; - } - - // If we are quoted, these are ordinary characters. - // - if (m != lexer_mode::quoted) - { - switch (c) - { - case ' ': - case '\t': - case '\n': - case '#': - case '{': - case '}': - case ')': - { - done = true; - break; - } - case '\\': - { - get (); - c = escape (); - if (c != '\n') // Ignore. - lexeme += c; - continue; - } - case '\'': - { - // If we are in the variable mode, then treat quote as just - // another separator. - // - if (m == lexer_mode::variable) - { - done = true; - break; - } - else - { - get (); - - for (c = get (); !eos (c) && c != '\''; c = get ()) - lexeme += c; - - if (eos (c)) - fail (c) << "unterminated single-quoted sequence"; - - quoted = true; - continue; - } - } - } - - if (done) - break; - } - - switch (c) - { - case '$': - case '(': - { - done = true; - break; - } - case '\"': - { - // If we are in the variable mode, then treat quote as just - // another separator. - // - if (m == lexer_mode::variable) - { - done = true; - break; - } - else - { - get (); - - if (m == lexer_mode::quoted) - mode_.pop (); - else - { - mode_.push (lexer_mode::quoted); - quoted = true; - } - - m = mode_.top (); - continue; - } - } - default: - { - get (); - lexeme += c; - continue; - } - } - - assert (done); - break; - } - - if (m == lexer_mode::quoted && eos (c)) - fail (c) << "unterminated double-quoted sequence"; - - // Expire variable mode at the end of the name. - // - if (m == lexer_mode::variable) - mode_.pop (); - - return token (lexeme, sep, quoted, ln, cn); - } - - bool lexer:: - skip_spaces () - { - bool r (sep_); - sep_ = false; - - xchar c (peek ()); - bool start (c.column == 1); - - for (; !eos (c); c = peek ()) - { - switch (c) - { - case ' ': - case '\t': - { - r = true; - break; - } - case '\n': - { - // Skip empty lines. - // - if (start) - { - r = false; - break; - } - - return r; - } - case '#': - { - get (); - - // Read until newline or eos. - // - for (c = peek (); !eos (c) && c != '\n'; c = peek ()) - get (); - - r = true; - continue; - } - case '\\': - { - get (); - - if (peek () == '\n') - break; // Ignore. - - unget (c); - // Fall through. - } - default: - return r; // Not a space. - } - - get (); - } - - return r; - } - - lexer::xchar lexer:: - escape () - { - xchar c (get ()); - - if (eos (c)) - fail (c) << "unterminated escape sequence"; - - return c; - } - - location_prologue lexer::fail_mark_base:: - operator() (const xchar& c) const - { - return build::fail_mark_base<failed>::operator() ( - location (name_.c_str (), c.line, c.column)); - } -} diff --git a/build/module b/build/module deleted file mode 100644 index 061ef60..0000000 --- a/build/module +++ /dev/null @@ -1,86 +0,0 @@ -// file : build/module -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD_MODULE -#define BUILD_MODULE - -#include <map> - -#include <build/types> -#include <build/utility> - -#include <build/diagnostics> - -namespace build -{ - class scope; - class location; - - class module - { - public: - virtual - ~module () = default; - }; - - extern "C" - using module_boot_function = - void (scope& root, const location&, unique_ptr<module>&); - - // Return false if the module configuration (normally based on the default - // values) was unsuccessful but this is not (yet) an error. One example - // would be the optional use of a module. Or a module might remain - // unconfigured for as long as it is actually not used (e.g., install, - // dist). The return value is used to set the <module>.configured variable. - // - extern "C" - using module_init_function = - bool (scope& root, - scope& base, - const location&, - unique_ptr<module>&, - bool first, // First time for this project. - bool optional); // Loaded with 'using?' (optional module). - - - struct module_state - { - bool boot; // True if the module boot'ed but not yet init'ed. - module_init_function* init; - unique_ptr<build::module> module; - const location loc; // Boot location. - }; - - using loaded_module_map = std::map<string, module_state>; - - // Load and boot the specified module. - // - void - boot_module (const string& name, scope& root, const location&); - - // Load (if not already loaded) and initialize the specified module. Used - // by the parser but also by some modules to load prerequisite modules. - // Return true if the module was both successfully loaded and configured - // (false can only be returned if optional). - // - bool - load_module (bool optional, - const std::string& name, - scope& root, - scope& base, - const location&); - - // Builtin modules. - // - struct module_functions - { - module_boot_function* boot; - module_init_function* init; - }; - - using available_module_map = std::map<string, module_functions>; - extern available_module_map builtin_modules; -} - -#endif // BUILD_MODULE diff --git a/build/module.cxx b/build/module.cxx deleted file mode 100644 index 79a9bdb..0000000 --- a/build/module.cxx +++ /dev/null @@ -1,114 +0,0 @@ -// file : build/module.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <build/module> - -#include <utility> // make_pair() - -#include <build/scope> -#include <build/variable> -#include <build/diagnostics> - -using namespace std; - -namespace build -{ - 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/build/name b/build/name deleted file mode 100644 index ecf86cc..0000000 --- a/build/name +++ /dev/null @@ -1,113 +0,0 @@ -// file : build/name -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD_NAME -#define BUILD_NAME - -#include <string> -#include <vector> -#include <iosfwd> -#include <utility> // move() - -#include <butl/path> - -// Note: include <build/types> instead of this file directly. -// -namespace build -{ - using butl::dir_path; - - // A name is what we operate on by default. Depending on the context, - // it can be interpreted as a target or prerequisite name. A name - // without a type and directory can be used to represent any text. - // A name with directory and empty value represents a directory. - // A name may also be project-qualified. If the project name is - // empty, then it means the name is in a project other than our - // own (e.g., it is installed). - // - // If pair is not '\0', then this name and the next in the list - // form a pair. - // - struct name - { - name () = default; - - explicit name (std::string v): value (std::move (v)) {} - name& operator= (std::string v) {return *this = name (std::move (v));} - - explicit name (dir_path d): dir (std::move (d)) {} - name& operator= (dir_path d) {return *this = name (std::move (d));} - - name (std::string t, std::string v) - : type (std::move (t)), value (std::move (v)) {} - - name (dir_path d, std::string t, std::string v) - : dir (std::move (d)), type (std::move (t)), value (std::move (v)) {} - - // The first argument should be from project_name_pool. - // - name (const std::string* p, dir_path d, std::string t, std::string v) - : proj (p), - dir (std::move (d)), - type (std::move (t)), - value (std::move (v)) {} - - bool - qualified () const {return proj != nullptr;} - - bool - unqualified () const {return proj == nullptr;} - - bool - typed () const {return !type.empty ();} - - bool - untyped () const {return type.empty ();} - - bool - empty () const {return dir.empty () && value.empty ();} - - // Note that strictly speaking the following tests should be - // orthogonal to qualification. However, the vast majority of - // cases where we expect a simple or directory name, we also - // expect it to be unqualified. - // - // Note also that empty name is simple but not a directory. - // - bool - simple () const {return unqualified () && untyped () && dir.empty ();} - - bool - directory () const - {return unqualified () && untyped () && !dir.empty () && value.empty ();} - - const std::string* proj = nullptr; // Points to project_name_pool. - dir_path dir; - std::string type; - std::string value; - char pair = '\0'; // Pair symbol, if any. - }; - - inline bool - operator== (const name& x, const name& y) - { - return x.proj == y.proj && // Pooled, so can just compare pointers. - x.type == y.type && - x.dir == y.dir && - x.value == y.value; - } - - inline bool - operator!= (const name& x, const name& y) {return !(x == y);} - - typedef std::vector<name> names; - - std::ostream& - operator<< (std::ostream&, const name&); - - std::ostream& - operator<< (std::ostream&, const names&); -} - -#endif // BUILD_NAME diff --git a/build/name.cxx b/build/name.cxx deleted file mode 100644 index 1bb8d31..0000000 --- a/build/name.cxx +++ /dev/null @@ -1,63 +0,0 @@ -// file : build/name.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <build/name> - -#include <ostream> - -#include <build/diagnostics> - -using namespace std; - -namespace build -{ - 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/build/operation b/build/operation deleted file mode 100644 index d8cbd07..0000000 --- a/build/operation +++ /dev/null @@ -1,359 +0,0 @@ -// file : build/operation -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD_OPERATION -#define BUILD_OPERATION - -#include <string> -#include <iosfwd> -#include <vector> -#include <cstdint> -#include <functional> // reference_wrapper - -#include <butl/string-table> - -#include <build/types> - -namespace build -{ - class location; - class scope; - class target_key; - - // While we are using uint8_t for the meta/operation ids, we assume - // that each is limited to 4 bits (max 128 entries) so that we can - // store the combined action id in uint8_t as well. This makes our - // life easier when it comes to defining switch labels for action - // ids (no need to mess with endian-ness). - // - // Note that 0 is not a valid meta/operation/action id. - // - using meta_operation_id = std::uint8_t; - using operation_id = std::uint8_t; - using action_id = std::uint8_t; - - // Meta-operations and operations are not the end of the story. We - // also have operation nesting (currently only one level deep) which - // is used to implement pre/post operations (currently, but may be - // useful for other things). Here is the idea: the test operation - // needs to make sure that the targets that it needs to test are - // up-to-date. So it runs update as its pre-operation. It is almost - // like an ordinary update except that it has test as its outer - // operation (the meta-operations are always the same). This way a - // rule can recognize that this is "update for test" and do something - // differently. For example, if an executable is not a test, then - // there is no use updating it. At the same time, most rules will - // ignore the fact that this is a nested update and for them it is - // "update as usual". - // - struct action - { - action (): inner_id (0), outer_id (0) {} // Invalid action. - - bool - valid () const {return inner_id != 0;} - - // If this is not a nested operation, then outer should be 0. - // - action (meta_operation_id m, operation_id inner, operation_id outer = 0) - : inner_id ((m << 4) | inner), - outer_id (outer == 0 ? 0 : (m << 4) | outer) {} - - meta_operation_id - meta_operation () const {return inner_id >> 4;} - - operation_id - operation () const {return inner_id & 0xF;} - - operation_id - outer_operation () const {return outer_id & 0xF;} - - // Implicit conversion operator to action_id for the switch() - // statement, etc. Most places will only care about the inner - // operation. - // - operator action_id () const {return inner_id;} - - action_id inner_id; - action_id outer_id; - }; - - // This is an "overrides" comparison, i.e., it returns true - // if the recipe for x overrides recipe for y. The idea is - // that for the same inner operation, action with an outer - // operation is "weaker" than the one without. - // - inline bool - operator> (action x, action y) - { - return x.inner_id != y.inner_id || - (x.outer_id != y.outer_id && y.outer_id != 0); - } - - // Note that these ignore the outer operation. - // - inline bool - operator== (action x, action y) {return x.inner_id == y.inner_id;} - - inline bool - operator!= (action x, action y) {return !(x == y);} - - std::ostream& - operator<< (std::ostream&, action); - - // Id constants for build-in and pre-defined meta/operations. - // - const meta_operation_id perform_id = 1; - const meta_operation_id configure_id = 2; - const meta_operation_id disfigure_id = 3; - const meta_operation_id dist_id = 4; - - // The default operation is a special marker that can be used to - // indicate that no operation was explicitly specified by the user. - // - const operation_id default_id = 1; // Shall be first. - const operation_id update_id = 2; - const operation_id clean_id = 3; - const operation_id test_id = 4; - const operation_id install_id = 5; - - const action_id perform_update_id = (perform_id << 4) | update_id; - const action_id perform_clean_id = (perform_id << 4) | clean_id; - const action_id perform_test_id = (perform_id << 4) | test_id; - const action_id perform_install_id = (perform_id << 4) | install_id; - - const action_id configure_update_id = (configure_id << 4) | update_id; - - // Recipe execution mode. - // - // When a target is a prerequisite of another target, its recipe can be - // executed before the dependent's recipe (the normal case) or after. - // We will call these "front" and "back" execution modes, respectively - // (think "the prerequisite is 'front-running' the dependent"). - // - // There could also be several dependent targets and the prerequisite's - // recipe can be execute as part of the first dependent (the normal - // case) or last (or for all/some of them; see the recipe execution - // protocol in <target>). We will call these "first" and "last" - // execution modes, respectively. - // - // Now you may be having a hard time imagining where a mode other than - // the normal one (first/front) could be useful. An the answer is, - // compensating or inverse operations such as clean, uninstall, etc. - // If we use the last/back mode for, say, clean, then we will remove - // targets in the order inverse to the way they were updated. While - // this sounds like an elegant idea, are there any practical benefits - // of doing it this way. As it turns out there is (at least) one: when - // we are removing a directory (see fsdir{}), we want to do it after - // all the targets that depend on it (such as files, sub-directories) - // were removed. If we do it before, then the directory won't be empty - // yet. - // - // It appears that this execution mode is dictated by the essence of - // the operation. Constructive operations (those that "do") seem to - // naturally use the first/front mode. That is, we need to "do" the - // prerequisite first before we can "do" the dependent. While the - // destructive ones (those that "undo") seem to need last/back. That - // is, we need to "undo" all the dependents before we can "undo" the - // prerequisite (say, we need to remove all the files before we can - // remove their directory). - // - // If you noticed the parallel with the way C++ construction and - // destruction works for base/derived object then you earned a gold - // star! - // - // Note that the front/back mode is realized in the dependen's recipe - // (which is another indication that it is a property of the operation). - // - enum class execution_mode {first, last}; - - // Meta-operation info. - // - - // Normally a list of resolved and matched targets to execute. But - // can be something else, depending on the meta-operation. - // - typedef std::vector<void*> action_targets; - - struct meta_operation_info - { - const std::string name; - - // Name derivatives for diagnostics. If empty, then the meta- - // operation need not be mentioned. - // - const std::string name_do; // E.g., [to] 'configure'. - const std::string name_doing; // E.g., [while] 'configuring'. - const std::string name_done; // E.g., 'is configured'. - - // If operation_pre() is not NULL, then it may translate default_id - // (and only default_id) to some other operation. If not translated, - // then default_id is used. If, however, operation_pre() is NULL, - // then default_id is translated to update_id. - // - void (*meta_operation_pre) (); // Start of meta-operation batch. - operation_id (*operation_pre) (operation_id); // Start of operation batch. - - // Meta-operation-specific logic to load the buildfile, search and match - // the targets, and execute the action on the targets. - // - void (*load) (const path& buildfile, - scope& root, - const dir_path& out_base, - const dir_path& src_base, - const location&); - - void (*search) (scope& root, - const target_key&, - const location&, - action_targets&); - - void (*match) (action, action_targets&); - - void (*execute) (action, const action_targets&, bool quiet); - - void (*operation_post) (operation_id); // End of operation batch. - void (*meta_operation_post) (); // End of meta-operation batch. - }; - - // Built-in meta-operations. - // - - // perform - // - - // Load the buildfile. This is the default implementation that first - // calls root_pre(), then creates the scope for out_base, and, finally, - // loads the buildfile unless it has already been loaded for the root - // scope. - // - void - load (const path& buildfile, - scope& root, - const dir_path& out_base, - const dir_path& src_base, - const location&); - - // Search and match the target. This is the default implementation - // that does just that and adds a pointer to the target to the list. - // - void - search (scope&, const target_key&, const location&, action_targets&); - - void - match (action, action_targets&); - - // Execute the action on the list of targets. This is the default - // implementation that does just that while issuing appropriate - // diagnostics (unless quiet). - // - void - execute (action, const action_targets&, bool quiet); - - extern meta_operation_info perform; - - // Operation info. - // - struct operation_info - { - const std::string name; - - // Name derivatives for diagnostics. Note that unlike meta-operations, - // these can only be empty for the default operation (id 1), And - // meta-operations that make use of the default operation shall not - // have empty derivatives (failed which only target name will be - // printed). - // - const std::string name_do; // E.g., [to] 'update'. - const std::string name_doing; // E.g., [while] 'updating'. - const std::string name_done; // E.g., 'is up to date'. - - const execution_mode mode; - - // If the returned operation_id's are not 0, then they are injected - // as pre/post operations for this operation. Can be NULL if unused. - // The returned operation_id shall not be default_id. - // - operation_id (*pre) (meta_operation_id); - operation_id (*post) (meta_operation_id); - }; - - // Built-in operations. - // - extern operation_info default_; - extern operation_info update; - extern operation_info clean; - - // Global meta/operation tables. Each registered meta/operation - // is assigned an id which is used as an index in the per-project - // registered meta/operation lists. - // - // We have three types of meta/operations: built-in (e.g., perform, - // update), pre-defined (e.g., configure, test), and dynamically- - // defined. For built-in ones, both the id and implementation are - // part of the build2 core. For pre-defined, the id is registered - // as part of the core but the implementation is loaded as part of - // a module. The idea with pre-defined operations is that they have - // common, well-established semantics but could still be optional. - // Another aspect of pre-defined operations is that often rules - // across multiple modules need to know their ids. Finally, - // dynamically-defined meta/operations have their ids registered - // as part of a module load. In this case, the meta/operation is - // normally (but not necessarily) fully implemented by this module. - // - // Note also that the name of a meta/operation in a sense defines - // its semantics. It would be strange to have an operation called - // test that does two very different things in different projects. - // - extern butl::string_table<meta_operation_id> meta_operation_table; - extern butl::string_table<operation_id> operation_table; - - // These are "sparse" in the sense that we may have "holes" that - // are represented as NULL pointers. Also, lookup out of bounds - // is treated as a hole. - // - template <typename T> - struct sparse_vector - { - using base_type = std::vector<T*>; - using size_type = typename base_type::size_type; - - void - insert (size_type i, T& x) - { - size_type n (v_.size ()); - - if (i < n) - v_[i] = &x; - else - { - if (n != i) - v_.resize (i, nullptr); // Add holes. - v_.push_back (&x); - } - } - - T* - operator[] (size_type i) const - { - return i < v_.size () ? v_[i] : nullptr; - } - - bool - empty () const {return v_.empty ();} - - // Note that this is more of a "max index" rather than size. - // - size_type - size () const {return v_.size ();} - - private: - base_type v_; - }; - - using meta_operations = sparse_vector<const meta_operation_info>; - using operations = sparse_vector<const operation_info>; -} - -#endif // BUILD_OPERATION diff --git a/build/operation.cxx b/build/operation.cxx deleted file mode 100644 index b92479b..0000000 --- a/build/operation.cxx +++ /dev/null @@ -1,232 +0,0 @@ -// file : build/operation.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <build/operation> - -#include <vector> -#include <ostream> -#include <cassert> -#include <functional> // reference_wrapper - -#include <butl/utility> // reverse_iterate - -#include <build/scope> -#include <build/target> -#include <build/file> -#include <build/algorithm> -#include <build/diagnostics> -#include <build/dump> - -using namespace std; -using namespace butl; - -namespace build -{ - // action - // - ostream& - operator<< (ostream& os, action a) - { - uint16_t - m (a.meta_operation ()), - i (a.operation ()), - o (a.outer_operation ()); - - os << '(' << m << ','; - - if (o != 0) - os << o << '('; - - os << i; - - if (o != 0) - os << ')'; - - os << ')'; - - return os; - } - - // perform - // - void - load (const path& bf, - scope& root, - const dir_path& out_base, - const dir_path& src_base, - const location&) - { - // Load project's root[-pre].build. - // - load_root_pre (root); - - // Create the base scope. Note that its existence doesn't - // mean it was already setup as a base scope; it can be the - // same as root. - // - auto i (scopes.insert (out_base, nullptr, true, false)); - scope& base (setup_base (i, out_base, src_base)); - - // Load the buildfile unless it has already been loaded. - // - source_once (bf, root, base, root); - } - - void - search (scope&, - const target_key& tk, - const location& l, - action_targets& ts) - { - tracer trace ("search"); - - auto i (targets.find (tk, trace)); - if (i == targets.end ()) - fail (l) << "unknown target " << tk; - - ts.push_back (i->get ()); - } - - void - match (action a, action_targets& ts) - { - tracer trace ("match"); - - if (verb >= 6) - dump (a); - - for (void* vt: ts) - { - target& t (*static_cast<target*> (vt)); - level5 ([&]{trace << "matching " << t;}); - match (a, t); - } - - if (verb >= 6) - dump (a); - } - - void - execute (action a, const action_targets& ts, bool quiet) - { - tracer trace ("execute"); - - // Execute collecting postponed targets (to be re-examined later). - // Do it in reverse order if the execution mode is 'last'. - // - vector<reference_wrapper<target>> psp; - - auto body ( - [a, quiet, &psp, &trace] (void* v) - { - target& t (*static_cast<target*> (v)); - - level5 ([&]{trace << diag_doing (a, t);}); - - switch (execute (a, t)) - { - case target_state::unchanged: - { - if (!quiet) - info << diag_done (a, t); - break; - } - case target_state::postponed: - psp.push_back (t); - break; - case target_state::changed: - break; - case target_state::failed: - //@@ This could probably happen in a parallel build. - default: - assert (false); - } - }); - - if (current_mode == execution_mode::first) - for (void* v: ts) body (v); - else - for (void* v: reverse_iterate (ts)) body (v); - - // We should have executed every target that we matched. - // - assert (dependency_count == 0); - - // Re-examine postponed targets. This is the only reliable way to - // find out whether the target has changed. - // - for (target& t: psp) - { - switch (execute (a, t)) - { - case target_state::unchanged: - { - if (!quiet) - info << diag_done (a, t); - break; - } - case target_state::changed: - break; - case target_state::postponed: - assert (false); - case target_state::failed: - //@@ This could probably happen in a parallel build. - default: - assert (false); - } - } - } - - meta_operation_info perform { - "perform", - "", - "", - "", - nullptr, // meta-operation pre - nullptr, // operation pre - &load, - &search, - &match, - &execute, - nullptr, // operation post - nullptr // meta-operation post - }; - - // operations - // - operation_info default_ { - "<default>", - "", - "", - "", - execution_mode::first, - nullptr, - nullptr - }; - - operation_info update { - "update", - "update", - "updating", - "is up to date", - execution_mode::first, - nullptr, - nullptr - }; - - operation_info clean { - "clean", - "clean", - "cleaning", - "is clean", - execution_mode::last, - nullptr, - nullptr - }; - - // Tables. - // - string_table<meta_operation_id> meta_operation_table; - string_table<operation_id> operation_table; -} diff --git a/build/options b/build/options deleted file mode 100644 index a2fea2c..0000000 --- a/build/options +++ /dev/null @@ -1,298 +0,0 @@ -// -*- C++ -*- -// -// This file was generated by CLI, a command line interface -// compiler for C++. -// - -#ifndef BUILD_OPTIONS -#define BUILD_OPTIONS - -// Begin prologue. -// -// -// End prologue. - -#include <iosfwd> -#include <string> -#include <cstddef> -#include <exception> - -namespace cl -{ - class unknown_mode - { - public: - enum value - { - skip, - stop, - fail - }; - - unknown_mode (value v); - - operator value () const - { - return v_; - } - - private: - value v_; - }; - - // Exceptions. - // - - class exception: public std::exception - { - public: - virtual void - print (::std::ostream&) const = 0; - }; - - ::std::ostream& - operator<< (::std::ostream&, const exception&); - - class unknown_option: public exception - { - public: - virtual - ~unknown_option () throw (); - - unknown_option (const std::string& option); - - const std::string& - option () const; - - virtual void - print (::std::ostream&) const; - - virtual const char* - what () const throw (); - - private: - std::string option_; - }; - - class unknown_argument: public exception - { - public: - virtual - ~unknown_argument () throw (); - - unknown_argument (const std::string& argument); - - const std::string& - argument () const; - - virtual void - print (::std::ostream&) const; - - virtual const char* - what () const throw (); - - private: - std::string argument_; - }; - - class missing_value: public exception - { - public: - virtual - ~missing_value () throw (); - - missing_value (const std::string& option); - - const std::string& - option () const; - - virtual void - print (::std::ostream&) const; - - virtual const char* - what () const throw (); - - private: - std::string option_; - }; - - class invalid_value: public exception - { - public: - virtual - ~invalid_value () throw (); - - invalid_value (const std::string& option, - const std::string& value); - - const std::string& - option () const; - - const std::string& - value () const; - - virtual void - print (::std::ostream&) const; - - virtual const char* - what () const throw (); - - private: - std::string option_; - std::string value_; - }; - - class eos_reached: public exception - { - public: - virtual void - print (::std::ostream&) const; - - virtual const char* - what () const throw (); - }; - - class scanner - { - public: - virtual - ~scanner (); - - virtual bool - more () = 0; - - virtual const char* - peek () = 0; - - virtual const char* - next () = 0; - - virtual void - skip () = 0; - }; - - class argv_scanner: public scanner - { - public: - argv_scanner (int& argc, char** argv, bool erase = false); - argv_scanner (int start, int& argc, char** argv, bool erase = false); - - int - end () const; - - virtual bool - more (); - - virtual const char* - peek (); - - virtual const char* - next (); - - virtual void - skip (); - - private: - int i_; - int& argc_; - char** argv_; - bool erase_; - }; - - template <typename X> - struct parser; -} - -#include <cstdint> - -class options -{ - public: - options (); - - options (int& argc, - char** argv, - bool erase = false, - ::cl::unknown_mode option = ::cl::unknown_mode::fail, - ::cl::unknown_mode argument = ::cl::unknown_mode::stop); - - options (int start, - int& argc, - char** argv, - bool erase = false, - ::cl::unknown_mode option = ::cl::unknown_mode::fail, - ::cl::unknown_mode argument = ::cl::unknown_mode::stop); - - options (int& argc, - char** argv, - int& end, - bool erase = false, - ::cl::unknown_mode option = ::cl::unknown_mode::fail, - ::cl::unknown_mode argument = ::cl::unknown_mode::stop); - - options (int start, - int& argc, - char** argv, - int& end, - bool erase = false, - ::cl::unknown_mode option = ::cl::unknown_mode::fail, - ::cl::unknown_mode argument = ::cl::unknown_mode::stop); - - options (::cl::scanner&, - ::cl::unknown_mode option = ::cl::unknown_mode::fail, - ::cl::unknown_mode argument = ::cl::unknown_mode::stop); - - // Option accessors. - // - const bool& - help () const; - - const bool& - version () const; - - const bool& - v () const; - - const bool& - q () const; - - const std::uint16_t& - verbose () const; - - bool - verbose_specified () const; - - // Print usage information. - // - static void - print_usage (::std::ostream&); - - // 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 <build/options.ixx> - -// Begin epilogue. -// -// -// End epilogue. - -#endif // BUILD_OPTIONS diff --git a/build/options.cli b/build/options.cli deleted file mode 100644 index 9bd1dcd..0000000 --- a/build/options.cli +++ /dev/null @@ -1,31 +0,0 @@ -// file : build/options.cli -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -include <cstdint>; - -class options -{ - bool --help {"Print usage information and exit."}; - bool --version {"Print version and exit."}; - - bool -v - { - "Print actual commands being executed. This is equivalent to - \cb{--verbose 2}." - }; - - bool -q - { - "Run quietly, only printing error messages. This is equivalent - to \cb{--verbose 0}." - }; - - std::uint16_t --verbose = 1 - { - "<level>", - "Set the diagnostics verbosity to <level> between 0 (disabled) and - 6 (lots of information). The default is 1. @@ Need to document - further." - }; -}; diff --git a/build/options.cxx b/build/options.cxx deleted file mode 100644 index b5c2754..0000000 --- a/build/options.cxx +++ /dev/null @@ -1,552 +0,0 @@ -// -*- C++ -*- -// -// This file was generated by CLI, a command line interface -// compiler for C++. -// - -// Begin prologue. -// -// -// End prologue. - -#include <build/options> - -#include <map> -#include <set> -#include <string> -#include <vector> -#include <ostream> -#include <sstream> - -namespace cl -{ - // unknown_option - // - unknown_option:: - ~unknown_option () throw () - { - } - - void unknown_option:: - print (::std::ostream& os) const - { - os << "unknown option '" << option ().c_str () << "'"; - } - - const char* unknown_option:: - what () const throw () - { - return "unknown option"; - } - - // unknown_argument - // - unknown_argument:: - ~unknown_argument () throw () - { - } - - void unknown_argument:: - print (::std::ostream& os) const - { - os << "unknown argument '" << argument ().c_str () << "'"; - } - - const char* unknown_argument:: - what () const throw () - { - return "unknown argument"; - } - - // missing_value - // - missing_value:: - ~missing_value () throw () - { - } - - void missing_value:: - print (::std::ostream& os) const - { - os << "missing value for option '" << option ().c_str () << "'"; - } - - const char* missing_value:: - what () const throw () - { - return "missing option value"; - } - - // invalid_value - // - invalid_value:: - ~invalid_value () throw () - { - } - - void invalid_value:: - print (::std::ostream& os) const - { - os << "invalid value '" << value ().c_str () << "' for option '" - << option ().c_str () << "'"; - } - - const char* invalid_value:: - what () const throw () - { - return "invalid option value"; - } - - // eos_reached - // - void eos_reached:: - print (::std::ostream& os) const - { - os << what (); - } - - const char* eos_reached:: - what () const throw () - { - return "end of argument stream reached"; - } - - // scanner - // - scanner:: - ~scanner () - { - } - - // argv_scanner - // - bool argv_scanner:: - more () - { - return i_ < argc_; - } - - const char* argv_scanner:: - peek () - { - if (i_ < argc_) - return argv_[i_]; - else - throw eos_reached (); - } - - const char* argv_scanner:: - next () - { - if (i_ < argc_) - { - const char* r (argv_[i_]); - - if (erase_) - { - for (int i (i_ + 1); i < argc_; ++i) - argv_[i - 1] = argv_[i]; - - --argc_; - argv_[argc_] = 0; - } - else - ++i_; - - return r; - } - else - throw eos_reached (); - } - - void argv_scanner:: - skip () - { - if (i_ < argc_) - ++i_; - else - throw eos_reached (); - } - - template <typename X> - struct parser - { - static void - parse (X& x, bool& xs, scanner& s) - { - std::string o (s.next ()); - - if (s.more ()) - { - std::string v (s.next ()); - std::istringstream is (v); - if (!(is >> x && is.eof ())) - throw invalid_value (o, v); - } - else - throw missing_value (o); - - xs = true; - } - }; - - template <> - struct parser<bool> - { - static void - parse (bool& x, scanner& s) - { - s.next (); - x = true; - } - }; - - template <> - struct parser<std::string> - { - static void - parse (std::string& x, bool& xs, scanner& s) - { - const char* o (s.next ()); - - if (s.more ()) - x = s.next (); - else - throw missing_value (o); - - xs = true; - } - }; - - template <typename X> - struct parser<std::vector<X> > - { - static void - parse (std::vector<X>& c, bool& xs, scanner& s) - { - X x; - bool dummy; - parser<X>::parse (x, dummy, s); - c.push_back (x); - xs = true; - } - }; - - template <typename X> - struct parser<std::set<X> > - { - static void - parse (std::set<X>& c, bool& xs, scanner& s) - { - X x; - bool dummy; - parser<X>::parse (x, dummy, s); - c.insert (x); - xs = true; - } - }; - - template <typename K, typename V> - struct parser<std::map<K, V> > - { - static void - parse (std::map<K, V>& m, bool& xs, scanner& s) - { - std::string o (s.next ()); - - if (s.more ()) - { - std::string ov (s.next ()); - std::string::size_type p = ov.find ('='); - - if (p == std::string::npos) - { - K k = K (); - - if (!ov.empty ()) - { - std::istringstream ks (ov); - - if (!(ks >> k && ks.eof ())) - throw invalid_value (o, ov); - } - - m[k] = V (); - } - else - { - K k = K (); - V v = V (); - std::string kstr (ov, 0, p); - std::string vstr (ov, p + 1); - - if (!kstr.empty ()) - { - std::istringstream ks (kstr); - - if (!(ks >> k && ks.eof ())) - throw invalid_value (o, ov); - } - - if (!vstr.empty ()) - { - std::istringstream vs (vstr); - - if (!(vs >> v && vs.eof ())) - throw invalid_value (o, ov); - } - - m[k] = v; - } - } - else - throw missing_value (o); - - xs = true; - } - }; - - template <typename X, typename T, T X::*M> - void - thunk (X& x, scanner& s) - { - parser<T>::parse (x.*M, s); - } - - template <typename X, typename T, T X::*M, bool X::*S> - void - thunk (X& x, scanner& s) - { - parser<T>::parse (x.*M, x.*S, s); - } -} - -#include <map> -#include <cstring> - -// options -// - -options:: -options () -: help_ (), - version_ (), - v_ (), - q_ (), - verbose_ (1), - verbose_specified_ (false) -{ -} - -options:: -options (int& argc, - char** argv, - bool erase, - ::cl::unknown_mode opt, - ::cl::unknown_mode arg) -: help_ (), - version_ (), - v_ (), - q_ (), - verbose_ (1), - verbose_specified_ (false) -{ - ::cl::argv_scanner s (argc, argv, erase); - _parse (s, opt, arg); -} - -options:: -options (int start, - int& argc, - char** argv, - bool erase, - ::cl::unknown_mode opt, - ::cl::unknown_mode arg) -: help_ (), - version_ (), - v_ (), - q_ (), - verbose_ (1), - verbose_specified_ (false) -{ - ::cl::argv_scanner s (start, argc, argv, erase); - _parse (s, opt, arg); -} - -options:: -options (int& argc, - char** argv, - int& end, - bool erase, - ::cl::unknown_mode opt, - ::cl::unknown_mode arg) -: help_ (), - version_ (), - v_ (), - q_ (), - verbose_ (1), - verbose_specified_ (false) -{ - ::cl::argv_scanner s (argc, argv, erase); - _parse (s, opt, arg); - end = s.end (); -} - -options:: -options (int start, - int& argc, - char** argv, - int& end, - bool erase, - ::cl::unknown_mode opt, - ::cl::unknown_mode arg) -: help_ (), - version_ (), - v_ (), - q_ (), - verbose_ (1), - verbose_specified_ (false) -{ - ::cl::argv_scanner s (start, argc, argv, erase); - _parse (s, opt, arg); - end = s.end (); -} - -options:: -options (::cl::scanner& s, - ::cl::unknown_mode opt, - ::cl::unknown_mode arg) -: help_ (), - version_ (), - v_ (), - q_ (), - verbose_ (1), - verbose_specified_ (false) -{ - _parse (s, opt, arg); -} - -void options:: -print_usage (::std::ostream& os) -{ - os << "--help Print usage information and exit." << ::std::endl; - - os << "--version Print version and exit." << ::std::endl; - - os << "-v Print actual commands being executed." << ::std::endl; - - os << "-q Run quietly, only printing error messages." << ::std::endl; - - os << "--verbose <level> Set the diagnostics verbosity to <level> between 0 (disabled)" << ::std::endl - << " and 6 (lots of information)." << ::std::endl; -} - -typedef -std::map<std::string, void (*) (options&, ::cl::scanner&)> -_cli_options_map; - -static _cli_options_map _cli_options_map_; - -struct _cli_options_map_init -{ - _cli_options_map_init () - { - _cli_options_map_["--help"] = - &::cl::thunk< options, bool, &options::help_ >; - _cli_options_map_["--version"] = - &::cl::thunk< options, bool, &options::version_ >; - _cli_options_map_["-v"] = - &::cl::thunk< options, bool, &options::v_ >; - _cli_options_map_["-q"] = - &::cl::thunk< options, bool, &options::q_ >; - _cli_options_map_["--verbose"] = - &::cl::thunk< options, std::uint16_t, &options::verbose_, - &options::verbose_specified_ >; - } -}; - -static _cli_options_map_init _cli_options_map_init_; - -bool options:: -_parse (const char* o, ::cl::scanner& s) -{ - _cli_options_map::const_iterator i (_cli_options_map_.find (o)); - - if (i != _cli_options_map_.end ()) - { - (*(i->second)) (*this, s); - return true; - } - - return false; -} - -void options:: -_parse (::cl::scanner& s, - ::cl::unknown_mode opt_mode, - ::cl::unknown_mode arg_mode) -{ - bool opt = true; - - while (s.more ()) - { - const char* o = s.peek (); - - if (std::strcmp (o, "--") == 0) - { - s.skip (); - opt = false; - continue; - } - - if (opt && _parse (o, s)); - else if (opt && std::strncmp (o, "-", 1) == 0 && o[1] != '\0') - { - switch (opt_mode) - { - case ::cl::unknown_mode::skip: - { - s.skip (); - continue; - } - case ::cl::unknown_mode::stop: - { - break; - } - case ::cl::unknown_mode::fail: - { - throw ::cl::unknown_option (o); - } - } - - break; - } - else - { - switch (arg_mode) - { - case ::cl::unknown_mode::skip: - { - s.skip (); - continue; - } - case ::cl::unknown_mode::stop: - { - break; - } - case ::cl::unknown_mode::fail: - { - throw ::cl::unknown_argument (o); - } - } - - break; - } - } -} - -// Begin epilogue. -// -// -// End epilogue. - diff --git a/build/options.ixx b/build/options.ixx deleted file mode 100644 index c44d7e5..0000000 --- a/build/options.ixx +++ /dev/null @@ -1,157 +0,0 @@ -// -*- C++ -*- -// -// This file was generated by CLI, a command line interface -// compiler for C++. -// - -// Begin prologue. -// -// -// End prologue. - -namespace cl -{ - // 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/build/parser b/build/parser deleted file mode 100644 index 2631ca2..0000000 --- a/build/parser +++ /dev/null @@ -1,296 +0,0 @@ -// file : build/parser -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD_PARSER -#define BUILD_PARSER - -#include <string> -#include <iosfwd> - -#include <build/types> -#include <build/utility> - -#include <build/spec> -#include <build/lexer> -#include <build/token> -#include <build/variable> // list_value -#include <build/diagnostics> - -namespace build -{ - class scope; - class target; - - class parser - { - public: - typedef build::names names_type; - typedef build::variable variable_type; - - // If boot is true, then we are parsing bootstrap.build and modules - // should only be bootstrapped. - // - parser (bool boot = false): fail (&path_), boot_ (boot) {} - - // Issue diagnostics and throw failed in case of an error. - // - void - parse_buildfile (std::istream&, const path&, scope& root, scope& base); - - buildspec - parse_buildspec (std::istream&, const std::string& name); - - token - parse_variable (lexer&, scope&, std::string name, token_type kind); - - names_type - parse_export_stub (std::istream& is, const path& p, scope& r, scope& b) - { - parse_buildfile (is, p, r, b); - return std::move (export_value_); - } - - // Recursive descent parser. - // - protected: - void - clause (token&, token_type&); - - void - print (token&, token_type&); - - void - source (token&, token_type&); - - void - include (token&, token_type&); - - void - import (token&, token_type&); - - void - export_ (token&, token_type&); - - void - using_ (token&, token_type&); - - void - define (token&, token_type&); - - void - if_else (token&, token_type&); - - void - variable (token&, token_type&, std::string name, token_type kind); - - std::string - variable_name (names_type&&, const location&); - - names_type - variable_value (token&, token_type&, const variable_type&); - - names_type - eval (token&, token_type&); - - // If chunk is true, then parse the smallest but complete, name-wise, - // chunk of input. Note that in this case you may still end up with - // multiple names, for example, {foo bar}. - // - names_type - names (token& t, token_type& tt, bool chunk = false) - { - names_type ns; - names (t, tt, ns, chunk, 0, nullptr, nullptr, nullptr); - return ns; - } - - void - names (token&, token_type&, - names_type&, - bool chunk, - std::size_t pair, - const std::string* prj, - const dir_path* dir, - const std::string* type); - - size_t - names_trailer (token&, token_type&, - names_type&, - size_t pair, - const std::string* prj, - const dir_path* dir, - const std::string* type); - - // Skip until newline or eos. - // - void - skip_line (token&, token_type&); - - // Skip until block-closing } or eos, taking into account nested blocks. - // - void - skip_block (token&, token_type&); - - // Return true if the name token can be considered a directive keyword. - // - bool - keyword (token&); - - // Buildspec. - // - buildspec - buildspec_clause (token&, token_type&, token_type end); - - // Utilities. - // - protected: - - // Switch to a new current scope. Note that this function might - // also have to switch to a new root scope if the new current - // scope is in another project. So both must be saved and - // restored. - // - void - switch_scope (const dir_path&); - - void - process_default_target (token&); - - // Enter buildfile as a target. - // - void - enter_buildfile (const path&); - - // Lexer. - // - protected: - token_type - next (token&, token_type&); - - // Be careful with peeking and switching the lexer mode. See keyword() - // for more information. - // - token_type - peek (); - - const token& - peeked () const - { - assert (peeked_); - return peek_; - } - - void - mode (lexer_mode m, char ps = '=') - { - if (replay_ != replay::play) - lexer_->mode (m, ps); - } - - lexer_mode - mode () const - { - assert (replay_ != replay::play); - return lexer_->mode (); - } - - void - expire_mode () - { - if (replay_ != replay::play) - lexer_->expire_mode (); - } - - // Token saving and replaying. Note that is can only be used in certain - // contexts. Specifically, the lexer mode should be the same and the code - // that parses a replay must not interact with the lexer directly (e.g., - // the keyword() test). For now we don't enforce any of this. - // - // Note also that the peeked token is not part of the replay, until it - // is "got". - // - // - void - replay_save () - { - assert (replay_ == replay::stop); - replay_ = replay::save; - } - - void - replay_play () - { - assert ((replay_ == replay::save && !replay_data_.empty ()) || - (replay_ == replay::play && replay_i_ == replay_data_.size ())); - - replay_i_ = 0; - replay_ = replay::play; - } - - void - replay_stop () - { - replay_data_.clear (); - replay_ = replay::stop; - } - - const token& - replay_next () - { - assert (replay_i_ != replay_data_.size ()); - return replay_data_[replay_i_++]; - } - - struct replay_guard - { - replay_guard (parser& p, bool start = true) - : p_ (start ? &p : nullptr) - { - if (p_ != nullptr) - p_->replay_save (); - } - - void - play () - { - if (p_ != nullptr) - p_->replay_play (); - } - - ~replay_guard () - { - if (p_ != nullptr) - p_->replay_stop (); - } - - private: - parser* p_; - }; - - // Diagnostics. - // - protected: - const fail_mark<failed> fail; - - protected: - bool boot_; - - const std::string* path_; // Path processed by diag_relative() and pooled. - lexer* lexer_; - target* target_; // Current target, if any. - scope* scope_; // Current base scope (out_base). - scope* root_; // Current root scope (out_root). - target* default_target_; - names_type export_value_; - - token peek_ = token (token_type::eos, false, 0, 0); - bool peeked_ = false; - - enum class replay {stop, save, play} replay_ = replay::stop; - vector<token> replay_data_; - size_t replay_i_; // Position of the next token during replay. - }; -} - -#endif // BUILD_PARSER diff --git a/build/parser.cxx b/build/parser.cxx deleted file mode 100644 index ad17ae9..0000000 --- a/build/parser.cxx +++ /dev/null @@ -1,2206 +0,0 @@ -// file : build/parser.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <build/parser> - -#include <cctype> // is{alpha alnum}() - -#include <memory> // unique_ptr -#include <fstream> -#include <utility> // move() -#include <iterator> // make_move_iterator() -#include <iostream> - -#include <build/types> -#include <build/utility> -#include <build/version> - -#include <build/scope> -#include <build/target> -#include <build/prerequisite> -#include <build/variable> -#include <build/module> -#include <build/file> -#include <build/diagnostics> -#include <build/context> - -using namespace std; - -namespace build -{ - static location - get_location (const token&, const void*); - - typedef token_type type; - - void parser:: - parse_buildfile (istream& is, const path& p, scope& root, scope& base) - { - enter_buildfile (p); - - path_ = &path_pool.find (diag_relative (p)); // Relative to work. - - lexer l (is, *path_); - lexer_ = &l; - target_ = nullptr; - scope_ = &base; - root_ = &root; - default_target_ = nullptr; - - token t (type::eos, false, 0, 0); - type tt; - next (t, tt); - - clause (t, tt); - - if (tt != type::eos) - fail (t) << "unexpected " << t; - - process_default_target (t); - } - - token parser:: - parse_variable (lexer& l, scope& s, string name, type kind) - { - path_ = &l.name (); // Note: not pooled. - lexer_ = &l; - target_ = nullptr; - scope_ = &s; - - type tt; - token t (type::eos, false, 0, 0); - variable (t, tt, name, kind); - return t; - } - - void parser:: - clause (token& t, type& tt) - { - tracer trace ("parser::clause", &path_); - - // clause() should always stop at a token that is at the beginning of - // the line (except for eof). That is, if something is called to parse - // a line, it should parse it until newline (or fail). This is important - // for if-else blocks, directory scopes, etc., that assume the } token - // they see is on the new line. - // - while (tt != type::eos) - { - // We always start with one or more names. - // - if (tt != type::name && - tt != type::lcbrace && // Untyped name group: '{foo ...' - tt != type::dollar && // Variable expansion: '$foo ...' - tt != type::lparen && // Eval context: '(foo) ...' - tt != type::colon) // Empty name: ': ...' - break; // Something else. Let our caller handle that. - - // See if this is one of the directives. - // - if (tt == type::name && keyword (t)) - { - const string& n (t.value); - - if (n == "print") - { - // @@ Is this the only place where it is valid? Probably also - // in var namespace. - // - print (t, tt); - continue; - } - else if (n == "source") - { - source (t, tt); - continue; - } - else if (n == "include") - { - include (t, tt); - continue; - } - else if (n == "import") - { - import (t, tt); - continue; - } - else if (n == "export") - { - export_ (t, tt); - continue; - } - else if (n == "using" || - n == "using?") - { - using_ (t, tt); - continue; - } - else if (n == "define") - { - define (t, tt); - continue; - } - else if (n == "if" || - n == "if!") - { - if_else (t, tt); - continue; - } - else if (n == "else" || - n == "elif" || - n == "elif!") - { - // Valid ones are handled in if_else(). - // - fail (t) << n << " without if"; - } - } - - // ': foo' is equvalent to '{}: foo' and to 'dir{}: foo'. - // - const location nloc (get_location (t, &path_)); - names_type ns (tt != type::colon - ? names (t, tt) - : names_type ({name ("dir", string ())})); - - if (tt == type::colon) - { - // While '{}:' means empty name, '{$x}:' where x is empty list - // means empty list. - // - if (ns.empty ()) - fail (t) << "target expected before :"; - - next (t, tt); - - if (tt == type::newline) - { - // See if this is a directory/target scope. - // - if (peek () == type::lcbrace) - { - next (t, tt); - - // Should be on its own line. - // - if (next (t, tt) != type::newline) - fail (t) << "expected newline after {"; - - // See if this is a directory or target scope. Different - // things can appear inside depending on which one it is. - // - bool dir (false); - for (const auto& n: ns) - { - // A name represents directory as an empty value. - // - if (n.directory ()) - { - if (ns.size () != 1) - { - // @@ TODO: point to name (and above). - // - fail (nloc) << "multiple names in directory scope"; - } - - dir = true; - } - } - - next (t, tt); - - if (dir) - { - // Directory scope. - // - dir_path p (move (ns[0].dir)); // Steal. - - // Relative scopes are opened relative to out, not src. - // - if (p.relative ()) - p = scope_->out_path () / p; - - p.normalize (); - - scope* ors (root_); - scope* ocs (scope_); - switch_scope (p); - - // A directory scope can contain anything that a top level can. - // - clause (t, tt); - - scope_ = ocs; - root_ = ors; - } - else - { - // @@ TODO: target scope. - } - - if (tt != type::rcbrace) - fail (t) << "expected } instead of " << t; - - // Should be on its own line. - // - if (next (t, tt) == type::newline) - next (t, tt); - else if (tt != type::eos) - fail (t) << "expected newline after }"; - - continue; - } - - // If this is not a scope, then it is a target without any - // prerequisites. - // - } - - // Dependency declaration or scope/target-specific variable - // assignment. - // - if (tt == type::name || - tt == type::lcbrace || - tt == type::dollar || - tt == type::lparen || - tt == type::newline || - tt == type::eos) - { - const location ploc (get_location (t, &path_)); - names_type pns (tt != type::newline && tt != type::eos - ? names (t, tt) - : names_type ()); - - // Common target entering code used in both cases. - // - auto enter_target = [this, &nloc, &trace] (name&& tn) -> target& - { - const string* e; - const target_type* ti (scope_->find_target_type (tn, e)); - - if (ti == nullptr) - fail (nloc) << "unknown target type " << tn.type; - - path& d (tn.dir); - - if (d.empty ()) - d = scope_->out_path (); // Already normalized. - else - { - if (d.relative ()) - d = scope_->out_path () / d; - - d.normalize (); - } - - // Find or insert. - // - return targets.insert ( - *ti, move (tn.dir), move (tn.value), e, trace).first; - }; - - // Scope/target-specific variable assignment. - // - if (tt == type::equal || - tt == type::equal_plus || - tt == type::plus_equal) - { - token at (t); - type att (tt); - - string v (variable_name (move (pns), ploc)); - - // If we have multiple targets/scopes, then we save the value - // tokens when parsing the first one and then replay them for - // the subsequent. We have to do it this way because the value - // may contain variable expansions that would be sensitive to - // the target/scope context in which they are evaluated. - // - replay_guard rg (*this, ns.size () > 1); - - for (name& n: ns) - { - if (n.qualified ()) - fail (nloc) << "project name in scope/target " << n; - - if (n.directory ()) - { - // The same code as in directory scope handling code above. - // - dir_path p (move (n.dir)); - - if (p.relative ()) - p = scope_->out_path () / p; - - p.normalize (); - - scope* ors (root_); - scope* ocs (scope_); - switch_scope (p); - - variable (t, tt, v, att); - - scope_ = ocs; - root_ = ors; - } - else - { - // Figure out if this is a target or type/pattern specific - // variable. - // - size_t p (n.value.find ('*')); - - if (p == string::npos) - { - target* ot (target_); - target_ = &enter_target (move (n)); - variable (t, tt, v, att); - target_ = ot; - } - else - { - // See tests/variable/type-pattern. - // - if (!n.dir.empty ()) - fail (nloc) << "directory in target type/pattern " << n; - - if (n.value.find ('*', p + 1) != string::npos) - fail (nloc) << "multiple wildcards in target type/pattern " - << n; - - // Resolve target type. If none is specified, use the root - // of the hierarchy. - // - const target_type* ti ( - n.untyped () - ? &target::static_type - : scope_->find_target_type (n.type)); - - if (ti == nullptr) - fail (nloc) << "unknown target type " << n.type; - - if (att == type::equal_plus) - fail (at) << "prepend to target type/pattern-specific " - << "variable " << v; - - if (att == type::plus_equal) - fail (at) << "append to target type/pattern-specific " - << "variable " << v; - - const auto& var (var_pool.find (v)); - - // Note: expand variables in the value in the context of - // the scope. - // - names_type vns (variable_value (t, tt, var)); - value& val (scope_->target_vars[*ti][move (n.value)].assign ( - var).first); - val.assign (move (vns), var); - } - } - - rg.play (); // Replay. - } - } - // Dependency declaration. - // - else - { - // Prepare the prerequisite list. - // - target::prerequisites_type ps; - ps.reserve (pns.size ()); - - for (auto& pn: pns) - { - const string* e; - const target_type* ti (scope_->find_target_type (pn, e)); - - if (ti == nullptr) - fail (ploc) << "unknown target type " << pn.type; - - pn.dir.normalize (); - - // Find or insert. - // - prerequisite& p ( - scope_->prerequisites.insert ( - pn.proj, - *ti, - move (pn.dir), - move (pn.value), - e, - *scope_, - trace).first); - - ps.emplace_back (p); - } - - for (auto& tn: ns) - { - if (tn.qualified ()) - fail (nloc) << "project name in target " << tn; - - target& t (enter_target (move (tn))); - - //@@ OPT: move if last/single target (common cases). - // - t.prerequisites.insert (t.prerequisites.end (), - ps.begin (), - ps.end ()); - - if (default_target_ == nullptr) - default_target_ = &t; - } - } - - if (tt == type::newline) - next (t, tt); - else if (tt != type::eos) - fail (t) << "expected newline instead of " << t; - - continue; - } - - if (tt == type::eos) - continue; - - fail (t) << "expected newline instead of " << t; - } - - // Variable assignment. - // - if (tt == type::equal || - tt == type::equal_plus || - tt == type::plus_equal) - { - variable (t, tt, variable_name (move (ns), nloc), tt); - - if (tt == type::newline) - next (t, tt); - else if (tt != type::eos) - fail (t) << "expected newline instead of " << t; - - continue; - } - - // Allow things like function calls that don't result in anything. - // - if (tt == type::newline && ns.empty ()) - { - next (t, tt); - continue; - } - - fail (t) << "unexpected " << t; - } - } - - void parser:: - source (token& t, type& tt) - { - tracer trace ("parser::source", &path_); - - // The rest should be a list of buildfiles. Parse them as names - // to get variable expansion and directory prefixes. - // - mode (lexer_mode::value); - next (t, tt); - const location l (get_location (t, &path_)); - names_type ns (tt != type::newline && tt != type::eos - ? names (t, tt) - : names_type ()); - - for (name& n: ns) - { - if (n.qualified () || n.empty () || n.value.empty ()) - fail (l) << "expected buildfile instead of " << n; - - // Construct the buildfile path. - // - path p (move (n.dir)); - p /= path (move (n.value)); - - // If the path is relative then use the src directory corresponding - // to the current directory scope. - // - if (root_->src_path_ != nullptr && p.relative ()) - p = src_out (scope_->out_path (), *root_) / p; - - p.normalize (); - - try - { - ifstream ifs (p.string ()); - - if (!ifs.is_open ()) - fail (l) << "unable to open " << p; - - ifs.exceptions (ifstream::failbit | ifstream::badbit); - - level5 ([&]{trace (t) << "entering " << p;}); - - enter_buildfile (p); - - const string* op (path_); - path_ = &path_pool.find (diag_relative (p)); // Relative to work. - - lexer l (ifs, *path_); - lexer* ol (lexer_); - lexer_ = &l; - - token t (type::eos, false, 0, 0); - type tt; - next (t, tt); - clause (t, tt); - - if (tt != type::eos) - fail (t) << "unexpected " << t; - - level5 ([&]{trace (t) << "leaving " << p;}); - - lexer_ = ol; - path_ = op; - } - catch (const ifstream::failure&) - { - fail (l) << "unable to read buildfile " << p; - } - } - - if (tt == type::newline) - next (t, tt); - else if (tt != type::eos) - fail (t) << "expected newline instead of " << t; - } - - void parser:: - include (token& t, type& tt) - { - tracer trace ("parser::include", &path_); - - if (root_->src_path_ == nullptr) - fail (t) << "inclusion during bootstrap"; - - // The rest should be a list of buildfiles. Parse them as names - // to get variable expansion and directory prefixes. - // - mode (lexer_mode::value); - next (t, tt); - const location l (get_location (t, &path_)); - names_type ns (tt != type::newline && tt != type::eos - ? names (t, tt) - : names_type ()); - - for (name& n: ns) - { - if (n.qualified () || n.empty ()) - fail (l) << "expected buildfile instead of " << n; - - // Construct the buildfile path. If it is a directory, then append - // 'buildfile'. - // - path p (move (n.dir)); - if (n.value.empty ()) - p /= path ("buildfile"); - else - { - bool d (path::traits::is_separator (n.value.back ()) - || n.type == "dir"); - - p /= path (move (n.value)); - if (d) - p /= path ("buildfile"); - } - - level6 ([&]{trace (l) << "relative path " << p;}); - - // Determine new out_base. - // - dir_path out_base; - - if (p.relative ()) - { - out_base = scope_->out_path () / p.directory (); - out_base.normalize (); - } - else - { - p.normalize (); - - // Make sure the path is in this project. Include is only meant - // to be used for intra-project inclusion (plus amalgamation). - // - bool in_out (false); - if (!p.sub (root_->src_path ()) && - !(in_out = p.sub (root_->out_path ()))) - fail (l) << "out of project include " << p; - - out_base = in_out - ? p.directory () - : out_src (p.directory (), *root_); - } - - // Switch the scope. Note that we need to do this before figuring - // out the absolute buildfile path since we may switch the project - // root and src_root with it (i.e., include into a sub-project). - // - scope* ors (root_); - scope* ocs (scope_); - switch_scope (out_base); - - // Use the new scope's src_base to get absolute buildfile path - // if it is relative. - // - if (p.relative ()) - p = scope_->src_path () / p.leaf (); - - level6 ([&]{trace (l) << "absolute path " << p;}); - - if (!root_->buildfiles.insert (p).second) // Note: may be "new" root. - { - level5 ([&]{trace (l) << "skipping already included " << p;}); - scope_ = ocs; - root_ = ors; - continue; - } - - try - { - ifstream ifs (p.string ()); - - if (!ifs.is_open ()) - fail (l) << "unable to open " << p; - - ifs.exceptions (ifstream::failbit | ifstream::badbit); - - level5 ([&]{trace (t) << "entering " << p;}); - - enter_buildfile (p); - - const string* op (path_); - path_ = &path_pool.find (diag_relative (p)); // Relative to work. - - lexer l (ifs, *path_); - lexer* ol (lexer_); - lexer_ = &l; - - target* odt (default_target_); - default_target_ = nullptr; - - token t (type::eos, false, 0, 0); - type tt; - next (t, tt); - clause (t, tt); - - if (tt != type::eos) - fail (t) << "unexpected " << t; - - process_default_target (t); - - level5 ([&]{trace (t) << "leaving " << p;}); - - default_target_ = odt; - lexer_ = ol; - path_ = op; - } - catch (const ifstream::failure&) - { - fail (l) << "unable to read buildfile " << p; - } - - scope_ = ocs; - root_ = ors; - } - - if (tt == type::newline) - next (t, tt); - else if (tt != type::eos) - fail (t) << "expected newline instead of " << t; - } - - void parser:: - import (token& t, type& tt) - { - tracer trace ("parser::import", &path_); - - if (root_->src_path_ == nullptr) - fail (t) << "import during bootstrap"; - - next (t, tt); - - // General import format: - // - // import [<var>=](<project>|<project>/<target>])+ - // - value* val (nullptr); - const build::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) - { - // build::import() will check the name, if required. - // - names_type r (build::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. - // build::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 > BUILD_VERSION) - fail (l) << "build2 " << v << " required" << - info << "running build2 " << BUILD_VERSION_STR; - } - } - else - { - assert (v.empty ()); // Module versioning not yet implemented. - - if (boot_) - boot_module (n, *root_, l); - else - load_module (optional, n, *root_, *scope_, l); - } - } - - if (tt == type::newline) - next (t, tt); - else if (tt != type::eos) - fail (t) << "expected newline instead of " << t; - } - - static target* - derived_factory (const target_type& t, dir_path d, string n, const string* e) - { - // Pass our type to the base factory so that it can detect that it is - // being called to construct a derived target. This can be used, for - // example, to decide whether to "link up" to the group. - // - target* r (t.base->factory (t, move (d), move (n), e)); - r->derived_type = &t; - return r; - } - - constexpr const char derived_ext_var[] = "extension"; - - void parser:: - define (token& t, type& tt) - { - // define <derived>: <base> - // - // See tests/define. - // - if (next (t, tt) != type::name) - fail (t) << "expected name instead of " << t << " in target type " - << "definition"; - - string dn (move (t.value)); - const location dnl (get_location (t, &path_)); - - if (next (t, tt) != type::colon) - fail (t) << "expected ':' instead of " << t << " in target type " - << "definition"; - - next (t, tt); - - if (tt == type::name) - { - // Target. - // - const string& bn (t.value); - const target_type* bt (scope_->find_target_type (bn)); - - if (bt == nullptr) - fail (t) << "unknown target type " << bn; - - unique_ptr<target_type> dt (new target_type (*bt)); - dt->base = bt; - dt->factory = &derived_factory; - - // Override extension derivation function: we most likely don't want - // to use the same default as our base (think cli: file). - // - dt->extension = &target_extension_var<derived_ext_var, nullptr>; - - target_type& rdt (*dt); // Save a non-const reference to the object. - - auto pr (scope_->target_types.emplace (dn, target_type_ref (move (dt)))); - - if (!pr.second) - fail (dnl) << "target type " << dn << " already define in this scope"; - - // Patch the alias name to use the map's key storage. - // - rdt.name = pr.first->first.c_str (); - - next (t, tt); // Get newline. - } - else - fail (t) << "expected name instead of " << t << " in target type " - << "definition"; - - if (tt == type::newline) - next (t, tt); - else if (tt != type::eos) - fail (t) << "expected newline instead of " << t; - } - - void parser:: - if_else (token& t, type& tt) - { - // Handle the whole if-else chain. See tests/if-else. - // - bool taken (false); // One of the branches has been taken. - - for (;;) - { - string k (move (t.value)); - next (t, tt); - - bool take (false); // Take this branch? - - if (k != "else") - { - // Should we evaluate the expression if one of the branches has - // already been taken? On the one hand, evaluating it is a waste - // of time. On the other, it can be invalid and the only way for - // the user to know their buildfile is valid is to test every - // branch. There could also be side effects. We also have the same - // problem with ignored branch blocks except there evaluating it - // is not an option. So let's skip it. - // - if (taken) - skip_line (t, tt); - else - { - if (tt == type::newline || tt == type::eos) - fail (t) << "expected " << k << "-expression instead of " << t; - - // Parse as names to get variable expansion, evaluation, etc. - // - const location nsl (get_location (t, &path_)); - names_type ns (names (t, tt)); - - // Should evaluate to true or false. - // - if (ns.size () != 1 || !value_traits<bool>::assign (ns[0])) - fail (nsl) << "expected " << k << "-expression to evaluate to " - << "'true' or 'false' instead of '" << ns << "'"; - - bool e (ns[0].value == "true"); - take = (k.back () == '!' ? !e : e); - } - } - else - take = !taken; - - if (tt != type::newline) - fail (t) << "expected newline instead of " << t << " after " << k - << (k != "else" ? "-expression" : ""); - - if (next (t, tt) != type::lcbrace) - fail (t) << "expected { instead of " << t << " at the beginning of " - << k << "-block"; - - if (next (t, tt) != type::newline) - fail (t) << "expected newline after {"; - - next (t, tt); - - if (take) - { - clause (t, tt); - taken = true; - } - else - skip_block (t, tt); - - if (tt != type::rcbrace) - fail (t) << "expected } instead of " << t << " at the end of " << k - << "-block"; - - next (t, tt); - - if (tt == type::newline) - next (t, tt); - else if (tt != type::eos) - fail (t) << "expected newline after }"; - - // See if we have another el* keyword. - // - if (k != "else" && tt == type::name && keyword (t)) - { - const string& n (t.value); - - if (n == "else" || n == "elif" || n == "elif!") - continue; - } - - break; - } - } - - void parser:: - print (token& t, type& tt) - { - // Parse the rest as names to get variable expansion, etc. Switch - // to the variable value lexing mode so that we don't treat special - // characters (e.g., ':') as the end of the names. - // - mode (lexer_mode::value); - next (t, tt); - names_type ns (tt != type::newline && tt != type::eos - ? names (t, tt) - : names_type ()); - - cout << ns << endl; - - if (tt != type::eos) - next (t, tt); // Swallow newline. - } - - string parser:: - variable_name (names_type&& ns, const location& l) - { - // The list should contain a single, simple name. - // - if (ns.size () != 1 || !ns[0].simple () || ns[0].empty ()) - fail (l) << "variable name expected instead of " << ns; - - string& n (ns[0].value); - - if (n.front () == '.') // Fully qualified name. - return string (n, 1, string::npos); - else - //@@ TODO: append namespace if any. - return move (n); - } - - void parser:: - variable (token& t, type& tt, string name, type kind) - { - const auto& var (var_pool.find (move (name))); - names_type vns (variable_value (t, tt, var)); - - if (kind == type::equal) - { - value& v (target_ != nullptr - ? target_->assign (var) - : scope_->assign (var)); - v.assign (move (vns), var); - } - else - { - value& v (target_ != nullptr - ? target_->append (var) - : scope_->append (var)); - - if (kind == type::equal_plus) - v.prepend (move (vns), var); - else - v.append (move (vns), var); - } - } - - names parser:: - variable_value (token& t, type& tt, const variable_type& var) - { - if (var.pairs != '\0') - mode (lexer_mode::pairs, var.pairs); - else - mode (lexer_mode::value); - - next (t, tt); - return (tt != type::newline && tt != type::eos - ? names (t, tt) - : names_type ()); - } - - parser::names_type parser:: - eval (token& t, type& tt) - { - mode (lexer_mode::eval); - next (t, tt); - - names_type ns (tt != type::rparen ? names (t, tt) : names_type ()); - - if (tt != type::rparen) - fail (t) << "expected ')' instead of " << t; - - return ns; - } - - // Parse names inside {} and handle the following "crosses" (i.e., - // {a b}{x y}) if any. Return the number of names added to the list. - // - size_t parser:: - names_trailer (token& t, type& tt, - names_type& ns, - size_t pair, - const string* pp, - const dir_path* dp, - const string* tp) - { - next (t, tt); // Get what's after '{'. - - size_t count (ns.size ()); - names (t, tt, - ns, - false, - (pair != 0 - ? pair - : (ns.empty () || ns.back ().pair == '\0' ? 0 : ns.size ())), - pp, dp, tp); - count = ns.size () - count; - - if (tt != type::rcbrace) - fail (t) << "expected } instead of " << t; - - // See if we have a cross. See tests/names. - // - if (peek () == type::lcbrace && !peeked ().separated) - { - next (t, tt); // Get '{'. - const location loc (get_location (t, &path_)); - - names_type x; // Parse into a separate list of names. - names_trailer (t, tt, x, 0, nullptr, nullptr, nullptr); - - if (size_t n = x.size ()) - { - // Now cross the last 'count' names in 'ns' with 'x'. First we will - // allocate n - 1 additional sets of last 'count' names in 'ns'. - // - size_t b (ns.size () - count); // Start of 'count' names. - ns.reserve (ns.size () + count * (n - 1)); - for (size_t i (0); i != n - 1; ++i) - for (size_t j (0); j != count; ++j) - ns.push_back (ns[b + j]); - - // Now cross each name, this time including the first set. - // - for (size_t i (0); i != n; ++i) - { - for (size_t j (0); j != count; ++j) - { - name& l (ns[b + i * count + j]); - const name& r (x[i]); - - // Move the project names. - // - if (r.proj != nullptr) - { - if (l.proj != nullptr) - fail (loc) << "nested project name " << *r.proj; - - l.proj = r.proj; - } - - // Merge directories. - // - if (!r.dir.empty ()) - { - if (l.dir.empty ()) - l.dir = move (r.dir); - else - l.dir /= r.dir; - } - - // Figure out the type. As a first step, "promote" the lhs value - // to type. - // - if (!l.value.empty ()) - { - if (!l.type.empty ()) - fail (loc) << "nested type name " << l.value; - - l.type.swap (l.value); - } - - if (!r.type.empty ()) - { - if (!l.type.empty ()) - fail (loc) << "nested type name " << r.type; - - l.type = move (r.type); - } - - l.value = move (r.value); - - // @@ TODO: need to handle pairs on lhs. I think all that needs - // to be done is skip pair's first elements. Maybe also check - // that there are no pairs on the rhs. There is just no easy - // way to enable the pairs mode to test it, yet. - } - } - - count *= n; - } - } - - return count; - } - - void parser:: - names (token& t, type& tt, - names_type& ns, - bool chunk, - size_t pair, - const string* pp, - const dir_path* dp, - const string* tp) - { - // If pair is not 0, then it is an index + 1 of the first half of - // the pair for which we are parsing the second halves, e.g., - // a={b c d{e f} {}}. - // - - // Buffer that is used to collect the complete name in case of - // an unseparated variable expansion or eval context, e.g., - // 'foo$bar($baz)fox'. The idea is to concatenate all the - // individual parts in this buffer and then re-inject it into - // the loop as a single token. - // - string concat; - - // Number of names in the last group. This is used to detect when - // we need to add an empty first pair element (e.g., {=y}) or when - // we have a for now unsupported multi-name LHS (e.g., {x y}=z). - // - size_t count (0); - - for (bool first (true);; first = false) - { - // If the accumulating buffer is not empty, then we have two options: - // continue accumulating or inject. We inject if the next token is - // not a name, var expansion, or eval context or if it is separated. - // - if (!concat.empty () && - ((tt != type::name && - tt != type::dollar && - tt != type::lparen) || peeked ().separated)) - { - tt = type::name; - t = token (move (concat), true, false, t.line, t.column); - concat.clear (); - } - else if (!first) - { - // If we are chunking, stop at the next separated token. Unless - // current or next token is a pair separator, since we want the - // "x = y" pair to be parsed as a single chunk. - // - bool p (t.type == type::pair_separator); // Current token. - - next (t, tt); - - if (chunk && t.separated && (tt != type::pair_separator && !p)) - break; - } - - // Name. - // - if (tt == type::name) - { - string name (t.value); //@@ move? - tt = peek (); - - // Should we accumulate? If the buffer is not empty, then - // we continue accumulating (the case where we are separated - // should have been handled by the injection code above). If - // the next token is a var expansion or eval context and it - // is not separated, then we need to start accumulating. - // - if (!concat.empty () || // Continue. - ((tt == type::dollar || - tt == type::lparen) && !peeked ().separated)) // Start. - { - concat += name; - continue; - } - - string::size_type p (name.find_last_of ("/%")); - - // First take care of project. A project-qualified name is - // not very common, so we can afford some copying for the - // sake of simplicity. - // - const string* pp1 (pp); - - if (p != string::npos) - { - bool last (name[p] == '%'); - string::size_type p1 (last ? p : name.rfind ('%', p - 1)); - - if (p1 != string::npos) - { - string proj; - proj.swap (name); - - // First fix the rest of the name. - // - name.assign (proj, p1 + 1, string::npos); - p = last ? string::npos : p - (p1 + 1); - - // Now process the project name. - // @@ Validate it. - // - proj.resize (p1); - - if (pp != nullptr) - fail (t) << "nested project name " << proj; - - pp1 = &project_name_pool.find (proj); - } - } - - string::size_type n (p != string::npos ? name.size () - 1 : 0); - - // See if this is a type name, directory prefix, or both. That - // is, it is followed by an un-separated '{'. - // - if (tt == type::lcbrace && !peeked ().separated) - { - next (t, tt); - - if (p != n && tp != nullptr) - fail (t) << "nested type name " << name; - - dir_path d1; - const dir_path* dp1 (dp); - - string t1; - const string* tp1 (tp); - - if (p == string::npos) // type - tp1 = &name; - else if (p == n) // directory - { - if (dp == nullptr) - d1 = dir_path (name); - else - d1 = *dp / dir_path (name); - - dp1 = &d1; - } - else // both - { - t1.assign (name, p + 1, n - p); - - if (dp == nullptr) - d1 = dir_path (name, 0, p + 1); - else - d1 = *dp / dir_path (name, 0, p + 1); - - dp1 = &d1; - tp1 = &t1; - } - - count = names_trailer (t, tt, ns, pair, pp1, dp1, tp1); - tt = peek (); - continue; - } - - // If we are a second half of a pair, add another first half - // unless this is the first instance. - // - if (pair != 0 && pair != ns.size ()) - ns.push_back (ns[pair - 1]); - - count = 1; - - // If it ends with a directory separator, then it is a directory. - // Note that at this stage we don't treat '.' and '..' as special - // (unless they are specified with a directory separator) because - // then we would have ended up treating '.: ...' as a directory - // scope. Instead, this is handled higher up the processing chain, - // in target_types::find(). This would also mess up reversibility - // to simple name. - // - // @@ TODO: and not quoted - // - if (p == n) - { - // For reversibility to simple name, only treat it as a directory - // if the string is an exact representation. - // - if (p != 0 && name[p - 1] != '/') // Take care of the "//" case. - name.resize (p); // Strip trailing '/'. - - dir_path dir (move (name), dir_path::exact); - - if (!dir.empty ()) - { - if (dp != nullptr) - dir = *dp / dir; - - ns.emplace_back (pp1, - move (dir), - (tp != nullptr ? *tp : string ()), - string ()); - continue; - } - - // Add the trailing slash back and treat it as a simple name. - // - if (p != 0 && name[p - 1] != '/') - name.push_back ('/'); - } - - ns.emplace_back (pp1, - (dp != nullptr ? *dp : dir_path ()), - (tp != nullptr ? *tp : string ()), - move (name)); - continue; - } - - // Variable expansion/function call or eval context. - // - if (tt == type::dollar || tt == type::lparen) - { - // These two cases are pretty similar in that in both we - // pretty quickly end up with a list of names that we need - // to splice into the result. - // - names_type lv_data; - const names_type* plv; - - location loc; - const char* what; // Variable or evaluation context. - - if (tt == type::dollar) - { - // Switch to the variable name mode. We want to use this - // mode for $foo but not for $(foo). Since we don't know - // whether the next token is a paren or a name, we turn - // it on and switch to the eval mode if what we get next - // is a paren. - // - mode (lexer_mode::variable); - next (t, tt); - loc = get_location (t, &path_); - - string n; - if (tt == type::name) - n = t.value; - else if (tt == type::lparen) - { - expire_mode (); - names_type ns (eval (t, tt)); - - // Make sure the result of evaluation is a single, simple name. - // - if (ns.size () != 1 || !ns.front ().simple ()) - fail (loc) << "variable/function name expected instead of '" - << ns << "'"; - - n = move (ns.front ().value); - } - else - fail (t) << "variable/function name expected instead of " << t; - - if (n.empty ()) - fail (loc) << "empty variable/function name"; - - // Figure out whether this is a variable expansion or a function - // call. - // - tt = peek (); - - if (tt == type::lparen) - { - next (t, tt); // Get '('. - names_type ns (eval (t, tt)); - - // Just a stub for now. - // - cout << n << "(" << ns << ")" << endl; - - tt = peek (); - - if (lv_data.empty ()) - continue; - - plv = &lv_data; - what = "function call"; - } - else - { - // Process variable name. - // - if (n.front () == '.') // Fully qualified name. - n.erase (0, 1); - else - { - //@@ TODO: append namespace if any. - } - - // Lookup. - // - const auto& var (var_pool.find (move (n))); - auto l (target_ != nullptr ? (*target_)[var] : (*scope_)[var]); - - // Undefined/NULL namespace variables are not allowed. - // - if (!l && var.name.find ('.') != string::npos) - fail (loc) << "undefined/null namespace variable " << var.name; - - if (!l || l->empty ()) - continue; - - plv = &l->data_; - what = "variable expansion"; - } - } - else - { - loc = get_location (t, &path_); - lv_data = eval (t, tt); - - tt = peek (); - - if (lv_data.empty ()) - continue; - - plv = &lv_data; - what = "context evaluation"; - } - - // @@ Could move if (lv == &lv_data). - // - const names_type& lv (*plv); - - // Should we accumulate? If the buffer is not empty, then - // we continue accumulating (the case where we are separated - // should have been handled by the injection code above). If - // the next token is a name or var expansion and it is not - // separated, then we need to start accumulating. - // - if (!concat.empty () || // Continue. - ((tt == type::name || // Start. - tt == type::dollar || - tt == type::lparen) && !peeked ().separated)) - { - // This should be a simple value or a simple directory. The - // token still points to the name (or closing paren). - // - if (lv.size () > 1) - fail (loc) << "concatenating " << what << " contains multiple " - << "values"; - - const name& n (lv[0]); - - if (n.qualified ()) - fail (loc) << "concatenating " << what << " contains project name"; - - if (n.typed ()) - fail (loc) << "concatenating " << what << " contains type"; - - if (!n.dir.empty ()) - { - if (!n.value.empty ()) - fail (loc) << "concatenating " << what << " contains directory"; - - concat += n.dir.string (); - } - else - concat += n.value; - } - else - { - // Copy the names from the variable into the resulting name list - // while doing sensible things with the types and directories. - // - for (const name& n: lv) - { - const string* pp1 (pp); - const dir_path* dp1 (dp); - const string* tp1 (tp); - - if (n.proj != 0) - { - if (pp == nullptr) - pp1 = n.proj; - else - fail (loc) << "nested project name " << *n.proj << " in " - << what; - } - - dir_path d1; - if (!n.dir.empty ()) - { - if (dp != nullptr) - { - if (n.dir.absolute ()) - fail (loc) << "nested absolute directory " << n.dir - << " in " << what; - - d1 = *dp / n.dir; - dp1 = &d1; - } - else - dp1 = &n.dir; - } - - if (!n.type.empty ()) - { - if (tp == nullptr) - tp1 = &n.type; - else - fail (loc) << "nested type name " << n.type << " in " << what; - } - - // If we are a second half of a pair. - // - if (pair != 0) - { - // Check that there are no nested pairs. - // - if (n.pair != '\0') - fail (loc) << "nested pair in " << what; - - // And add another first half unless this is the first instance. - // - if (pair != ns.size ()) - ns.push_back (ns[pair - 1]); - } - - ns.emplace_back (pp1, - (dp1 != nullptr ? *dp1 : dir_path ()), - (tp1 != nullptr ? *tp1 : string ()), - n.value); - - ns.back ().pair = n.pair; - } - - count = lv.size (); - } - - continue; - } - - // Untyped name group without a directory prefix, e.g., '{foo bar}'. - // - if (tt == type::lcbrace) - { - count = names_trailer (t, tt, ns, pair, pp, dp, tp); - tt = peek (); - continue; - } - - // A pair separator (only in the pairs mode). - // - if (tt == type::pair_separator) - { - if (pair != 0) - fail (t) << "nested pair on the right hand side of a pair"; - - if (count > 1) - fail (t) << "multiple names on the left hand side of a pair"; - - if (count == 0) - { - // Empty LHS, (e.g., {=y}), create an empty name. - // - ns.emplace_back (pp, - (dp != nullptr ? *dp : dir_path ()), - (tp != nullptr ? *tp : string ()), - string ()); - count = 1; - } - - ns.back ().pair = t.pair; - tt = peek (); - continue; - } - - if (!first) - break; - - if (tt == type::rcbrace) // Empty name, e.g., dir{}. - { - // If we are a second half of a pair, add another first half - // unless this is the first instance. - // - if (pair != 0 && pair != ns.size ()) - ns.push_back (ns[pair - 1]); - - ns.emplace_back (pp, - (dp != nullptr ? *dp : dir_path ()), - (tp != nullptr ? *tp : string ()), - string ()); - break; - } - else - // Our caller expected this to be a name. - // - fail (t) << "expected name instead of " << t; - } - - // Handle the empty RHS in a pair, (e.g., {y=}). - // - if (!ns.empty () && ns.back ().pair != '\0') - { - ns.emplace_back (pp, - (dp != nullptr ? *dp : dir_path ()), - (tp != nullptr ? *tp : string ()), - string ()); - } - } - - void parser:: - skip_line (token& t, type& tt) - { - for (; tt != type::newline && tt != type::eos; next (t, tt)) ; - } - - void parser:: - skip_block (token& t, type& tt) - { - // Skip until } or eos, keeping track of the {}-balance. - // - for (size_t b (0); tt != type::eos; ) - { - if (tt == type::lcbrace || tt == type::rcbrace) - { - type ptt (peek ()); - if (ptt == type::newline || ptt == type::eos) // Block { or }. - { - if (tt == type::lcbrace) - ++b; - else - { - if (b == 0) - break; - - --b; - } - } - } - - skip_line (t, tt); - - if (tt != type::eos) - next (t, tt); - } - } - - bool parser:: - keyword (token& t) - { - assert (replay_ == replay::stop); // Can't be used in a replay. - assert (t.type == type::name); - - // The goal here is to allow using keywords as variable names and - // target types without imposing ugly restrictions/decorators on - // keywords (e.g., '.using' or 'USING'). A name is considered a - // potential keyword if: - // - // - it is not quoted [so a keyword can always be escaped] and - // - next token is '\n' (or eos) or '(' [so if(...) will work] or - // - next token is separated and is not '=', '=+', or '+=' [which - // means a "directive trailer" can never start with one of them]. - // - // See tests/keyword. - // - if (!t.quoted) - { - // We cannot peek at the whole token here since it might have to be - // lexed in a different mode. So peek at its first character. - // - pair<char, bool> p (lexer_->peek_char ()); - char c (p.first); - - return c == '\n' || c == '\0' || c == '(' || - (p.second && c != '=' && c != '+'); - } - - return false; - } - - // Buildspec parsing. - // - - // Here is the problem: we "overload" '(' and ')' to mean operation - // application rather than the eval context. At the same time we want - // to use names() to parse names, get variable expansion/function calls, - // quoting, etc. We just need to disable the eval context. The way this - // is done has two parts: Firstly, we parse names in chunks and detect - // and handle the opening paren. In other words, a buildspec like - // 'clean (./)' is "chunked" as 'clean', '(', etc. While this is fairly - // straightforward, there is one snag: concatenating eval contexts, as - // in 'clean(./)'. Normally, this will be treated as a single chunk and - // we don't want that. So here comes the trick (or hack, if you like): - // we will make every opening paren token "separated" (i.e., as if it - // was proceeded by a space). This will disable concatenating eval. In - // fact, we will even go a step further and only do this if we are in - // the original pairs mode. This will allow us to still use eval - // contexts in buildspec, provided that we quote it: '"cle(an)"'. Note - // also that function calls still work as usual: '$filter (clean test)'. - // To disable a function call and make it instead a var that is expanded - // into operation name(s), we can use quoting: '"$ops"(./)'. - // - static void - paren_processor (token& t, const lexer& l) - { - if (t.type == type::lparen && l.mode () == lexer_mode::pairs) - t.separated = true; - } - - buildspec parser:: - parse_buildspec (istream& is, const std::string& name) - { - path_ = &name; // Note: caller pools. - - lexer l (is, name, &paren_processor); - lexer_ = &l; - target_ = nullptr; - scope_ = root_ = global_scope; - - // Turn on pairs recognition with '@' as the pair separator (e.g., - // src_root/@out_root/exe{foo bar}). - // - mode (lexer_mode::pairs, '@'); - - token t (type::eos, false, 0, 0); - type tt; - next (t, tt); - - return buildspec_clause (t, tt, type::eos); - } - - static bool - opname (const name& n) - { - // First it has to be a non-empty simple name. - // - if (n.pair != '\0' || !n.simple () || n.empty ()) - return false; - - // C identifier. - // - for (size_t i (0); i != n.value.size (); ++i) - { - char c (n.value[i]); - if (c != '_' && !(i != 0 ? isalnum (c) : isalpha (c))) - return false; - } - - return true; - } - - buildspec parser:: - buildspec_clause (token& t, type& tt, type tt_end) - { - buildspec bs; - - while (tt != tt_end) - { - // We always start with one or more names. Eval context - // (lparen) only allowed if quoted. - // - if (tt != type::name && - tt != type::lcbrace && // Untyped name group: '{foo ...' - tt != type::dollar && // Variable expansion: '$foo ...' - !(tt == type::lparen && mode () == lexer_mode::quoted) && - tt != type::pair_separator) // Empty pair LHS: '@foo ...' - fail (t) << "operation or target expected instead of " << t; - - const location l (get_location (t, &path_)); // Start of names. - - // This call will parse the next chunk of output and produce - // zero or more names. - // - names_type ns (names (t, tt, true)); - - // What these names mean depends on what's next. If it is an - // opening paren, then they are operation/meta-operation names. - // Otherwise they are targets. - // - if (tt == type::lparen) // Peeked into by names(). - { - if (ns.empty ()) - fail (t) << "operation name expected before '('"; - - for (const name& n: ns) - if (!opname (n)) - fail (l) << "operation name expected instead of '" << n << "'"; - - // Inside '(' and ')' we have another, nested, buildspec. - // - next (t, tt); - const location l (get_location (t, &path_)); // Start of nested names. - buildspec nbs (buildspec_clause (t, tt, type::rparen)); - - // Merge the nested buildspec into ours. But first determine - // if we are an operation or meta-operation and do some sanity - // checks. - // - bool meta (false); - for (const metaopspec& nms: nbs) - { - // We definitely shouldn't have any meta-operations. - // - if (!nms.name.empty ()) - fail (l) << "nested meta-operation " << nms.name; - - if (!meta) - { - // If we have any operations in the nested spec, then this - // mean that our names are meta-operation names. - // - for (const opspec& nos: nms) - { - if (!nos.name.empty ()) - { - meta = true; - break; - } - } - } - } - - // No nested meta-operations means we should have a single - // metaopspec object with empty meta-operation name. - // - assert (nbs.size () == 1); - const metaopspec& nmo (nbs.back ()); - - if (meta) - { - for (name& n: ns) - { - bs.push_back (nmo); - bs.back ().name = move (n.value); - } - } - else - { - // Since we are not a meta-operation, the nested buildspec - // should be just a bunch of targets. - // - assert (nmo.size () == 1); - const opspec& nos (nmo.back ()); - - if (bs.empty () || !bs.back ().name.empty ()) - bs.push_back (metaopspec ()); // Empty (default) meta operation. - - for (name& n: ns) - { - bs.back ().push_back (nos); - bs.back ().back ().name = move (n.value); - } - } - - next (t, tt); // Done with '('. - } - else if (!ns.empty ()) - { - // Group all the targets into a single operation. In other - // words, 'foo bar' is equivalent to 'update(foo bar)'. - // - if (bs.empty () || !bs.back ().name.empty ()) - bs.push_back (metaopspec ()); // Empty (default) meta operation. - - metaopspec& ms (bs.back ()); - - for (auto i (ns.begin ()), e (ns.end ()); i != e; ++i) - { - // @@ We may actually want to support this at some point. - // - if (i->qualified ()) - fail (l) << "target name expected instead of " << *i; - - if (opname (*i)) - ms.push_back (opspec (move (i->value))); - else - { - // Do we have the src_base? - // - dir_path src_base; - if (i->pair != '\0') - { - if (i->typed ()) - fail (l) << "expected target src_base instead of " << *i; - - src_base = move (i->dir); - - if (!i->value.empty ()) - src_base /= dir_path (move (i->value)); - - ++i; - assert (i != e); // Got to have the second half of the pair. - } - - if (ms.empty () || !ms.back ().name.empty ()) - ms.push_back (opspec ()); // Empty (default) operation. - - opspec& os (ms.back ()); - os.emplace_back (move (src_base), move (*i)); - } - } - } - } - - return bs; - } - - void parser:: - switch_scope (const dir_path& p) - { - tracer trace ("parser::switch_scope", &path_); - - // First, enter the scope into the map and see if it is in any - // project. If it is not, then there is nothing else to do. - // - auto i (scopes.insert (p, nullptr, true, false)); - scope_ = i->second; - scope* rs (scope_->root_scope ()); - - if (rs == nullptr) - return; - - // Path p can be src_base or out_base. Figure out which one it is. - // - dir_path out_base (p.sub (rs->out_path ()) ? p : src_out (p, *rs)); - - // Create and bootstrap root scope(s) of subproject(s) that this - // scope may belong to. If any were created, load them. Note that - // we need to do this before figuring out src_base since we may - // switch the root project (and src_root with it). - // - { - scope* nrs (&create_bootstrap_inner (*rs, out_base)); - - if (rs != nrs) - { - load_root_pre (*nrs); // Load outer roots recursively. - rs = nrs; - } - } - - // Switch to the new root scope. - // - if (rs != root_) - { - level5 ([&]{trace << "switching to root scope " << rs->out_path ();}); - root_ = rs; - } - - // Now we can figure out src_base and finish setting the scope. - // - dir_path src_base (src_out (out_base, *rs)); - setup_base (i, move (out_base), move (src_base)); - } - - void parser:: - process_default_target (token& t) - { - tracer trace ("parser::process_default_target", &path_); - - // The logic is as follows: if we have an explicit current directory - // target, then that's the default target. Otherwise, we take the - // first target and use it as a prerequisite to create an implicit - // current directory target, effectively making it the default - // target via an alias. If there are no targets in this buildfile, - // then we don't do anything. - // - if (default_target_ == nullptr || // No targets in this buildfile. - targets.find (dir::static_type, // Explicit current dir target. - scope_->out_path (), - "", - nullptr, - trace) != targets.end ()) - return; - - target& dt (*default_target_); - - level5 ([&]{trace (t) << "creating current directory alias for " << dt;}); - - target& ct ( - targets.insert ( - dir::static_type, scope_->out_path (), "", nullptr, trace).first); - - prerequisite& p ( - scope_->prerequisites.insert ( - nullptr, - dt.type (), - dt.dir, - dt.name, - dt.ext, - *scope_, // Doesn't matter which scope since dir is absolute. - trace).first); - - p.target = &dt; - ct.prerequisites.emplace_back (p); - } - - void parser:: - enter_buildfile (const path& p) - { - tracer trace ("parser::enter_buildfile", &path_); - - const char* e (p.extension ()); - targets.insert<buildfile> ( - p.directory (), - p.leaf ().base ().string (), - &extension_pool.find (e == nullptr ? "" : e), // Always specified. - trace); - } - - type parser:: - next (token& t, type& tt) - { - if (peeked_) - { - t = move (peek_); - peeked_ = false; - } - else - t = (replay_ == replay::play ? replay_next () : lexer_->next ()); - - if (replay_ == replay::save) - replay_data_.push_back (t); - - tt = t.type; - return tt; - } - - type parser:: - peek () - { - if (!peeked_) - { - peek_ = (replay_ == replay::play ? replay_next () : lexer_->next ()); - peeked_ = true; - } - - return peek_.type; - } - - static location - get_location (const token& t, const void* data) - { - assert (data != nullptr); - const string& p (**static_cast<const string* const*> (data)); - return location (p.c_str (), t.line, t.column); - } -} diff --git a/build/path-io b/build/path-io deleted file mode 100644 index 888a8b4..0000000 --- a/build/path-io +++ /dev/null @@ -1,26 +0,0 @@ -// file : build/path-io -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD_PATH_IO -#define BUILD_PATH_IO - -#include <iosfwd> - -#include <butl/path> - -// Custom path IO. -// -namespace build -{ - using butl::path; - using butl::dir_path; - - std::ostream& - operator<< (std::ostream&, const path&); - - std::ostream& - operator<< (std::ostream&, const dir_path&); -} - -#endif // BUILD_PATH_IO diff --git a/build/path-io.cxx b/build/path-io.cxx deleted file mode 100644 index b6fd9e7..0000000 --- a/build/path-io.cxx +++ /dev/null @@ -1,39 +0,0 @@ -// file : build/path-io.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <build/path-io> - -#include <ostream> - -#include <build/context> -#include <build/diagnostics> - -using namespace std; - -namespace build -{ - 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/build/prerequisite b/build/prerequisite deleted file mode 100644 index c435fd3..0000000 --- a/build/prerequisite +++ /dev/null @@ -1,129 +0,0 @@ -// file : build/prerequisite -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD_PREREQUISITE -#define BUILD_PREREQUISITE - -#include <set> -#include <string> -#include <iosfwd> -#include <utility> // move -#include <cassert> -#include <functional> // reference_wrapper - -#include <build/types> -#include <build/target-key> -#include <build/utility> // extension_pool -#include <build/diagnostics> - -namespace build -{ - class scope; - class target; - - // Light-weight (by being shallow-pointing) prerequisite key, similar - // to (and based on) target key. - // - class prerequisite_key - { - public: - typedef build::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 build::target target_type; - typedef build::target_type target_type_type; - typedef build::scope scope_type; - - prerequisite (const std::string* p, - const target_type_type& t, - dir_path d, - std::string n, - const std::string* e, - scope_type& s) - : proj (p), - type (t), - dir (std::move (d)), - name (std::move (n)), - ext (e), - scope (s), - target (nullptr) {} - - public: - const std::string* proj; // NULL if not project-qualified. - const target_type_type& type; - const dir_path dir; // Normalized absolute or relative (to scope). - const std::string name; - const std::string* ext; // NULL if unspecified. - scope_type& scope; - target_type* target; // NULL if not yet resolved. Note that this should - // always be the "primary target", not a member of - // a target group. - prerequisite_key - key () const - { - return prerequisite_key {proj, {&type, &dir, &name, &ext}, &scope}; - } - - public: - // Prerequisite (target) type. - // - template <typename T> - bool - is_a () const {return type.is_a<T> ();} - }; - - inline bool - operator< (const prerequisite& x, const prerequisite& y) - { - return x.key () < y.key (); - } - - inline std::ostream& - operator<< (std::ostream& os, const prerequisite& p) - { - return os << p.key (); - } - - // Set of prerequisites in a scope. - // - struct prerequisite_set: std::set<prerequisite> - { - std::pair<prerequisite&, bool> - insert (const std::string* proj, - const target_type&, - dir_path dir, - std::string name, - const std::string* ext, - scope&, - tracer&); - - std::pair<prerequisite&, bool> - insert (const std::string* proj, const target_key& tk, scope& s, tracer& t) - { - return insert (proj, *tk.type, *tk.dir, *tk.name, *tk.ext, s, t); - } - }; -} - -#endif // BUILD_PREREQUISITE diff --git a/build/prerequisite.cxx b/build/prerequisite.cxx deleted file mode 100644 index 49cab89..0000000 --- a/build/prerequisite.cxx +++ /dev/null @@ -1,82 +0,0 @@ -// file : build/prerequisite.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <build/prerequisite> - -#include <ostream> - -#include <build/scope> -#include <build/target> // target_type -#include <build/context> -#include <build/diagnostics> - -using namespace std; - -namespace build -{ - // prerequisite_key - // - ostream& - operator<< (ostream& os, const prerequisite_key& pk) - { - if (pk.proj != nullptr) - os << *pk.proj << '%'; - - // Don't print scope if we are project-qualified or the - // prerequisite's directory is absolute. In both these - // cases the scope is not used to resolve it to target. - // - else if (!pk.tk.dir->absolute ()) - { - string s (diag_relative (pk.scope->out_path (), false)); - - if (!s.empty ()) - os << s << ':'; - } - - return os << pk.tk; - } - - // prerequisite_set - // - auto prerequisite_set:: - insert (const std::string* proj, - const target_type& tt, - dir_path dir, - std::string name, - const std::string* ext, - scope& s, - tracer& trace) -> pair<prerequisite&, bool> - { - //@@ OPT: would be nice to somehow first check if this prerequisite is - // already in the set before allocating a new instance. - - // Find or insert. - // - auto r (emplace (proj, tt, move (dir), move (name), ext, s)); - prerequisite& p (const_cast<prerequisite&> (*r.first)); - - // Update extension if the existing prerequisite has it unspecified. - // - if (p.ext != ext) - { - level5 ([&]{ - diag_record r (trace); - r << "assuming prerequisite " << p << " is the same as the " - << "one with "; - if (ext == nullptr) - r << "unspecified extension"; - else if (ext->empty ()) - r << "no extension"; - else - r << "extension " << *ext; - }); - - if (ext != nullptr) - p.ext = ext; - } - - return pair<prerequisite&, bool> (p, r.second); - } -} diff --git a/build/rule b/build/rule deleted file mode 100644 index af1bcc8..0000000 --- a/build/rule +++ /dev/null @@ -1,135 +0,0 @@ -// file : build/rule -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD_RULE -#define BUILD_RULE - -#include <string> -#include <cstddef> // nullptr_t - -#include <build/types> -#include <build/target> -#include <build/operation> - -namespace build -{ - class match_result - { - public: - typedef build::target target_type; - typedef build::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 build::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 // BUILD_RULE diff --git a/build/rule-map b/build/rule-map deleted file mode 100644 index 74d1cfc..0000000 --- a/build/rule-map +++ /dev/null @@ -1,115 +0,0 @@ -// file : build/rule-map -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD_RULE_MAP -#define BUILD_RULE_MAP - -#include <map> -#include <vector> -#include <string> -#include <memory> // unique_ptr -#include <functional> // reference_wrapper - -#include <butl/prefix-map> - -#include <build/types> -#include <build/operation> - -namespace build -{ - class rule; - - using target_type_rule_map = std::map< - const target_type*, - butl::prefix_map<std::string, // Rule hint. - std::reference_wrapper<rule>, '.'>>; - - // This is an "indexed map" with operation_id being the index. Entry - // with id 0 is a wildcard. - // - class operation_rule_map - { - public: - template <typename T> - void - insert (operation_id oid, const char* hint, rule& r) - { - // 3 is the number of builtin operations. - // - if (oid >= map_.size ()) - map_.resize ((oid < 3 ? 3 : oid) + 1); - - map_[oid][&T::static_type].emplace (hint, r); - } - - // Return NULL if not found. - // - const target_type_rule_map* - operator[] (operation_id oid) const - { - return map_.size () > oid ? &map_[oid] : nullptr; - } - - bool - empty () const {return map_.empty ();} - - private: - std::vector<target_type_rule_map> map_; - }; - - // This is another indexed map but this time meta_operation_id is the - // index. The implementation is different, however: here we use a linked - // list with the first, statically-allocated node corresponding to the - // perform meta-operation. The idea is to try and get away with a dynamic - // allocation for the common cases since most rules will be registered - // for perform, at least on non-root scopes. - // - class rule_map - { - public: - - template <typename T> - void - insert (action_id a, const char* hint, rule& r) - { - insert<T> (a >> 4, a & 0x0F, hint, r); - } - - template <typename T> - void - insert (meta_operation_id mid, operation_id oid, const char* hint, rule& r) - { - if (mid_ == mid) - map_.insert<T> (oid, hint, r); - else - { - if (next_ == nullptr) - next_.reset (new rule_map (mid)); - - next_->insert<T> (mid, oid, hint, r); - } - } - - // Return NULL if not found. - // - const operation_rule_map* - operator[] (meta_operation_id mid) const - { - return mid == mid_ ? &map_ : next_ == nullptr ? nullptr : (*next_)[mid]; - } - - explicit - rule_map (meta_operation_id mid = perform_id): mid_ (mid) {} - - bool - empty () const {return map_.empty () && next_ == nullptr;} - - private: - meta_operation_id mid_; - operation_rule_map map_; - std::unique_ptr<rule_map> next_; - }; -} - -#endif // BUILD_RULE_MAP diff --git a/build/rule.cxx b/build/rule.cxx deleted file mode 100644 index cce291c..0000000 --- a/build/rule.cxx +++ /dev/null @@ -1,249 +0,0 @@ -// file : build/rule.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <build/rule> - -#include <utility> // move() -#include <system_error> - -#include <butl/filesystem> - -#include <build/scope> -#include <build/target> -#include <build/algorithm> -#include <build/diagnostics> -#include <build/context> - -using namespace std; -using namespace butl; - -namespace build -{ - // file_rule - // - // Note that this rule is special. It is the last, fallback rule. If - // it doesn't match, then no other rule can possibly match and we have - // an error. It also cannot be ambigious with any other rule. As a - // result the below implementation bends or ignores quite a few rules - // that normal implementations should follow. So you probably shouldn't - // use it as a guide to implement your own, normal, rules. - // - match_result file_rule:: - match (action a, target& t, const string&) const - { - tracer trace ("file_rule::match"); - - // While strictly speaking we should check for the file's existence - // for every action (because that's the condition for us matching), - // for some actions this is clearly a waste. Say, perform_clean: we - // are not doing anything for this action so not checking if the file - // exists seems harmless. So the overall guideline seems to be this: - // if we don't do anything for the action (other than performing it - // on the prerequisites), then we match. - // - switch (a) - { - case perform_update_id: - { - path_target& pt (dynamic_cast<path_target&> (t)); - - // Assign the path. While normally we shouldn't do this in match(), - // no other rule should ever be ambiguous with the fallback one. - // - if (pt.path ().empty ()) - pt.derive_path (); - - // We cannot just call pt.mtime() since we haven't matched yet. - // - timestamp ts (file_mtime (pt.path ())); - pt.mtime (ts); - - if (ts != timestamp_nonexistent) - return t; - - level4 ([&]{trace << "no existing file for target " << t;}); - return nullptr; - } - default: - return t; - } - } - - recipe file_rule:: - apply (action a, target& t, const match_result&) const - { - // Update triggers the update of this target's prerequisites - // so it would seem natural that we should also trigger their - // cleanup. However, this possibility is rather theoretical - // since such an update would render this target out of date - // which in turn would lead to an error. So until we see a - // real use-case for this functionality, we simply ignore - // the clean operation. - // - if (a.operation () == clean_id) - return noop_recipe; - - // If we have no prerequisites, then this means this file - // is up to date. Return noop_recipe which will also cause - // the target's state to be set to unchanged. This is an - // important optimization on which quite a few places that - // deal with predominantly static content rely. - // - if (!t.has_prerequisites ()) - return noop_recipe; - - // Search and match all the prerequisites. - // - search_and_match_prerequisites (a, t); - return a == perform_update_id ? &perform_update : default_recipe; - } - - target_state file_rule:: - perform_update (action a, target& t) - { - // Make sure the target is not older than any of its prerequisites. - // - timestamp mt (dynamic_cast<path_target&> (t).mtime ()); - - for (target* pt: t.prerequisite_targets) - { - target_state ts (execute (a, *pt)); - - // If this is an mtime-based target, then compare timestamps. - // - if (auto mpt = dynamic_cast<const mtime_target*> (pt)) - { - timestamp mp (mpt->mtime ()); - - if (mt < mp) - fail << "no recipe to " << diag_do (a, t) << - info << "prerequisite " << *pt << " is ahead of " << t - << " by " << (mp - mt); - } - else - { - // Otherwise we assume the prerequisite is newer if it was changed. - // - if (ts == target_state::changed) - fail << "no recipe to " << diag_do (a, t) << - info << "prerequisite " << *pt << " is ahead of " << t - << " because it was updated"; - } - } - - return target_state::unchanged; - } - - file_rule file_rule::instance; - - // alias_rule - // - match_result alias_rule:: - match (action, target& t, const string&) const - { - return t; - } - - recipe alias_rule:: - apply (action a, target& t, const match_result&) const - { - search_and_match_prerequisites (a, t); - return default_recipe; - } - - alias_rule alias_rule::instance; - - // fsdir_rule - // - match_result fsdir_rule:: - match (action, target& t, const string&) const - { - return t; - } - - recipe fsdir_rule:: - apply (action a, target& t, const match_result&) const - { - // Inject dependency on the parent directory. Note that we - // don't do it for clean since we shouldn't be removing it. - // - if (a.operation () != clean_id) - inject_parent_fsdir (a, t); - - search_and_match_prerequisites (a, t); - - switch (a) - { - case perform_update_id: return &perform_update; - case perform_clean_id: return &perform_clean; - default: assert (false); return default_recipe; - } - } - - target_state fsdir_rule:: - perform_update (action a, target& t) - { - target_state ts (target_state::unchanged); - - // First update prerequisites (e.g. create parent directories) - // then create this directory. - // - if (!t.prerequisite_targets.empty ()) - ts = execute_prerequisites (a, t); - - const dir_path& d (t.dir); // Everything is in t.dir. - - // Generally, it is probably correct to assume that in the majority - // of cases the directory will already exist. If so, then we are - // going to get better performance by first checking if it indeed - // exists. See try_mkdir() for details. - // - if (!dir_exists (d)) - { - if (verb >= 2) - text << "mkdir " << d; - else if (verb) - text << "mkdir " << t; - - try - { - try_mkdir (d); - } - catch (const system_error& e) - { - fail << "unable to create directory " << d << ": " << e.what (); - } - - ts |= target_state::changed; - } - - return ts; - } - - target_state fsdir_rule:: - perform_clean (action a, target& t) - { - // The reverse order of update: first delete this directory, - // then clean prerequisites (e.g., delete parent directories). - // - // Don't fail if we couldn't remove the directory because it - // is not empty (or is current working directory). In this - // case rmdir() will issue a warning when appropriate. - // - target_state ts (rmdir (t.dir, t) - ? target_state::changed - : target_state::unchanged); - - if (!t.prerequisite_targets.empty ()) - ts |= reverse_execute_prerequisites (a, t); - - return ts; - } - - fsdir_rule fsdir_rule::instance; - - // fallback_rule - // - fallback_rule fallback_rule::instance; -} diff --git a/build/scope b/build/scope deleted file mode 100644 index 0cb319f..0000000 --- a/build/scope +++ /dev/null @@ -1,312 +0,0 @@ -// file : build/scope -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD_SCOPE -#define BUILD_SCOPE - -#include <functional> // function -#include <unordered_set> -#include <unordered_map> - -#include <butl/path-map> - -#include <build/types> -#include <build/utility> - -#include <build/module> -#include <build/variable> -#include <build/prerequisite> -#include <build/target-type> -#include <build/rule-map> -#include <build/operation> - -namespace build -{ - 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. - // - build::lookup<const value> - operator[] (const variable& var) const - { - return lookup (nullptr, nullptr, var); - } - - build::lookup<const value> - operator[] (const std::string& name) const - { - return operator[] (var_pool.find (name)); - } - - // As above, but includes target type/pattern-specific variables. - // - build::lookup<const value> - lookup (const target_key& tk, const variable& var) const - { - return lookup (tk.type, tk.name, var); - } - - build::lookup<const value> - lookup (const target_key& tk, const string& var) const - { - return lookup (tk, var_pool.find (var)); - } - - build::lookup<const value> - lookup (const target_type& tt, - const string& name, - const variable& var) const - { - return lookup (&tt, &name, var); - } - - build::lookup<const value> - lookup (const target_type& tt, const string& name, const string& var) const - { - return lookup (tt, name, var_pool.find (var)); - } - - build::lookup<const value> - lookup (const target_type*, const string* name, const variable&) const; - - // Return a value suitable for assignment (or append if you only - // want to append to the value from this scope). If the variable - // does not exist in this scope's map, then a new one with the - // NULL value is added and returned. Otherwise the existing value - // is returned. - // - value& - assign (const variable& var) {return vars.assign (var).first.get ();} - - value& - assign (const std::string& name) {return vars.assign (name).first.get ();} - - // Return a value suitable for appending. If the variable does not - // exist in this scope's map, then outer scopes are searched for - // the same variable. If found then a new variable with the found - // value is added to this scope and returned. Otherwise this - // function proceeds as assign(). - // - value& - append (const variable&); - - value& - append (const std::string& name) - { - return append (var_pool.find (name)); - } - - // Target type/pattern-specific variables. - // - variable_type_map target_vars; - - // Prerequisite cache. - // - public: - prerequisite_set prerequisites; - - // Meta/operations supported by this project (set on the root - // scope only). - // - build::meta_operations meta_operations; - build::operations operations; - - typedef build::path path_type; - - // Set of buildfiles already loaded for this scope. The included - // buildfiles are checked against the project's root scope while - // imported -- against the global scope (global_scope). - // - std::unordered_set<path_type> buildfiles; - - // Target types. - // - public: - target_type_map target_types; - - const target_type* - find_target_type (const string&, const scope** = nullptr) const; - - // Given a name, figure out its type, taking into account extensions, - // special names (e.g., '.' and '..'), or anything else that might be - // relevant. Also process the name (in place) by extracting the - // extension, adjusting dir/value, etc., (note that the dir is not - // necessarily normalized). Return NULL if not found. - // - const target_type* - find_target_type (name&, const string*& ext) const; - - // Rules. - // - public: - rule_map rules; - - // Modules. - // - public: - loaded_module_map modules; // Only on root scope. - - public: - bool - empty () const - { - return - vars.empty () && - target_vars.empty () && - prerequisites.empty () && - meta_operations.empty () && - operations.empty () && - buildfiles.empty () && - target_types.empty () && - rules.empty () && - modules.empty (); - } - - private: - friend class scope_map; - friend class temp_scope; - - // These two from <build/file> set strong_. - // - friend void create_bootstrap_outer (scope&); - friend scope& create_bootstrap_inner (scope&, const dir_path&); - - scope () = default; - - scope* parent_; - scope* root_; - scope* strong_ = nullptr; // Only set on root sopes. - // NULL means no strong amalgamtion. - }; - - // Temporary scope. The idea is to be able to create a temporary - // scope in order not to change the variables in the current scope. - // Such a scope is not entered in to the scope map. As a result it - // can only be used as a temporary set of variables. In particular, - // defining targets/prerequisites directly in such a scope will surely - // end up badly. Defining any nested scopes will be as if defining - // such a scope in the parent (since path() returns parent's path). - // - class temp_scope: public scope - { - public: - temp_scope (scope& p) - { - out_path_ = p.out_path_; - src_path_ = p.src_path_; - parent_ = &p; - root_ = p.root_; - // No need to copy strong_ since we are never root scope. - } - }; - - class scope_map - { - public: - using map_type = butl::dir_path_map<scope*>; - using iterator = map_type::iterator; - using const_iterator = map_type::const_iterator; - - // Note that we assume the first insertion into the map is that - // of the global scope. If the passed scope pointer is not NULL, - // then insert this scope instead of a new one. - // - iterator - insert (const dir_path&, scope*, bool parent, bool root); - - // Find the most qualified scope that encompasses this path. - // - scope& - find (const dir_path&) const; - - scope& - find (const path& p) const - { - // Natural thing to do here would be to call find (p.directory ()). - // However, there could be a situation where the passed path is a - // directory (i.e., the calling code does not know what it is dealing - // with), so let's use the whole path. - // - return find (dir_path (p.string ())); - } - - const_iterator begin () const {return map_.begin ();} - const_iterator end () const {return map_.end ();} - - void - clear (); - - ~scope_map () {clear ();} - - private: - map_type map_; - }; - - extern scope_map scopes; - extern scope* global_scope; -} - -#endif // BUILD_SCOPE diff --git a/build/scope.cxx b/build/scope.cxx deleted file mode 100644 index dfddf20..0000000 --- a/build/scope.cxx +++ /dev/null @@ -1,317 +0,0 @@ -// file : build/scope.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <build/scope> - -#include <build/target> - -using namespace std; - -namespace build -{ - // scope - // - lookup<const value> scope:: - lookup (const target_type* tt, const string* name, const variable& var) const - { - using result = build::lookup<const value>; - - for (const scope* s (this); s != nullptr; ) - { - if (tt != nullptr && !s->target_vars.empty ()) - { - if (auto l = s->target_vars.lookup (*tt, *name, var)) - return l; - } - - if (auto r = s->vars.find (var)) - return result (r, &s->vars); - - switch (var.visibility) - { - case variable_visibility::scope: - s = nullptr; - break; - case variable_visibility::project: - s = s->root () ? nullptr : s->parent_scope (); - break; - case variable_visibility::normal: - s = s->parent_scope (); - break; - } - } - - return result (); - } - - value& scope:: - append (const variable& var) - { - auto l (operator[] (var)); - - if (l && l.belongs (*this)) // Existing variable in this scope. - return const_cast<value&> (*l); - - value& r (assign (var)); - - if (l) - r = *l; // Copy value from the outer scope. - - return r; - } - - const target_type* scope:: - find_target_type (const string& tt, const scope** rs) const - { - // Search scopes outwards, stopping at the project root. - // - for (const scope* s (this); - s != nullptr; - s = s->root () ? global_scope : s->parent_scope ()) - { - if (s->target_types.empty ()) - continue; - - auto i (s->target_types.find (tt)); - - if (i != s->target_types.end ()) - { - if (rs != nullptr) - *rs = s; - - return &i->second.get (); - } - } - - return nullptr; - } - - static const string dir_tt ("dir"); - static const string file_tt ("file"); - - const target_type* scope:: - find_target_type (name& n, const string*& ext) const - { - ext = nullptr; - - string& v (n.value); - - // First determine the target type. - // - const string* tt; - if (n.untyped ()) - { - // Empty name or '.' and '..' signify a directory. - // - if (v.empty () || v == "." || v == "..") - tt = &dir_tt; - else - //@@ TODO: derive type from extension. - // - tt = &file_tt; - } - else - tt = &n.type; - - const target_type* r (find_target_type (*tt)); - - if (r == nullptr) - return r; - - // Directories require special name processing. If we find that more - // targets deviate, then we should make this target-type-specific. - // - if (r->is_a<dir> () || r->is_a<fsdir> ()) - { - // The canonical representation of a directory name is with empty - // value. - // - if (!v.empty ()) - { - n.dir /= dir_path (v); // Move name value to dir. - v.clear (); - } - } - else - { - // Split the path into its directory part (if any) the name part, - // and the extension (if any). We cannot assume the name part is - // a valid filesystem name so we will have to do the splitting - // manually. - // - path::size_type i (path::traits::rfind_separator (v)); - - if (i != string::npos) - { - n.dir /= dir_path (v, i != 0 ? i : 1); // Special case: "/". - v = string (v, i + 1, string::npos); - } - - // Extract the extension. - // - string::size_type j (path::traits::find_extension (v)); - - if (j != string::npos) - { - ext = &extension_pool.find (v.c_str () + j + 1); - v.resize (j); - } - } - - return r; - } - - // scope_map - // - scope_map scopes; - scope* global_scope; - - auto scope_map:: - insert (const dir_path& k, scope* ns, bool parent, bool root) -> iterator - { - auto er (map_.emplace (k, nullptr)); - scope*& ps (er.first->second); - - if (er.second) - ps = ns == nullptr ? new scope : ns; - else if (ns != nullptr && ps != ns) - { - assert (ps->out_path_ == nullptr || ps->src_path_ == nullptr); - - if (!ps->empty ()) - fail << "attempt to replace non-empty scope " << k; - - // Un-parent ourselves. We will becomes a new parent below, - // if requested by the caller. - // - auto r (map_.find_prefix (k)); // The first entry is ourselves. - for (++r.first; r.first != r.second; ++r.first) - { - scope& c (*r.first->second); - - if (c.parent_ == ps) // No intermediate parent. - c.parent_ = ps->parent_; - } - - delete ps; - ps = ns; - er.second = true; - } - - scope& s (*ps); - - if (parent) - { - if (er.second) - { - scope* p (nullptr); - - // Update scopes of which we are a new parent/root (unless this - // is the global scope). Also find our parent while at it. - // - if (map_.size () > 1) - { - // The first entry is ourselves. - // - auto r (map_.find_prefix (k)); - for (++r.first; r.first != r.second; ++r.first) - { - scope& c (*r.first->second); - - // The child-parent relationship is based on the out hierarchy, - // thus the extra check. - // - if (c.out_path_ != nullptr && !c.out_path_->sub (k)) - continue; - - // The first scope of which we are a parent is the least - // (shortest) one which means there is no other scope - // between it and our parent. - // - if (p == nullptr) - p = c.parent_; - - if (root && c.root_ == p->root_) // No intermediate root. - c.root_ = &s; - - if (p == c.parent_) // No intermediate parent. - c.parent_ = &s; - } - - // We couldn't get the parent from one of its old children - // so we have to find it ourselves. - // - if (p == nullptr) - p = &find (k.directory ()); - } - - s.parent_ = p; - s.root_ = root ? &s : (p != nullptr ? p->root_ : nullptr); - } - else if (root && !s.root ()) - { - // Upgrade to root scope. - // - auto r (map_.find_prefix (k)); - for (++r.first; r.first != r.second; ++r.first) - { - scope& c (*r.first->second); - - if (c.root_ == s.root_) // No intermediate root. - c.root_ = &s; - } - - s.root_ = &s; - } - } - else - assert (s.parent_ != nullptr); - - return er.first; - } - - // Find the most qualified scope that encompasses this path. - // - scope& scope_map:: - find (const dir_path& k) const - { - // Normally we would have a scope for the full path so try - // that before making any copies. - // - auto i (map_.find (k)), e (map_.end ()); - - if (i != e) - return *i->second; - - for (dir_path d (k.directory ());; d = d.directory ()) - { - auto i (map_.find (d)); - - if (i != e) - return *i->second; - - assert (!d.empty ()); // We should have the global scope. - } - } - - void scope_map:: - clear () - { - for (auto& p: map_) - { - scope* s (p.second); - - if (s->out_path_ == &p.first) - s->out_path_ = nullptr; - - if (s->src_path_ == &p.first) - s->src_path_ = nullptr; - - if (s->out_path_ == nullptr && s->src_path_ == nullptr) - delete s; - } - - map_.clear (); - } -} diff --git a/build/search b/build/search deleted file mode 100644 index d5e2c04..0000000 --- a/build/search +++ /dev/null @@ -1,31 +0,0 @@ -// file : build/search -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD_SEARCH -#define BUILD_SEARCH - -#include <build/types> - -namespace build -{ - 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 // BUILD_SEARCH diff --git a/build/search.cxx b/build/search.cxx deleted file mode 100644 index c06d281..0000000 --- a/build/search.cxx +++ /dev/null @@ -1,171 +0,0 @@ -// file : build/search.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <build/search> - -#include <utility> // move() -#include <cassert> - -#include <butl/filesystem> - -#include <build/scope> -#include <build/target> -#include <build/prerequisite> -#include <build/diagnostics> - -using namespace std; -using namespace butl; - -namespace build -{ - target* - search_existing_target (const prerequisite_key& pk) - { - tracer trace ("search_existing_target"); - - const target_key& tk (pk.tk); - - // Look for an existing target in this directory scope. - // - dir_path d; - if (tk.dir->absolute ()) - d = *tk.dir; // Already normalized. - else - { - d = pk.scope->out_path (); - - if (!tk.dir->empty ()) - { - d /= *tk.dir; - d.normalize (); - } - } - - auto i (targets.find (*tk.type, d, *tk.name, *tk.ext, trace)); - - if (i == targets.end ()) - return 0; - - target& t (**i); - - level5 ([&]{trace << "existing target " << t << " for prerequisite " - << pk;}); - - return &t; - } - - target* - search_existing_file (const prerequisite_key& cpk, const dir_paths& sp) - { - tracer trace ("search_existing_file"); - - prerequisite_key pk (cpk); // Make a copy so we can update extension. - target_key& tk (pk.tk); - assert (tk.dir->relative ()); - - // Figure out the extension. Pretty similar logic to file::derive_path(). - // - const string* ext (*tk.ext); - - if (ext == nullptr) - { - if (auto f = tk.type->extension) - { - ext = &f (tk, *pk.scope); // Already from the pool. - tk.ext = &ext; - } - else - { - // What should we do here, fail or say we didn't find anything? - // Current think is that if the target type didn't provide the - // default extension, then it doesn't want us to search for an - // existing file (of course, if the user specified the extension - // explicitly, we will still do so). But let me know what you - // think. - // - //fail << "no default extension for prerequisite " << pk; - level4 ([&]{trace << "no existing file found for prerequisite " - << pk;}); - return nullptr; - } - } - - // Go over paths looking for a file. - // - for (const dir_path& d: sp) - { - path f (d / *tk.dir / path (*tk.name)); - f.normalize (); - - if (!ext->empty ()) - { - f += '.'; - f += *ext; - } - - timestamp mt (file_mtime (f)); - - if (mt == timestamp_nonexistent) - continue; - - level5 ([&]{trace << "found existing file " << f << " for prerequisite " - << pk;}); - - // Find or insert. Note: using our updated extension. - // - auto r (targets.insert (*tk.type, f.directory (), *tk.name, ext, trace)); - - // Has to be a file_target. - // - file& t (dynamic_cast<file&> (r.first)); - - level5 ([&]{trace << (r.second ? "new" : "existing") << " target " - << t << " for prerequisite " << pk;}); - - if (t.path ().empty ()) - t.path (move (f)); - - t.mtime (mt); - return &t; - } - - level4 ([&]{trace << "no existing file found for prerequisite " << pk;}); - return nullptr; - } - - target& - create_new_target (const prerequisite_key& pk) - { - tracer trace ("create_new_target"); - - const target_key& tk (pk.tk); - - // We default to the target in this directory scope. - // - dir_path d; - if (tk.dir->absolute ()) - d = *tk.dir; // Already normalized. - else - { - d = pk.scope->out_path (); - - if (!tk.dir->empty ()) - { - d /= *tk.dir; - d.normalize (); - } - } - - // Find or insert. - // - auto r (targets.insert (*tk.type, move (d), *tk.name, *tk.ext, trace)); - assert (r.second); - - target& t (r.first); - - level5 ([&]{trace << "new target " << t << " for prerequisite " << pk;}); - - return t; - } -} diff --git a/build/spec b/build/spec deleted file mode 100644 index f50110b..0000000 --- a/build/spec +++ /dev/null @@ -1,61 +0,0 @@ -// file : build/spec -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD_SPEC -#define BUILD_SPEC - -#include <string> -#include <vector> -#include <iosfwd> -#include <utility> // move() - -#include <build/types> - -namespace build -{ - struct targetspec - { - typedef build::name name_type; - - explicit - targetspec (name_type n): name (std::move (n)) {} - targetspec (dir_path sb, name_type n) - : src_base (std::move (sb)), name (std::move (n)) {} - - dir_path src_base; - name_type name; - }; - - struct opspec: std::vector<targetspec> - { - opspec () = default; - opspec (std::string n): name (std::move (n)) {} - - std::string name; - }; - - struct metaopspec: std::vector<opspec> - { - metaopspec () = default; - metaopspec (std::string n): name (std::move (n)) {} - - std::string name; - }; - - typedef std::vector<metaopspec> buildspec; - - std::ostream& - operator<< (std::ostream&, const targetspec&); - - std::ostream& - operator<< (std::ostream&, const opspec&); - - std::ostream& - operator<< (std::ostream&, const metaopspec&); - - std::ostream& - operator<< (std::ostream&, const buildspec&); -} - -#endif // BUILD_SPEC diff --git a/build/spec.cxx b/build/spec.cxx deleted file mode 100644 index b08cdb3..0000000 --- a/build/spec.cxx +++ /dev/null @@ -1,81 +0,0 @@ -// file : build/spec.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <build/spec> - -#include <ostream> - -#include <build/context> -#include <build/diagnostics> - -using namespace std; - -namespace build -{ - 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/build/target b/build/target deleted file mode 100644 index 22b5e89..0000000 --- a/build/target +++ /dev/null @@ -1,1084 +0,0 @@ -// file : build/target -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD_TARGET -#define BUILD_TARGET - -#include <map> -#include <string> -#include <vector> -#include <memory> // unique_ptr -#include <cstddef> // size_t -#include <cstdint> // uint8_t -#include <functional> // reference_wrapper -#include <ostream> -#include <cassert> -#include <utility> // move(), forward(), declval() -#include <iterator> -#include <type_traits> - -#include <butl/utility> // reverse_iterate() -#include <butl/multi-index> // map_iterator_adapter - -#include <build/types> -#include <build/utility> - -#include <build/scope> -#include <build/variable> -#include <build/operation> -#include <build/target-type> -#include <build/target-key> -#include <build/prerequisite> - -namespace build -{ - class scope; - class target; - - target& - search (prerequisite&); // From <build/algorithm>. - - // Target state. - // - enum class target_state: std::uint8_t - { - // The order of the enumerators is arranged so that their integral - // values indicate whether one "overrides" the other in the merge - // operator (see below). - // - unknown, - unchanged, - postponed, - changed, - failed, - group // Target's state is the group's state. - }; - - std::ostream& - operator<< (std::ostream&, target_state); - - inline target_state& - operator |= (target_state& l, target_state r) - { - if (static_cast<std::uint8_t> (r) > static_cast<std::uint8_t> (l)) - l = r; - - return l; - } - - // Recipe. - // - // The returned target state should be changed, unchanged, or - // postponed, though you shouldn't be returning postponed - // directly. If there is an error, then the recipe should - // throw rather than returning failed. - // - // The return value of the recipe is used to update the target - // state except if the state was manually set by the recipe to - // target_state::group. Note that in this case the returned by - // the recipe value is still used as the resulting target state - // so it should match the group's state. - // - using recipe_function = target_state (action, target&); - using recipe = std::function<recipe_function>; - - // Commonly-used recipes. The default recipe executes the action on - // all the prerequisites in a loop, skipping ignored. Specifically, - // for actions with the "first" execution mode, it calls - // execute_prerequisites() while for those with the "last" mode -- - // reverse_execute_prerequisites(); see <build/operation>, - // <build/algorithm> for details. The group recipe call's the group's - // recipe. - // - extern const recipe empty_recipe; - extern const recipe noop_recipe; - extern const recipe default_recipe; - extern const recipe group_recipe; - - target_state - noop_action (action, target&); // Defined in <build/algorithm>. - - target_state - group_action (action, target&); // Defined in <build/algorithm>. - - // Prerequisite references as used in the target::prerequisites list - // below. - // - struct prerequisite_ref: std::reference_wrapper<prerequisite> - { - typedef std::reference_wrapper<prerequisite> base; - - using base::base; - - // Return true if this reference belongs to the target's prerequisite - // list. Note that this test only works if you use references to - // the container elements and the container hasn't been resized - // since such a reference was obtained. Normally this function is - // used when iterating over a combined prerequisites range (see - // group_prerequisites below). - // - bool - belongs (const target&) const; - }; - - // A view of target group members. - // - struct group_view - { - target* const* members; // NULL means not yet known. - std::size_t count; - }; - - // Target. - // - class target - { - public: - typedef build::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 <build/algorithm>. - // - virtual group_view - group_members (action_type) const; - - target_key - key () const {return target_key {&type (), &dir, &name, &ext};} - - // Scoping. - // - public: - // Most qualified scope that contains this target. - // - scope& - base_scope () const; - - // Root scope of a project that contains this target. Note that - // a target can be out of any (known) project root in which case - // this function asserts. If you need to detect this situation, - // then use base_scope().root_scope() expression instead. - // - scope& - root_scope () const; - - // Root scope of a strong amalgamation that contains this target. - // The same notes as to root_scope() apply. - // - scope& - strong_scope () const {return *root_scope ().strong_scope ();} - - // Root scope of the outermost amalgamation that contains this target. - // The same notes as to root_scope() apply. - // - scope& - weak_scope () const {return *root_scope ().weak_scope ();} - - - bool - in (const scope& s) const - { - return - (s.out_path_ != nullptr && dir.sub (*s.out_path_)) || - (s.src_path_ != nullptr && dir.sub (*s.src_path_)); - } - - // Prerequisites. - // - public: - typedef std::vector<prerequisite_ref> prerequisites_type; - prerequisites_type prerequisites; - - // Targets to which prerequisites resolve for this recipe. Note - // that unlike prerequisite::target, these can be resolved to - // group members. NULL means the target should be skipped (or - // the rule may simply not add such a target to the list). - // - // Note also that it is possible the target can vary from - // action to action, just like recipes. We don't need to keep - // track of the action here since the targets will be updated - // if the recipe is updated, normally as part of rule::apply(). - // - typedef std::vector<target*> prerequisite_targets_type; - prerequisite_targets_type prerequisite_targets; - - // Check if there are any prerequisites, taking into account - // group prerequisites. - // - bool - has_prerequisites () const - { - return !prerequisites.empty () || - (group != nullptr && !group->prerequisites.empty ()); - } - - // Target-specific variables. - // - public: - variable_map vars; - - // Lookup, including in groups to which this target belongs and - // then in outer scopes (including target type/pattern-specific - // variables). If you only want to lookup in this target, do it - // on the variable map directly. - // - lookup<const value> - operator[] (const variable&) const; - - lookup<const value> - operator[] (const std::string& name) const - { - return operator[] (var_pool.find (name)); - } - - // Return a value suitable for assignment. See class scope for - // details. - // - value& - assign (const variable& var) {return vars.assign (var).first;} - - value& - assign (const std::string& name) {return vars.assign (name).first;} - - // Return a value suitable for appending. See class scope for - // details. - // - value& - append (const variable&); - - value& - append (const std::string& name) - { - return append (var_pool.find (name)); - } - - public: - target_state raw_state; - - target_state - state () const - { - return raw_state != target_state::group ? raw_state : group->raw_state; - } - - // Number of direct targets that depend on this target in the current - // action. It is incremented during the match phase and then decremented - // during execution, before running the recipe. As a result, the recipe - // can detect the last chance (i.e., last dependent) to execute the - // command (see also the first/last execution modes in <operation>). - // - // Note that setting a new recipe (which happens when we match the rule - // and which in turn is triggered by the first dependent) clears this - // counter. However, if the previous action was the same as the current, - // then the existing recipe is reused. In this case, however, the counter - // should have been decremented to 0 naturally, as part of the previous - // action execution. - // - std::size_t dependents; - - public: - action_type action; // Action this recipe is for. - - public: - typedef build::recipe recipe_type; - - const recipe_type& - recipe (action_type a) const {return a > action ? empty_recipe : recipe_;} - - void - recipe (action_type, recipe_type); - - // Target type info. - // - public: - template <typename T> - T* - is_a () {return dynamic_cast<T*> (this);} - - template <typename T> - const T* - is_a () const {return dynamic_cast<const T*> (this);} - - // Dynamic derivation to support define. - // - const target_type* derived_type = nullptr; - - const target_type& - type () const - { - return derived_type != nullptr ? *derived_type : dynamic_type (); - } - - virtual const target_type& dynamic_type () const = 0; - static const target_type static_type; - - private: - recipe_type recipe_; - }; - - std::ostream& - operator<< (std::ostream&, const target&); - - // A "range" that presents the prerequisites of a group and one of - // its members as one continuous sequence, or, in other words, as - // if they were in a single container. The group's prerequisites - // come first followed by the member's. If you need to see them - // in the other direction, iterate in reverse, for example: - // - // for (prerequisite_ref& pr: group_prerequisites (t)) - // - // for (prerequisite_ref& pr: reverse_iterate (group_prerequisites (t)) - // - // Note that in this case the individual elements of each list will - // also be traversed in reverse, but that's what you usually want, - // anyway. - // - class group_prerequisites - { - public: - typedef target::prerequisites_type prerequisites_type; - - explicit - group_prerequisites (target& t): t_ (t) {} - - struct iterator - { - typedef prerequisites_type::iterator base_iterator; - - typedef base_iterator::value_type value_type; - typedef base_iterator::pointer pointer; - typedef base_iterator::reference reference; - typedef base_iterator::difference_type difference_type; - typedef std::bidirectional_iterator_tag iterator_category; - - iterator () {} - iterator (target* t, prerequisites_type* c, base_iterator i) - : t_ (t), c_ (c), i_ (i) {} - - iterator& - operator++ () - { - if (++i_ == c_->end () && c_ != &t_->prerequisites) - { - c_ = &t_->prerequisites; - i_ = c_->begin (); - } - return *this; - } - - iterator - operator++ (int) {iterator r (*this); operator++ (); return r;} - - iterator& - operator-- () - { - if (i_ == c_->begin () && c_ == &t_->prerequisites) - { - c_ = &t_->group->prerequisites; - i_ = c_->end (); - } - - --i_; - return *this; - } - - iterator - operator-- (int) {iterator r (*this); operator-- (); return r;} - - reference operator* () const {return *i_;} - pointer operator-> () const {return i_.operator -> ();} - - friend bool - operator== (const iterator& x, const iterator& y) - { - return x.t_ == y.t_ && x.c_ == y.c_ && x.i_ == y.i_; - } - - friend bool - operator!= (const iterator& x, const iterator& y) {return !(x == y);} - - private: - target* t_ {nullptr}; - prerequisites_type* c_ {nullptr}; - base_iterator i_; - }; - - typedef std::reverse_iterator<iterator> reverse_iterator; - - iterator - begin () const - { - auto& c ((t_.group != nullptr && !t_.group->prerequisites.empty () - ? *t_.group : t_).prerequisites); - return iterator (&t_, &c, c.begin ()); - } - - iterator - end () const - { - auto& c (t_.prerequisites); - return iterator (&t_, &c, c.end ()); - } - - reverse_iterator - rbegin () const {return reverse_iterator (end ());} - - reverse_iterator - rend () const {return reverse_iterator (begin ());} - - std::size_t - size () const - { - return t_.prerequisites.size () + - (t_.group != nullptr ? t_.group->prerequisites.size () : 0); - } - - private: - target& t_; - }; - - // A member of a prerequisite. If 'target' is NULL, then this is the - // prerequisite itself. Otherwise, it is its member. In this case - // 'prerequisite' still refers to the prerequisite. - // - struct prerequisite_member - { - typedef build::target target_type; - typedef build::prerequisite prerequisite_type; - - prerequisite_ref& prerequisite; - target_type* target; - - template <typename T> - bool - is_a () const - { - return target != nullptr - ? target->is_a<T> () != nullptr - : prerequisite.get ().is_a<T> (); - } - - prerequisite_key - key () const - { - return target != nullptr - ? prerequisite_key {prerequisite.get ().proj, target->key (), nullptr} - : prerequisite.get ().key (); - } - - const build::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 : build::search (prerequisite); - } - - prerequisite_type& - as_prerequisite (tracer&) const; - }; - - inline std::ostream& - operator<< (std::ostream& os, const prerequisite_member& pm) - { - return os << pm.key (); - } - - // A "range" that presents a sequence of prerequisites (e.g., from - // group_prerequisites()) as a sequence of prerequisite_member's. For - // each group prerequisite you will "see" either the prerequisite - // itself or all its members, depending on the default iteration - // mode of the target group type. You can skip the rest of the - // group members with leave_group() and you can force iteration - // over the members with enter_group(). Usage: - // - // for (prerequisite_member pm: prerequisite_members (a, ...)) - // - // Where ... can be: - // - // t.prerequisites - // reverse_iterate(t.prerequisites) - // group_prerequisites (t) - // reverse_iterate (group_prerequisites (t)) - // - // But use shortcuts instead: - // - // prerequisite_members (a, t) - // reverse_prerequisite_members (a, t) - // group_prerequisite_members (a, t) - // reverse_group_prerequisite_members (a, t) - // - template <typename T> - class prerequisite_members_range; - - template <typename T> - inline prerequisite_members_range<T> - prerequisite_members (action a, T&& x, bool members = true) - { - return prerequisite_members_range<T> (a, std::forward<T> (x), members); - } - - template <typename T> - class prerequisite_members_range - { - public: - prerequisite_members_range (action a, T&& r, bool m) - : a_ (a), members_ (m), r_ (std::forward<T> (r)), e_ (r_.end ()) {} - - using base_iterator = decltype (std::declval<T> ().begin ()); - - struct iterator - { - typedef prerequisite_member value_type; - typedef const value_type* pointer; - typedef const value_type& reference; - typedef typename base_iterator::difference_type difference_type; - typedef std::forward_iterator_tag iterator_category; - - iterator (): r_ (nullptr) {} - iterator (const prerequisite_members_range* r, const base_iterator& i) - : r_ (r), i_ (i), g_ {nullptr, 0} - { - if (r_->members_ && i_ != r_->e_ && i_->get ().type.see_through) - { - bool r (switch_members ()); - assert (r); // Group could not be resolved. - } - } - - iterator& operator++ (); - iterator operator++ (int) {iterator r (*this); operator++ (); return r;} - - // Skip iterating over the rest of this group's members, if any. - // Note that the only valid operation after this call is to - // increment the iterator. - // - void - leave_group () - { - // Pretend we are on the last member of some group. - // - j_ = 0; - g_.count = 1; - } - - // Iterate over this group's members. Return false if the member - // information is not available. Similar to leave_group(), you - // should increment the iterator after calling this function - // (provided it returned true). - // - bool - enter_group () - { - bool r (switch_members ()); - if (r) - --j_; // Compensate for the increment that will follow. - return r; - } - - value_type operator* () const - { - return value_type {*i_, g_.count != 0 ? g_.members[j_ - 1] : nullptr}; - } - - pointer operator-> () const - { - static_assert ( - std::is_trivially_destructible<prerequisite_member>::value, - "prerequisite_member is not trivially destructible"); - - return new (&m_) - value_type {*i_, g_.count != 0 ? g_.members[j_ - 1] : nullptr}; - } - - friend bool - operator== (const iterator& x, const iterator& y) - { - return x.i_ == y.i_ && - x.g_.count == y.g_.count && - (x.g_.count == 0 || x.j_ == y.j_); - } - - friend bool - operator!= (const iterator& x, const iterator& y) {return !(x == y);} - - private: - bool - switch_members (); - - private: - const prerequisite_members_range* r_; - base_iterator i_; - group_view g_; - std::size_t j_; // 1-based index, to support enter_group(). - mutable std::aligned_storage<sizeof (prerequisite_member), - alignof (prerequisite_member)>::type m_; - }; - - iterator - begin () const {return iterator (this, r_.begin ());} - - iterator - end () const {return iterator (this, e_);} - - private: - action a_; - bool members_; // Go into group members by default? - T r_; - base_iterator e_; - }; - - // prerequisite_members(t.prerequisites) - // - inline auto - prerequisite_members (action a, target& t, bool members = true) - { - return prerequisite_members (a, t.prerequisites, members); - } - - // prerequisite_members(reverse_iterate(t.prerequisites)) - // - inline auto - reverse_prerequisite_members (action a, target& t, bool members = true) - { - return prerequisite_members ( - a, butl::reverse_iterate (t.prerequisites), members); - } - - // prerequisite_members(group_prerequisites (t)) - // - inline auto - group_prerequisite_members (action a, target& t, bool members = true) - { - return prerequisite_members (a, group_prerequisites (t), members); - } - - // prerequisite_members(reverse_iterate (group_prerequisites (t))) - // - inline auto - reverse_group_prerequisite_members (action a, target& t, bool members = true) - { - return prerequisite_members ( - a, butl::reverse_iterate (group_prerequisites (t)), members); - } - - // - // - struct target_set - { - typedef std::map<target_key, std::unique_ptr<target>> map; - typedef butl::map_iterator_adapter<map::const_iterator> iterator; - - iterator - find (const target_key& k, tracer& trace) const; - - iterator - find (const target_type& type, - const dir_path& dir, - const std::string& name, - const std::string* ext, - tracer& trace) const - { - return find (target_key {&type, &dir, &name, &ext}, trace); - } - - // As above but ignore the extension and return the target or - // nullptr instead of the iterator. - // - template <typename T> - T* - find (const dir_path& dir, const std::string& name) const - { - const std::string* e (nullptr); - auto i (map_.find (target_key {&T::static_type, &dir, &name, &e})); - return i != map_.end () ? static_cast<T*> (i->second.get ()) : nullptr; - } - - iterator begin () const {return map_.begin ();} - iterator end () const {return map_.end ();} - - std::pair<target&, bool> - insert (const target_type&, - dir_path dir, - std::string name, - const std::string* ext, - tracer&); - - template <typename T> - T& - insert (const dir_path& dir, - const std::string& name, - const std::string* ext, - tracer& t) - { - return static_cast<T&> ( - insert (T::static_type, dir, name, ext, t).first); - } - - template <typename T> - T& - insert (const dir_path& dir, const std::string& name, tracer& t) - { - return static_cast<T&> ( - insert (T::static_type, dir, name, nullptr, t).first); - } - - void - clear () {map_.clear ();} - - private: - map map_; - }; - - extern target_set targets; - - // Modification time-based target. - // - class mtime_target: public target - { - public: - using target::target; - - // Generally, modification time for a target can only be queried - // after a rule has been matched since that's where the path is - // normally gets assigned. Normally, however, it would make sense - // to first execute the rule to get the "updated" timestamp. - // - // The rule for groups that utilize the group state is as follows: - // if it has any members that are mtime_targets, then the group - // should be mtime_target and the members get the mtime from it. - // - timestamp - mtime () const - { - const mtime_target* t (raw_state == target_state::group - ? static_cast<const mtime_target*> (group) - : this); - - if (t->mtime_ == timestamp_unknown) - t->mtime_ = t->load_mtime (); - - return t->mtime_; - } - - void - mtime (timestamp mt) - { - // While we can cache the mtime at any time, it may be ignored - // if the target state is group (see the mtime() accessor). - // - mtime_ = mt; - } - - protected: - virtual timestamp - load_mtime () const = 0; - - public: - static const target_type static_type; - - private: - mutable timestamp mtime_ {timestamp_unknown}; - }; - - // Filesystem path-based target. - // - class path_target: public mtime_target - { - public: - using mtime_target::mtime_target; - - typedef build::path path_type; - - const path_type& - path () const {return path_;} - - void - path (path_type p) {assert (path_.empty ()); path_ = std::move (p);} - - // Derive a path from target's dir, name, and, if specified, ext. - // If ext is not specified, then use default_ext and also update - // the target's extension (this becomes important if later we need - // to reliably determine whether this file has an extension; think - // hxx{foo.bar.} and hxx.ext is empty). - // - // If name_prefix is not NULL, add it before the name part and after - // the directory. Similarly, if name_suffix is not NULL, add it after - // the name part and before the extension. - // - // Finally, if the path was already assigned to this target, then - // this function verifies that the two are the same. - // - void - derive_path (const char* default_ext = nullptr, - const char* name_prefix = nullptr, - const char* name_suffix = nullptr); - - public: - static const target_type static_type; - - private: - path_type path_; - }; - - // File target. - // - class file: public path_target - { - public: - using path_target::path_target; - - protected: - // Note that it is final in order to be consistent with file_rule, - // search_existing_file(). - // - virtual timestamp - load_mtime () const final; - - public: - static const target_type static_type; - virtual const target_type& dynamic_type () const {return static_type;} - }; - - // Alias target. It represents a list of targets (its prerequisites) - // as a single "name". - // - class alias: public target - { - public: - using target::target; - - public: - static const target_type static_type; - virtual const target_type& dynamic_type () const {return static_type;} - }; - - // Directory target. Note that this is not a filesystem directory - // but rather an alias target with the directory name. For actual - // filesystem directory (creation), see fsdir. - // - class dir: public alias - { - public: - using alias::alias; - - public: - static const target_type static_type; - virtual const target_type& dynamic_type () const {return static_type;} - }; - - // While a filesystem directory is mtime-based, the semantics is - // not very useful in our case. In particular, if another target - // depends on fsdir{}, then all that's desired is the creation of - // the directory if it doesn't already exist. In particular, we - // don't want to update the target just because some unrelated - // entry was created in that directory. - // - class fsdir: public target - { - public: - using target::target; - - public: - static const target_type static_type; - virtual const target_type& dynamic_type () const {return static_type;} - }; - - class buildfile: public file - { - public: - using file::file; - - public: - static const target_type static_type; - virtual const target_type& dynamic_type () const {return static_type;} - }; - - // Common documentation file targets. - // - // @@ Maybe these should be in the built-in doc module? - // - class doc: public file - { - public: - using file::file; - - public: - static const target_type static_type; - virtual const target_type& dynamic_type () const {return static_type;} - }; - - // The problem with man pages is this: different platforms have - // different sets of sections. What seems to be the "sane" set - // is 1-9 (Linux and BSDs). SysV (e.g., Solaris) instead maps - // 8 to 1M (system administration). The section determines two - // things: the directory where the page is installed (e.g., - // /usr/share/man/man1) as well as the extension of the file - // (e.g., test.1). Note also that there could be sub-sections, - // e.g., 1p (for POSIX). Such a page would still go into man1 - // but will have the .1p extension (at least that's what happens - // on Linux). The challenge is to somehow handle this in a - // portable manner. So here is the plan: - // - // First of all, we have the man{} target type which can be used - // for a custom man page. That is, you can have any extension and - // install it anywhere you please: - // - // man{foo.X}: install = man/manX - // - // Then we have man1..9{} target types which model the "sane" - // section set and that would be automatically installed into - // correct locations on other platforms. In other words, the - // idea is that you should be able to have the foo.8 file, - // write man8{foo} and have it installed as man1m/foo.1m on - // some SysV host. - // - // Re-mapping the installation directory is easy: to help with - // that we have assigned install.man1..9 directory names. The - // messy part is to change the extension. It seems the only - // way to do that would be to have special logic for man pages - // in the generic install rule. @@ This is still a TODO. - // - // Note that handling subsections with man1..9{} is easy, we - // simply specify the extension explicitly, e.g., man{foo.1p}. - // - class man: public doc - { - public: - using doc::doc; - - public: - static const target_type static_type; - virtual const target_type& dynamic_type () const {return static_type;} - }; - - class man1: public man - { - public: - using man::man; - - public: - static const target_type static_type; - virtual const target_type& dynamic_type () const {return static_type;} - }; - - // Common implementation of the target factory, extension, and - // search functions. - // - template <typename T> - target* - target_factory (const target_type&, dir_path d, string n, const string* e) - { - return new T (move (d), move (n), e); - } - - // Return fixed target extension. - // - template <const char* ext> - const std::string& - target_extension_fix (const target_key&, scope&); - - // Get the extension from the variable or use the default if none set. - // Issue diagnostics and fail if the default is NULL. - // - template <const char* var, const char* def> - const std::string& - target_extension_var (const target_key&, scope&); - - // The default behavior, that is, look for an existing target in the - // prerequisite's directory scope. - // - target* - search_target (const prerequisite_key&); - - // First look for an existing target as above. If not found, then look - // for an existing file in the target-type-specific list of paths. - // - target* - search_file (const prerequisite_key&); - -} - -#include <build/target.ixx> -#include <build/target.txx> - -#endif // BUILD_TARGET diff --git a/build/target-key b/build/target-key deleted file mode 100644 index 526a33a..0000000 --- a/build/target-key +++ /dev/null @@ -1,55 +0,0 @@ -// file : build/target-key -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD_TARGET_KEY -#define BUILD_TARGET_KEY - -#include <map> -#include <string> -#include <ostream> -#include <functional> // reference_wrapper - -#include <butl/utility> // compare_c_string - -#include <build/types> -#include <build/target-type> - -namespace build -{ - // 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 // BUILD_TARGET_KEY diff --git a/build/target-type b/build/target-type deleted file mode 100644 index e299fe2..0000000 --- a/build/target-type +++ /dev/null @@ -1,97 +0,0 @@ -// file : build/target-type -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD_TARGET_TYPE -#define BUILD_TARGET_TYPE - -#include <map> -#include <string> -#include <ostream> - -#include <build/types> - -namespace build -{ - class scope; - class target; - class target_key; - class prerequisite_key; - - // Target type. - // - // Note that we assume there is always a single instance of this class - // for any target type. As a result, we can use address comparison to - // determine if two target types are the same. - // - // - struct target_type - { - const char* name; - const target_type* base; - target* (*factory) (const target_type&, dir_path, string, const string*); - const string& (*extension) (const target_key&, scope&); - target* (*search) (const prerequisite_key&); - bool see_through; // A group with the default "see through" semantics. - - bool - is_a (const target_type&) const; // Defined in target.cxx - - template <typename T> - bool - is_a () const {return is_a (T::static_type);} - }; - - inline bool - operator< (const target_type& x, const target_type& y) {return &x < &y;} - - inline bool - operator== (const target_type& x, const target_type& y) {return &x == &y;} - - inline bool - operator!= (const target_type& x, const target_type& y) {return &x != &y;} - - inline std::ostream& - operator<< (std::ostream& os, const target_type& tt) {return os << tt.name;} - - // Target type map. - // - struct target_type_ref - { - // Like reference_wrapper except it sometimes deletes the target type. - // - explicit - target_type_ref (const target_type& r): p_ (&r), d_ (false) {} - - explicit - target_type_ref (unique_ptr<target_type>&& p) - : p_ (p.release ()), d_ (true) {} - - target_type_ref (target_type_ref&& r) - : p_ (r.p_), d_ (r.d_) {r.p_ = nullptr;} - - ~target_type_ref () {if (p_ != nullptr && d_) delete p_;} - - explicit operator const target_type& () const {return *p_;} - const target_type& get () const {return *p_;} - - private: - const target_type* p_; - bool d_; - }; - - using target_type_map_base = std::map<string, target_type_ref>; - - class target_type_map: public target_type_map_base - { - public: - void - insert (const target_type& tt) {emplace (tt.name, target_type_ref (tt));} - - template <typename T> - void - insert () {insert (T::static_type);} - }; -} - -#endif // BUILD_TARGET_TYPE diff --git a/build/target.cxx b/build/target.cxx deleted file mode 100644 index 41fe54a..0000000 --- a/build/target.cxx +++ /dev/null @@ -1,537 +0,0 @@ -// file : build/target.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <build/target> - -#include <cassert> - -#include <butl/filesystem> - -#include <build/scope> -#include <build/search> -#include <build/algorithm> -#include <build/diagnostics> - -using namespace std; - -namespace build -{ - // target_type - // - bool target_type:: - is_a (const target_type& tt) const - { - for (const target_type* p (this); p != nullptr; p = p->base) - if (*p == tt) - return true; - - return false; - } - - // target_state - // - static const char* target_state_[] = { - "unknown", "unchanged", "postponed", "changed", "failed", "group"}; - - ostream& - operator<< (ostream& os, target_state ts) - { - return os << target_state_[static_cast<uint8_t> (ts)]; - } - - // recipe - // - const recipe empty_recipe; - const recipe noop_recipe (&noop_action); - const recipe default_recipe (&default_action); - const recipe group_recipe (&group_action); - - // target - // - - void target:: - recipe (action_type a, recipe_type r) - { - assert (a > action || !recipe_); - - bool override (a == action && recipe_); // See action::operator<. - - // Only noop_recipe can be overridden. - // - if (override) - { - recipe_function** f (recipe_.target<recipe_function*> ()); - assert (f != nullptr && *f == &noop_action); - } - - action = a; - recipe_ = std::move (r); - - // Also reset the target state. If this is a noop recipe, then - // mark the target unchanged so that we don't waste time executing - // the recipe. - // - raw_state = target_state::unknown; - - if (recipe_function** f = recipe_.target<recipe_function*> ()) - { - if (*f == &noop_action) - raw_state = target_state::unchanged; - } - - // This one is tricky: we don't want to reset the dependents count - // if we are merely overriding with a "stronger" recipe. - // - if (!override) - dependents = 0; - } - - void target:: - reset (action_type) - { - prerequisite_targets.clear (); - } - - group_view target:: - group_members (action_type) const - { - assert (false); // Not a group or doesn't expose its members. - return group_view {nullptr, 0}; - } - - scope& target:: - base_scope () const - { - return scopes.find (dir); - } - - scope& target:: - root_scope () const - { - // This is tricky to cache so we do the lookup for now. - // - scope* r (scopes.find (dir).root_scope ()); - assert (r != nullptr); - return *r; - } - - lookup<const value> target:: - operator[] (const variable& var) const - { - using result = lookup<const value>; - - if (auto p = vars.find (var)) - return result (p, &vars); - - if (group != nullptr) - { - if (auto p = group->vars.find (var)) - return result (p, &group->vars); - } - - // We cannot simply delegate to scope's lookup() since we also need - // to check the group. - // - for (const scope* s (&base_scope ()); s != nullptr; ) - { - if (!s->target_vars.empty ()) - { - if (auto l = s->target_vars.lookup (type (), name, var)) - return l; - - if (group != nullptr) - { - if (auto l = s->target_vars.lookup (group->type (), group->name, var)) - return l; - } - } - - if (auto r = s->vars.find (var)) - return result (r, &s->vars); - - switch (var.visibility) - { - case variable_visibility::scope: - s = nullptr; - break; - case variable_visibility::project: - s = s->root () ? nullptr : s->parent_scope (); - break; - case variable_visibility::normal: - s = s->parent_scope (); - break; - } - } - - return result (); - } - - value& target:: - append (const variable& var) - { - auto l (operator[] (var)); - - if (l && l.belongs (*this)) // Existing variable in this target. - return const_cast<value&> (*l); - - value& r (assign (var)); - - if (l) - r = *l; // Copy value from the outer scope. - - return r; - } - - ostream& - operator<< (ostream& os, const target& t) - { - return os << target_key {&t.type (), &t.dir, &t.name, &t.ext}; - } - - // target_set - // - target_set targets; - - auto target_set:: - find (const target_key& k, tracer& trace) const -> iterator - { - iterator i (map_.find (k)); - - if (i != end ()) - { - target& t (**i); - - // Update the extension if the existing target has it unspecified. - // - const string* ext (*k.ext); - if (t.ext != ext) - { - level5 ([&]{ - diag_record r (trace); - r << "assuming target " << t << " is the same as the one with "; - if (ext == nullptr) - r << "unspecified extension"; - else if (ext->empty ()) - r << "no extension"; - else - r << "extension " << *ext; - }); - - if (ext != nullptr) - t.ext = ext; - } - } - - return i; - } - - pair<target&, bool> target_set:: - insert (const target_type& tt, - dir_path dir, - string name, - const string* ext, - tracer& trace) - { - iterator i (find (target_key {&tt, &dir, &name, &ext}, trace)); - bool r (i == end ()); - - if (r) - { - unique_ptr<target> pt (tt.factory (tt, move (dir), move (name), ext)); - i = map_.emplace ( - make_pair (target_key {&tt, &pt->dir, &pt->name, &pt->ext}, - move (pt))).first; - } - - return pair<target&, bool> (**i, r); - } - - ostream& - operator<< (ostream& os, const target_key& k) - { - // If the name is empty, then we want to print the directory - // inside {}, e.g., dir{bar/}, not bar/dir{}. - // - bool n (!k.name->empty ()); - string d (diag_relative (*k.dir, false)); - - if (n) - os << d; - - os << k.type->name << '{'; - - if (n) - { - os << *k.name; - - if (*k.ext != nullptr && !(*k.ext)->empty ()) - os << '.' << **k.ext; - } - else - os << d; - - os << '}'; - - return os; - } - - // path_target - // - void path_target:: - derive_path (const char* de, const char* np, const char* ns) - { - string n; - - if (np != nullptr) - n += np; - - n += name; - - if (ns != nullptr) - n += ns; - - // Update the extension. - // - // See also search_existing_file() if updating anything here. - // - if (ext == nullptr) - { - // If provided by the caller, then use that. - // - if (de != nullptr) - ext = &extension_pool.find (de); - // - // Otherwis see if the target type has function that will - // give us the default extension. - // - else if (auto f = type ().extension) - ext = &f (key (), base_scope ()); // Already from the pool. - else - fail << "no default extension for target " << *this; - } - - // Add the extension. - // - if (!ext->empty ()) - { - n += '.'; - n += *ext; - } - - path_type p (dir / path_type (move (n))); - const path_type& ep (path ()); - - if (ep.empty ()) - path (p); - else if (p != ep) - fail << "path mismatch for target " << *this << - info << "assigned '" << ep << "'" << - info << "derived '" << p << "'"; - } - - // file_target - // - timestamp file:: - load_mtime () const - { - const path_type& f (path ()); - assert (!f.empty ()); - return file_mtime (f); - } - - // Search functions. - // - - target* - search_target (const prerequisite_key& pk) - { - // The default behavior is to look for an existing target in the - // prerequisite's directory scope. - // - return search_existing_target (pk); - } - - target* - search_file (const prerequisite_key& pk) - { - // First see if there is an existing target. - // - if (target* t = search_existing_target (pk)) - return t; - - // Then look for an existing file in this target-type-specific - // list of paths (@@ TODO: comes from the variable). - // - if (pk.tk.dir->relative ()) - { - dir_paths sp; - sp.push_back (pk.scope->src_path ()); // src_base - return search_existing_file (pk, sp); - } - else - return nullptr; - } - - static target* - search_alias (const prerequisite_key& pk) - { - // For an alias we don't want to silently create a target since it - // will do nothing and it most likely not what the user intended. - // - target* t (search_existing_target (pk)); - - if (t == nullptr) - fail << "no explicit target for prerequisite " << pk; - - return t; - } - - // type info - // - - const target_type target::static_type - { - "target", - nullptr, - nullptr, - nullptr, - &search_target, - false - }; - - const target_type mtime_target::static_type - { - "mtime_target", - &target::static_type, - nullptr, - nullptr, - &search_target, - false - }; - - const target_type path_target::static_type - { - "path_target", - &mtime_target::static_type, - nullptr, - nullptr, - &search_target, - false - }; - - template <typename T> - static target* - file_factory (const target_type&, dir_path d, string n, const string* e) - { - // The file target type doesn't imply any extension. So if one - // wasn't specified, set it to empty rather than unspecified. - // In other words, we always treat file{foo} as file{foo.}. - // - return new T (move (d), - move (n), - (e != nullptr ? e : &extension_pool.find (""))); - } - - constexpr const char file_ext_var[] = "extension"; - constexpr const char file_ext_def[] = ""; - - const target_type file::static_type - { - "file", - &path_target::static_type, - &file_factory<file>, - &target_extension_var<file_ext_var, file_ext_def>, - &search_file, - false - }; - - const target_type alias::static_type - { - "alias", - &target::static_type, - &target_factory<alias>, - nullptr, // Should never need. - &search_alias, - false - }; - - const target_type dir::static_type - { - "dir", - &alias::static_type, - &target_factory<dir>, - nullptr, // Should never need. - &search_alias, - false - }; - - const target_type fsdir::static_type - { - "fsdir", - &target::static_type, - &target_factory<fsdir>, - nullptr, // Should never need. - &search_target, - false - }; - - static const std::string& - buildfile_target_extension (const target_key& tk, scope&) - { - // If the name is special 'buildfile', then there is no extension, - // otherwise it is .build. - // - return extension_pool.find (*tk.name == "buildfile" ? "" : "build"); - } - - const target_type buildfile::static_type - { - "buildfile", - &file::static_type, - &file_factory<buildfile>, - &buildfile_target_extension, - &search_file, - false - }; - - const target_type doc::static_type - { - "doc", - &file::static_type, - &file_factory<doc>, - &target_extension_var<file_ext_var, file_ext_def>, // Same as file. - &search_file, - false - }; - - static target* - man_factory (const target_type&, dir_path d, string n, const string* e) - { - if (e == nullptr) - fail << "man target '" << n << "' must include extension (man section)"; - - return new man (move (d), move (n), e); - } - - const target_type man::static_type - { - "man", - &doc::static_type, - &man_factory, - nullptr, // Should be specified explicitly. - &search_file, - false - }; - - constexpr const char man1_ext[] = "1"; - const target_type man1::static_type - { - "man1", - &man::static_type, - &file_factory<man1>, - &target_extension_fix<man1_ext>, - &search_file, - false - }; -} diff --git a/build/target.ixx b/build/target.ixx deleted file mode 100644 index 6bcd265..0000000 --- a/build/target.ixx +++ /dev/null @@ -1,85 +0,0 @@ -// file : build/target.ixx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -namespace build -{ - // 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&); // <build/algorithm> - - template <typename T> - inline auto prerequisite_members_range<T>::iterator:: - operator++ () -> iterator& - { - if (g_.count != 0) - { - if (++j_ <= g_.count) - return *this; - - // Switch back to prerequisite iteration mode. - // - g_.count = 0; - } - - ++i_; - - // Switch to member iteration mode. - // - if (r_->members_ && i_ != r_->e_ && i_->get ().type.see_through) - { - bool r (switch_members ()); - assert (r); // Group could not be resolved. - } - - return *this; - } - - template <typename T> - inline bool prerequisite_members_range<T>::iterator:: - switch_members () - { - do - { - g_ = resolve_group_members (r_->a_, search (*i_)); - - // If members are not know, iterate over the group as itself. - // - if (g_.members == nullptr) - { - g_.count = 0; - return false; - } - } - while (g_.count == 0 && // Skip empty groups. - ++i_ != r_->e_ && - i_->get ().type.see_through); - - j_ = 1; // Start from the first group member. - return true; - } -} diff --git a/build/target.txx b/build/target.txx deleted file mode 100644 index 192cfbc..0000000 --- a/build/target.txx +++ /dev/null @@ -1,58 +0,0 @@ -// file : build/target.txx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <build/scope> -#include <build/context> // extension_pool -#include <build/diagnostics> -#include <build/prerequisite> - -namespace build -{ - template <const char* ext> - const string& - target_extension_fix (const target_key&, scope&) - { - return extension_pool.find (ext); - } - - template <const char* var, const char* def> - const string& - target_extension_var (const target_key& tk, scope& s) - { - // Include target type/pattern-specific variables. - // - if (auto l = s.lookup (tk, var)) - { - // Help the user here and strip leading '.' from the extension. - // - const string& e (as<string> (*l)); - return extension_pool.find ( - !e.empty () && e.front () == '.' ? string (e, 1) : e); - } - - if (def != nullptr) - return extension_pool.find (def); - - { - diag_record dr; - dr << error << "no default extension in variable '" << var << "'" - << info << "required to derive file name for "; - - // This is a bit hacky: we may be dealing with a target (see - // file::derive_path()) or prerequsite (see search_existing_file()). - // So we are going to check if dir is absolute. If it is, then - // we assume this is a target, otherwise -- prerequsite. - // - if (tk.dir->absolute ()) - dr << "target " << tk; - else - dr << "prerequisite " << prerequisite_key {nullptr, tk, &s}; - - dr << info << "perhaps you forgot to add " - << tk.type->name << "{*}: " << var << " = ..."; - } - - throw failed (); - } -} diff --git a/build/test/module b/build/test/module deleted file mode 100644 index 83c6d60..0000000 --- a/build/test/module +++ /dev/null @@ -1,24 +0,0 @@ -// file : build/test/module -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD_TEST_MODULE -#define BUILD_TEST_MODULE - -#include <build/types> -#include <build/module> - -namespace build -{ - namespace test - { - extern "C" void - test_boot (scope&, const location&, unique_ptr<module>&); - - extern "C" bool - test_init ( - scope&, scope&, const location&, unique_ptr<module>&, bool, bool); - } -} - -#endif // BUILD_TEST_MODULE diff --git a/build/test/module.cxx b/build/test/module.cxx deleted file mode 100644 index 9372103..0000000 --- a/build/test/module.cxx +++ /dev/null @@ -1,88 +0,0 @@ -// file : build/test/module.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <build/test/module> - -#include <build/scope> -#include <build/target> -#include <build/rule> -#include <build/diagnostics> - -#include <build/test/operation> -#include <build/test/rule> - -using namespace std; -using namespace butl; - -namespace build -{ - namespace test - { - static rule rule_; - - extern "C" void - test_boot (scope& root, const location&, unique_ptr<module>&) - { - tracer trace ("test::boot"); - - level5 ([&]{trace << "for " << root.out_path ();}); - - // Register the test operation. - // - root.operations.insert (test_id, test); - } - - extern "C" bool - test_init (scope& root, - scope&, - const location& l, - unique_ptr<module>&, - bool first, - bool) - { - tracer trace ("test::init"); - - if (!first) - { - warn (l) << "multiple test module initializations"; - return true; - } - - const dir_path& out_root (root.out_path ()); - level5 ([&]{trace << "for " << out_root;}); - - // Enter module variables. - // - { - auto& v (var_pool); - - v.find ("test", bool_type); - v.find ("test.input", name_type); - v.find ("test.output", name_type); - v.find ("test.roundtrip", name_type); - v.find ("test.options", strings_type); - v.find ("test.arguments", strings_type); - } - - // Register rules. - // - { - auto& r (root.rules); - - // Register our test running rule. - // - r.insert<target> (perform_test_id, "test", rule_); - - // Register our rule for the dist meta-operation. We need - // to do this because we have "ad-hoc prerequisites" (test - // input/output files) that need to be entered into the - // target list. - // - r.insert<target> (dist_id, test_id, "test", rule_); - } - - return true; - } - } -} diff --git a/build/test/operation b/build/test/operation deleted file mode 100644 index 1942936..0000000 --- a/build/test/operation +++ /dev/null @@ -1,18 +0,0 @@ -// file : build/test/operation -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD_TEST_OPERATION -#define BUILD_TEST_OPERATION - -#include <build/operation> - -namespace build -{ - namespace test - { - extern operation_info test; - } -} - -#endif // BUILD_TEST_OPERATION diff --git a/build/test/operation.cxx b/build/test/operation.cxx deleted file mode 100644 index 40c5b86..0000000 --- a/build/test/operation.cxx +++ /dev/null @@ -1,32 +0,0 @@ -// file : build/test/operation.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <build/test/operation> - -using namespace std; -using namespace butl; - -namespace build -{ - 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/build/test/rule b/build/test/rule deleted file mode 100644 index 17f32ce..0000000 --- a/build/test/rule +++ /dev/null @@ -1,30 +0,0 @@ -// file : build/test/rule -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD_TEST_RULE -#define BUILD_TEST_RULE - -#include <build/rule> -#include <build/operation> - -namespace build -{ - namespace test - { - class rule: public build::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 // BUILD_TEST_RULE diff --git a/build/test/rule.cxx b/build/test/rule.cxx deleted file mode 100644 index 3a4c91d..0000000 --- a/build/test/rule.cxx +++ /dev/null @@ -1,439 +0,0 @@ -// file : build/test/rule.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <build/test/rule> - -#include <butl/process> - -#include <build/scope> -#include <build/target> -#include <build/algorithm> -#include <build/diagnostics> - -#include <build/config/utility> // add_options() - -using namespace std; -using namespace butl; - -namespace build -{ - namespace test - { - match_result rule:: - match (action a, target& t, const std::string&) const - { - // First determine if this is a test. This is controlled by - // the test target variable and text.<tt> scope variables. - // Also, it feels redundant to specify, say, "test = true" - // and "test.output = test.out" -- the latter already says - // this is a test. So take care of that as well. - // - bool r (false); - lookup<const value> l; - - // @@ This logic doesn't take into account target type/pattern- - // specific variables. - // - // @@ Perhaps a find_any(<list-of-vars>)? - // - for (auto p (t.vars.find_namespace ("test")); - p.first != p.second; - ++p.first) - { - const variable& var (p.first->first); - const value& val (p.first->second); - - // If we have test, then always use that. - // - if (var.name == "test") - { - l = lookup<const value> (val, t); - break; - } - - // Otherwise check for variables that would indicate this - // is a test. - // - if (var.name == "test.input" || - var.name == "test.output" || - var.name == "test.roundtrip" || - var.name == "test.options" || - var.name == "test.arguments") - { - r = true; - break; - } - } - - if (!r) - { - // See if there is a scope variable. - // - if (!l.defined ()) - l = t.base_scope ()[ - var_pool.find (string("test.") + t.type ().name, bool_type)]; - - r = l && as<bool> (*l); - } - - // If this is the update pre-operation, then all we really need to - // do is say we are not a match and the standard matching machinery - // will (hopefully) find the rule to update this target. - // - // There is one thing that compilates this simple approach: test - // input/output. While normally they will be existing (in src_base) - // files, they could also be auto-generated. In fact, they could - // only be needed for testing, which means the normall update won't - // even know about them (nor clean, for that matter; this is why we - // need cleantest). - // - // To make generated input/output work we will have to cause their - // update ourselves. I other words, we may have to do some actual - // work for (update, test), and not simply "guide" (update, 0) as - // to which targets need updating. For how exactly we are going to - // do it, see apply() below. - // - match_result mr (t, r); - - // If this is the update pre-operation, change the recipe action - // to (update, 0) (i.e., "unconditional update"). - // - if (r && a.operation () == update_id) - mr.recipe_action = action (a.meta_operation (), update_id); - - return mr; - } - - recipe rule:: - apply (action a, target& t, const match_result& mr) const - { - tracer trace ("test::rule::apply"); - - if (!mr.bvalue) // Not a test. - return noop_recipe; - - // Ok, if we are here, then this means: - // - // 1. This target is a test. - // 2. The action is either - // a. (perform, test, 0) or - // b. (*, update, install) - // - // In both cases, the next step is to see if we have test.{input, - // output,roundtrip}. - // - - // First check the target-specific vars since they override any - // scope ones. - // - auto il (t.vars["test.input"]); - auto ol (t.vars["test.output"]); - auto rl (t.vars["test.roundtrip"]); - auto al (t.vars["test.arguments"]); // Should be input or arguments. - - if (al) - { - if (il) - fail << "both test.input and test.arguments specified for " - << "target " << t; - - if (rl) - fail << "both test.roundtrip and test.arguments specified for " - << "target " << t; - } - - scope& bs (t.base_scope ()); - - if (!il && !ol && !rl) - { - string n ("test."); - n += t.type ().name; - - const variable& in (var_pool.find (n + ".input", name_type)); - const variable& on (var_pool.find (n + ".output", name_type)); - const variable& rn (var_pool.find (n + ".roundtrip", name_type)); - - // We should only keep value(s) that were specified together - // in the innermost scope. - // - // @@ Shouldn't we stop at project root? - // - for (scope* s (&bs); s != nullptr; s = s->parent_scope ()) - { - ol = s->vars[on]; - - if (!al) // Not overriden at target level by test.arguments? - { - il = s->vars[in]; - rl = s->vars[rn]; - } - - if (il || ol || rl) - break; - } - } - - const name* in; - const name* on; - - // Reduce the roundtrip case to input/output. - // - if (rl) - { - if (il || ol) - fail << "both test.roundtrip and test.input/output specified " - << "for target " << t; - - in = on = &as<name> (*rl); - } - else - { - in = il ? &as<name> (*il) : nullptr; - on = ol ? &as<name> (*ol) : nullptr; - } - - // Resolve them to targets, which normally would be existing files - // but could also be targets that need updating. - // - target* it (in != nullptr ? &search (*in, bs) : nullptr); - target* ot (on != nullptr ? in == on ? it : &search (*on, bs) : nullptr); - - if (a.operation () == update_id) - { - // First see if input/output are existing, up-to-date files. This - // is a common case optimization. - // - if (it != nullptr) - { - build::match (a, *it); - - if (it->state () == target_state::unchanged) - { - unmatch (a, *it); - it = nullptr; - } - } - - if (ot != nullptr) - { - if (in != on) - { - build::match (a, *ot); - - if (ot->state () == target_state::unchanged) - { - unmatch (a, *ot); - ot = nullptr; - } - } - else - ot = it; - } - - - // Find the "real" update rule, that is, the rule that would - // have been found if we signalled that we do not match from - // match() above. - // - recipe d (match_delegate (a, t).first); - - // If we have no input/output that needs updating, then simply - // redirect to it. - // - if (it == nullptr && ot == nullptr) - return d; - - // Ok, time to handle the worst case scenario: we need to - // cause update of input/output targets and also delegate - // to the real update. - // - return [it, ot, dr = move (d)] (action a, target& t) -> target_state - { - // Do the general update first. - // - target_state r (execute_delegate (dr, a, t)); - - if (it != nullptr) - r |= execute (a, *it); - - if (ot != nullptr) - r |= execute (a, *ot); - - return r; - }; - } - else - { - // Cache the targets in our prerequsite targets lists where they - // can be found by perform_test(). If we have either or both, - // then the first entry is input and the second -- output (either - // can be NULL). - // - if (it != nullptr || ot != nullptr) - { - auto& pts (t.prerequisite_targets); - pts.resize (2, nullptr); - pts[0] = it; - pts[1] = ot; - } - - return &perform_test; - } - } - - static void - add_arguments (cstrings& args, const target& t, const char* n) - { - string var ("test."); - var += n; - - auto l (t.vars[var]); - - if (!l) - { - var.resize (5); - var += t.type ().name; - var += '.'; - var += n; - l = t.base_scope ()[var_pool.find (var, strings_type)]; - } - - if (l) - config::append_options (args, as<strings> (*l)); - } - - // The format of args shall be: - // - // name1 arg arg ... nullptr - // name2 arg arg ... nullptr - // ... - // nameN arg arg ... nullptr nullptr - // - static bool - run_test (target& t, - diag_record& dr, - char const** args, - process* prev = nullptr) - { - // Find the next process, if any. - // - char const** next (args); - for (next++; *next != nullptr; next++) ; - next++; - - // Redirect stdout to a pipe unless we are last, in which - // case redirect it to stderr. - // - int out (*next == nullptr ? 2 : -1); - bool pr, wr; - - try - { - if (prev == nullptr) - { - // First process. - // - process p (args, 0, out); - pr = *next == nullptr || run_test (t, dr, next, &p); - wr = p.wait (); - } - else - { - // Next process. - // - process p (args, *prev, out); - pr = *next == nullptr || run_test (t, dr, next, &p); - wr = p.wait (); - } - } - catch (const process_error& e) - { - error << "unable to execute " << args[0] << ": " << e.what (); - - if (e.child ()) - exit (1); - - throw failed (); - } - - if (!wr) - { - if (pr) // First failure? - dr << fail << "test " << t << " failed"; // Multi test: test 1. - - dr << error << "non-zero exit status: "; - print_process (dr, args); - } - - return pr && wr; - } - - target_state rule:: - perform_test (action, target& t) - { - // @@ Would be nice to print what signal/core was dumped. - // - // @@ Doesn't have to be a file target if we have test.cmd. - // - - file& ft (static_cast<file&> (t)); - assert (!ft.path ().empty ()); // Should have been assigned by update. - - cstrings args {ft.path ().string ().c_str ()}; - - // Do we have options? - // - add_arguments (args, t, "options"); - - // Do we have input? - // - auto& pts (t.prerequisite_targets); - if (pts.size () != 0 && pts[0] != nullptr) - { - file& it (static_cast<file&> (*pts[0])); - assert (!it.path ().empty ()); // Should have been assigned by update. - args.push_back (it.path ().string ().c_str ()); - } - // Maybe arguments then? - // - else - add_arguments (args, t, "arguments"); - - args.push_back (nullptr); - - // Do we have output? - // - if (pts.size () != 0 && pts[1] != nullptr) - { - file& ot (static_cast<file&> (*pts[1])); - assert (!ot.path ().empty ()); // Should have been assigned by update. - - args.push_back ("diff"); - args.push_back ("-u"); - args.push_back (ot.path ().string ().c_str ()); - args.push_back ("-"); - args.push_back (nullptr); - } - - args.push_back (nullptr); // Second. - - if (verb >= 2) - print_process (args); - else if (verb) - text << "test " << t; - - { - diag_record dr; - - if (!run_test (t, dr, args.data ())) - { - dr << info << "test command line: "; - print_process (dr, args); - } - } - - return target_state::changed; - } - } -} diff --git a/build/token b/build/token deleted file mode 100644 index d98b2e1..0000000 --- a/build/token +++ /dev/null @@ -1,73 +0,0 @@ -// file : build/token -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD_TOKEN -#define BUILD_TOKEN - -#include <string> -#include <iosfwd> -#include <cstddef> // size_t -#include <cstdint> // uint64_t -#include <utility> // move - -namespace build -{ - 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 // BUILD_TOKEN diff --git a/build/token.cxx b/build/token.cxx deleted file mode 100644 index cf47fd4..0000000 --- a/build/token.cxx +++ /dev/null @@ -1,35 +0,0 @@ -// file : build/token.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <build/token> - -#include <ostream> - -using namespace std; - -namespace build -{ - ostream& - operator<< (ostream& os, const token& t) - { - switch (t.type) - { - case token_type::eos: os << "<end-of-file>"; break; - case token_type::newline: os << "<newline>"; break; - case token_type::pair_separator: os << "<pair separator>"; break; - case token_type::colon: os << ":"; break; - case token_type::lcbrace: os << "{"; break; - case token_type::rcbrace: os << "}"; break; - case token_type::equal: os << "="; break; - case token_type::equal_plus: os << "=+"; break; - case token_type::plus_equal: os << "+="; break; - case token_type::dollar: os << "$"; break; - case token_type::lparen: os << "("; break; - case token_type::rparen: os << ")"; break; - case token_type::name: os << t.value; break; - } - - return os; - } -} diff --git a/build/types b/build/types deleted file mode 100644 index 3028fc2..0000000 --- a/build/types +++ /dev/null @@ -1,55 +0,0 @@ -// file : build/types -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD_TYPES -#define BUILD_TYPES - -#include <vector> -#include <string> -#include <utility> // pair -#include <memory> // unique_ptr, shared_ptr -#include <cstddef> // size_t -#include <functional> // reference_wrapper - -#include <butl/path> -#include <butl/timestamp> - -#include <build/name> - -namespace build -{ - // Commonly-used types. - // - using std::pair; - using std::size_t; - using std::string; - using std::unique_ptr; - using std::shared_ptr; - using std::reference_wrapper; - using std::vector; - - using strings = vector<string>; - using cstrings = vector<const char*>; - - // <butl/path> - // - using butl::path; - using butl::dir_path; - using butl::basic_path; - using butl::invalid_path; - - using paths = std::vector<path>; - using dir_paths = std::vector<dir_path>; - - // <butl/timestamp> - // - using butl::system_clock; - using butl::timestamp; - using butl::duration; - using butl::timestamp_unknown; - using butl::timestamp_nonexistent; - using butl::operator<<; -} - -#endif // BUILD_TYPES diff --git a/build/utility b/build/utility deleted file mode 100644 index bcb6b58..0000000 --- a/build/utility +++ /dev/null @@ -1,85 +0,0 @@ -// file : build/utility -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD_UTILITY -#define BUILD_UTILITY - -#include <utility> // move(), make_pair() -#include <cassert> // assert() -#include <exception> // uncaught_exception() -#include <unordered_set> - -#include <build/types> - -namespace build -{ - 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 <build/version>. Throw invalid_argument - // if the passed string is not a valid version. - // - unsigned int - to_version (const string&); - - // Call a function if there is an exception. - // - - // Means we are in the body of a destructor that is being called - // as part of the exception stack unwindining. Used to compensate - // for the deficiencies of uncaught_exception() until C++17 - // uncaught_exceptions() becomes available. - // - // @@ MT: will have to be TLS. - // - extern bool exception_unwinding_dtor; - - template <typename F> - struct exception_guard; - - template <typename F> - inline exception_guard<F> - make_exception_guard (F f) - { - return exception_guard<F> (move (f)); - } - - template <typename F> - struct exception_guard - { - exception_guard (F f): f_ (move (f)) {} - ~exception_guard () - { - if (std::uncaught_exception ()) - { - exception_unwinding_dtor = true; - f_ (); - exception_unwinding_dtor = false; - } - } - - private: - F f_; - }; - - // Pools (@@ perhaps move into a separate header). - // - struct string_pool: std::unordered_set<std::string> - { - const std::string& - find (const char* s) {return *emplace (s).first;} - - const std::string& - find (const std::string& s) {return *emplace (s).first;} - }; -} - -#endif // BUILD_UTILITY diff --git a/build/utility.cxx b/build/utility.cxx deleted file mode 100644 index 2361c8b..0000000 --- a/build/utility.cxx +++ /dev/null @@ -1,94 +0,0 @@ -// file : build/utility.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <build/utility> - -#include <cstdlib> // strtol() - -using namespace std; - -namespace build -{ - const string empty_string; - const path empty_path; - const dir_path empty_dir_path; - - unsigned int - to_version (const string& s) - { - // See tests/version. - // - - auto parse = [&s] (size_t& p, const char* m, long min = 0, long max = 99) - -> unsigned int - { - if (s[p] == '-' || s[p] == '+') // stoi() allows these. - throw invalid_argument (m); - - const char* b (s.c_str () + p); - char* e; - long r (strtol (b, &e, 10)); - - if (b == e || r < min || r > max) - throw invalid_argument (m); - - p = e - s.c_str (); - return static_cast<unsigned int> (r); - }; - - auto bail = [] (const char* m) {throw invalid_argument (m);}; - - unsigned int ma, mi, bf, ab (0); - - size_t p (0), n (s.size ()); - ma = parse (p, "invalid major version"); - - if (p >= n || s[p] != '.') - bail ("'.' expected after major version"); - - mi = parse (++p, "invalid minor version"); - - if (p >= n || s[p] != '.') - bail ("'.' expected after minor version"); - - bf = parse (++p, "invalid bugfix version"); - - if (p < n) - { - if (s[p] != '-') - bail ("'-' expected after bugfix version"); - - char k (s[++p]); - - if (k != 'a' && k != 'b') - bail ("'a' or 'b' expected in release component"); - - ab = parse (++p, "invalid release component", 1, 49); - - if (p != n) - bail ("junk after release component"); - - if (k == 'b') - ab += 50; - } - - // AABBCCDD - unsigned int r (ma * 1000000U + - mi * 10000U + - bf * 100U); - - if (ab != 0) - { - if (r == 0) - bail ("0.0.0 version with release component"); - - r -= 100; - r += ab; - } - - return r; - } - - bool exception_unwinding_dtor = false; -} diff --git a/build/variable b/build/variable deleted file mode 100644 index 799a35c..0000000 --- a/build/variable +++ /dev/null @@ -1,831 +0,0 @@ -// file : build/variable -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD_VARIABLE -#define BUILD_VARIABLE - -#include <map> -#include <vector> -#include <cstddef> // nullptr_t -#include <utility> // pair, make_pair() -#include <iterator> -#include <functional> // hash -#include <type_traits> // conditional, is_reference, remove_reference, etc. -#include <unordered_set> - -#include <butl/prefix-map> - -#include <build/types> -#include <build/utility> - -#include <build/target-type> - -namespace build -{ - struct variable; - - // If assign is NULL, then the value is assigned as is. If append - // is NULL, then the names are appended to the end of the value - // and assign is called, if not NULL. Variable is provided primarily - // for diagnostics. Return true if the resulting value is not empty. - // - struct value_type - { - const char* name; - bool (*const assign) (names&, const variable&); - bool (*const append) (names&, names, const variable&); - }; - - enum class variable_visibility - { - scope, // This scope (no outer scopes). - project, // This project (no outer projects). - normal // All outer scopes. - }; - - // variable - // - // The two variables are considered the same if they have the same name. - // - struct variable - { - std::string name; - const value_type* type; // If NULL, then not (yet) typed. - variable_visibility visibility; - char pairs; // Pair symbold or '\0' if not used. - }; - - inline bool - operator== (const variable& x, const variable& y) {return x.name == y.name;} - - typedef std::reference_wrapper<const variable> variable_cref; - - // value - // - class value - { - public: - // By default we create NULL value. - // - explicit value (const value_type* t = nullptr) - : type (t), state_ (state_type::null) {} - - value (value&&) = default; - - value& - operator= (std::nullptr_t) - { - data_.clear (); - state_ = state_type::null; - return *this; - } - - value& - operator= (value&&); - - value& - operator= (const value& v) - { - if (&v != this) - *this = value (v); - return *this; - } - - value& - operator= (reference_wrapper<value> v) {return *this = v.get ();} - - value& - operator= (reference_wrapper<const value> v) {return *this = v.get ();} - - value& - append (value, const variable&); // Aka operator+=(). - - value& - prepend (value, const variable&); // Aka operator=+(). - - // Forwarded to the representation type's assign()/append() (see below). - // - template <typename T> value& operator= (T); - value& operator= (const char* v) {return *this = std::string (v);} - - template <typename T> value& operator+= (T); - value& operator+= (const char* v) {return *this += std::string (v);} - - private: - explicit value (const value&) = default; - - public: - const value_type* type; // NULL means this value is not (yet) typed. - - bool null () const {return state_ == state_type::null;} - bool empty () const {return state_ == state_type::empty;} - - explicit operator bool () const {return !null ();} - bool operator== (std::nullptr_t) const {return null ();} - bool operator!= (std::nullptr_t) const {return !null ();} - - // Raw data read interface. - // - using const_iterator = names::const_iterator; - - const_iterator begin () const {return data_.begin ();} - const_iterator end () const {return data_.end ();} - - // Raw data write interface. Note that it triggers callbacks for - // typed values. Variable is passed for diagnostics. - // - void - assign (names, const variable&); - - void - append (names, const variable&); - - void - prepend (names, const variable&); - - public: - // Don't use directly except in the implementation of representation - // types, in which case take care to update state. - // - enum class state_type {null, empty, filled} state_; - names data_; - }; - - //@@ Right now we assume that for each value type each value has a - // unique representation. This is currently not the case for map. - // - inline bool - operator== (const value& x, const value& y) - { - return x.state_ == y.state_ && x.data_ == y.data_; - } - - inline bool - operator!= (const value& x, const value& y) {return !(x == y);} - - // lookup - // - // A variable can be undefined, NULL, or contain a (potentially - // empty) value. - // - struct variable_map; - - template <typename V> - struct lookup - { - V* value; - const variable_map* vars; - - bool - defined () const {return value != nullptr;} - - // Note: returns true if defined and not NULL. - // - explicit operator bool () const {return defined () && !value->null ();} - - V& operator* () const {return *value;} - V* operator-> () const {return value;} - - // Return true if this value belongs to the specified scope or target. - // Note that it can also be a target type/pattern-specific value. - // - template <typename T> - bool - belongs (const T& x) const {return vars == &x.vars;} - - lookup (): value (nullptr), vars (nullptr) {} - lookup (V* v, const variable_map* vs) - : value (v), vars (v != nullptr ? vs : nullptr) {} - - template <typename T> - lookup (V& v, const T& x): lookup (&v, &x.vars) {} - }; - - // Representation types. - // - template <typename T> struct value_traits; - - // Assign value type to the value. - // - template <typename T> - void assign (value&, const variable&); - void assign (value&, const value_type*, const variable&); - - template <typename T> typename value_traits<T>::type as (value&); - template <typename T> typename value_traits<T>::const_type as (const value&); - - // "Assign" simple value type to the value stored in name. Return false - // if the value is not valid for this type. - // - template <typename T> bool assign (name&); - - template <typename T> typename value_traits<T>::type as (name&); - template <typename T> typename value_traits<T>::const_type as (const name&); - - // bool - // - template <typename D> - struct bool_value - { - explicit - operator bool () const {return d->value[0] == 't';} - - bool_value& - operator= (bool v) - { - d->value = v ? "true" : "false"; - return *this; - } - - bool_value& - operator+= (bool v) - { - if (!*this && v) - d->value = "true"; - return *this; - } - - // Implementation details. - // - public: - explicit bool_value (D& d): d (&d) {} - - bool_value (const bool_value&) = delete; - bool_value& operator= (const bool_value&) = delete; // Rebind or deep? - - bool_value (bool_value&&) = default; - bool_value& operator= (bool_value&&) = delete; - - D* d; // name - }; - - template <> - struct value_traits<bool> - { - using type = bool_value<name>; - using const_type = bool_value<const name>; - - static type as (name& n) {return type (n);} - static const_type as (const name& n) {return const_type (n);} - - static type as (value&); - static const_type as (const value&); - - static bool assign (name&); - static void assign (value&, bool); - static void append (value&, bool); - - static const build::value_type value_type; - }; - - extern const value_type* bool_type; - - // string - // - template <> - struct value_traits<std::string> - { - using type = std::string&; - using const_type = const std::string&; - - static type as (name& n) {return n.value;} - static const_type as (const name& n) {return n.value;} - - static type as (value&); - static const_type as (const value&); - - static bool assign (name&); - static void assign (value&, std::string); - static void append (value&, std::string); - - static const build::value_type value_type; - }; - - extern const value_type* string_type; - - // dir_path - // - template <> - struct value_traits<dir_path> - { - using type = dir_path&; - using const_type = const dir_path&; - - static type as (name& n) {return n.dir;} - static const_type as (const name& n) {return n.dir;} - - static type as (value&); - static const_type as (const value&); - - static bool assign (name&); - static void assign (value&, dir_path); - static void append (value&, dir_path); - - static const build::value_type value_type; - }; - - extern const value_type* dir_path_type; - - // name - // - template <> - struct value_traits<name> - { - using type = name&; - using const_type = const name&; - - static type as (name& n) {return n;} - static const_type as (const name& n) {return n;} - - static type as (value&); - static const_type as (const value&); - - static bool assign (name&) {return true;} - static void assign (value&, name); - static void append (value&, name) = delete; - - static const build::value_type value_type; - }; - - extern const value_type* name_type; - - // vector<T> - // - template <typename T, typename D> - struct vector_value - { - using size_type = typename D::size_type; - - using value_type = typename value_traits<T>::type; - using const_value_type = typename value_traits<T>::const_type; - - template <typename I, typename V, typename R> - struct iterator_impl: I - { - using value_type = V; - using pointer = value_type*; - using reference = R; - using difference_type = typename I::difference_type; - - iterator_impl () = default; - iterator_impl (const I& i): I (i) {} - - // Note: operator->() is unavailable if R is a value. - // - reference operator* () const {return as<T> (I::operator* ());} - pointer operator-> () const {return &as<T> (I::operator* ());} - reference operator[] (difference_type n) const - { - return as<T> (I::operator[] (n)); - } - }; - - // Make iterator the same as const_iterator if our data type is const. - // - using const_iterator = - iterator_impl<names::const_iterator, const T, const_value_type>; - using iterator = typename std::conditional< - std::is_const<D>::value, - const_iterator, - iterator_impl<names::iterator, T, value_type>>::type; - - public: - vector_value& - operator= (std::vector<T> v) {assign (std::move (v)); return *this;} - - vector_value& - assign (std::vector<T>); - - template <typename D1> - vector_value& - assign (const vector_value<T, D1>&); - - template <typename D1> - vector_value& - append (const vector_value<T, D1>&); - - public: - bool empty () const {return d->empty ();} - size_type size () const {return d->size ();} - - value_type operator[] (size_type i) {return as<T> ((*d)[i]);} - const_value_type operator[] (size_type i) const {return as<T> ((*d)[i]);} - - iterator begin () {return iterator (d->begin ());} - iterator end () {return iterator (d->end ());} - - const_iterator begin () const {return const_iterator (d->begin ());} - const_iterator end () const {return const_iterator (d->end ());} - - const_iterator cbegin () const {return const_iterator (d->begin ());} - const_iterator cend () const {return const_iterator (d->end ());} - - // Implementation details. - // - public: - explicit vector_value (D& d): d (&d) {} - - vector_value (const vector_value&) = delete; - vector_value& operator= (const vector_value&) = delete; // Rebind or deep? - - vector_value (vector_value&&) = default; - vector_value& operator= (vector_value&&) = default; //@@ TMP - - explicit vector_value (std::nullptr_t): d (nullptr) {} //@@ TMP - - D* d; // names - }; - - template <typename T> - struct value_traits<std::vector<T>> - { - using type = vector_value<T, names>; - using const_type = vector_value<T, const names>; - - static type as (value&); - static const_type as (const value&); - - template <typename V> static void assign (value&, V); - template <typename V> static void append (value&, V); - - static const std::string type_name; - static const build::value_type value_type; - }; - - template <typename T, typename D> - struct value_traits<vector_value<T, D>>: value_traits<std::vector<T>> {}; - - using strings_value = vector_value<std::string, names>; - using const_strings_value = vector_value<std::string, const names>; - - extern const value_type* strings_type; // vector<string> aka strings - extern const value_type* dir_paths_type; // vector<dir_path> aka dir_paths - extern const value_type* names_type; // vector<name> aka names - - // map<K, V> - // - template <typename K, typename V, typename D> - struct map_value - { - template <typename F, typename S> - struct pair - { - using first_type = typename std::conditional< - std::is_reference<F>::value, - std::reference_wrapper<typename std::remove_reference<F>::type>, - F>::type; - - using second_type = typename std::conditional< - std::is_reference<S>::value, - std::reference_wrapper<typename std::remove_reference<S>::type>, - S>::type; - - first_type first; - second_type second; - }; - - template <typename I, typename T> - struct iterator_impl - { - using value_type = T; - using pointer = value_type*; - using reference = value_type; // Note: value. - using difference_type = typename I::difference_type; - using iterator_category = std::bidirectional_iterator_tag; - - iterator_impl () = default; - iterator_impl (const I& i): i_ (i) {} - - pointer operator-> () const = delete; - reference operator* () const - { - return value_type {as<K> (*i_), as<V> (*(i_ + 1))}; - } - - iterator_impl& operator++ () {i_ += 2; return *this;} - iterator_impl operator++ (int) {auto r (*this); operator++ (); return r;} - - iterator_impl& operator-- () {i_ -= 2; return *this;} - iterator_impl operator-- (int) {auto r (*this); operator-- (); return r;} - - bool operator== (const iterator_impl& y) const {return i_ == y.i_;} - bool operator!= (const iterator_impl& y) const {return i_ != y.i_;} - - private: - I i_; - }; - - using size_type = typename D::size_type; - - using value_type = pair<typename value_traits<K>::const_type, - typename value_traits<V>::type>; - - using const_value_type = pair<typename value_traits<K>::const_type, - typename value_traits<V>::const_type>; - - // Make iterator the same as const_iterator if our data type is const. - // - using const_iterator = - iterator_impl<names::const_iterator, const_value_type>; - using iterator = typename std::conditional< - std::is_const<D>::value, - const_iterator, - iterator_impl<names::iterator, value_type>>::type; - - - public: - map_value& - operator= (std::map<K, V> m) {assign (std::move (m)); return *this;} - - map_value& - assign (std::map<K, V>); - - bool empty () const {return d->empty ();} - size_type size () const {return d->size ();} - - iterator find (const K&); - const_iterator find (const K&) const; - - iterator begin () {return iterator (d->begin ());} - iterator end () {return iterator (d->end ());} - - const_iterator begin () const {return const_iterator (d->begin ());} - const_iterator end () const {return const_iterator (d->end ());} - - // Implementation details. - // - public: - explicit map_value (D& d): d (&d) {} - - map_value (const map_value&) = delete; - map_value& operator= (const map_value&) = delete; // Rebind or deep? - - map_value (map_value&&) = default; - map_value& operator= (map_value&&) = delete; - - D* d; // names - }; - - template <typename K, typename V> - struct value_traits<std::map<K, V>> - { - using type = map_value<K, V, names>; - using const_type = map_value<K, V, const names>; - - static type as (value&); - static const_type as (const value&); - - template <typename M> static void assign (value&, M); - template <typename M> static void append (value&, M); - - static const std::string type_name; - static const build::value_type value_type; - }; - - template <typename K, typename V, typename D> - struct value_traits<map_value<K, V, D>>: value_traits<std::map<K, V>> {}; -} - -namespace std -{ - template <> - struct hash<build::variable>: hash<string> - { - size_t - operator() (const build::variable& v) const noexcept - { - return hash<string>::operator() (v.name); - } - }; -} - -namespace butl -{ - template <> - struct compare_prefix<build::variable_cref>: compare_prefix<std::string> - { - typedef compare_prefix<std::string> base; - - explicit - compare_prefix (char d): base (d) {} - - bool - operator() (const build::variable& x, const build::variable& y) const - { - return base::operator() (x.name, y.name); - } - - bool - prefix (const build::variable& p, const build::variable& k) const - { - return base::prefix (p.name, k.name); - } - }; -} - -namespace build -{ - // variable_pool - // - using variable_pool_base = std::unordered_set<variable>; - struct variable_pool: private variable_pool_base - { - const variable& - find (string name, const build::value_type* t = nullptr, char p = '\0') - { - return find (name, nullptr, t, p); - } - - const variable& - find (string name, - variable_visibility v, - const build::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 build::value_type* t, - char p) - { - auto r ( - insert ( - variable { - std::move (name), - t, - vv != nullptr ? *vv : variable_visibility::normal, - p})); - const variable& v (*r.first); - - // Update type? - // - if (!r.second && t != nullptr && v.type != t) - { - assert (v.type == nullptr); - const_cast<variable&> (v).type = t; // Not changing the key. - } - - // Change visibility? While this might at first seem like a bad idea, - // it can happen that the variable lookup happens before any values - // were set, in which case the variable will be entered with the - // default visibility. - // - if (!r.second && vv != nullptr && v.visibility != *vv) - { - assert (v.visibility == variable_visibility::normal); // Default. - const_cast<variable&> (v).visibility = *vv; // Not changing the key. - } - - return v; - } - }; - - extern variable_pool var_pool; - - // variable_map - // - struct variable_map - { - using map_type = butl::prefix_map<variable_cref, value, '.'>; - using size_type = map_type::size_type; - - template <typename I> - struct iterator_adapter: I - { - iterator_adapter () = default; - iterator_adapter (const I& i): I (i) {} - typename I::reference operator* () const; - typename I::pointer operator-> () const; - }; - - using const_iterator = iterator_adapter<map_type::const_iterator>; - - const value* - find (const variable& var) const - { - auto i (m_.find (var)); - const value* r (i != m_.end () ? &i->second : nullptr); - - // First access after being assigned a type? - // - if (r != nullptr && var.type != nullptr && r->type != var.type) - build::assign (const_cast<value&> (*r), var.type, var); - - return r; - } - - value* - find (const variable& var) - { - auto i (m_.find (var)); - value* r (i != m_.end () ? &i->second : nullptr); - - // First access after being assigned a type? - // - if (r != nullptr && var.type != nullptr && r->type != var.type) - build::assign (*r, var.type, var); - - return r; - } - - lookup<const value> - operator[] (const variable& var) const - { - return lookup<const value> (find (var), this); - } - - lookup<const value> - operator[] (const std::string& name) const - { - return operator[] (var_pool.find (name)); - } - - // Non-const lookup. Only exposed on the map directly. - // - lookup<value> - operator[] (const variable& var) - { - return lookup<value> (find (var), this); - } - - lookup<value> - operator[] (const std::string& name) - { - return operator[] (var_pool.find (name)); - } - - // The second member in the pair indicates whether the new - // value (which will be NULL) was assigned. - // - std::pair<std::reference_wrapper<value>, bool> - assign (const variable& var) - { - auto r (m_.emplace (var, value (var.type))); - value& v (r.first->second); - - // First access after being assigned a type? - // - if (!r.second && var.type != nullptr && v.type != var.type) - build::assign (v, var.type, var); - - return std::make_pair (std::reference_wrapper<value> (v), r.second); - } - - std::pair<std::reference_wrapper<value>, bool> - assign (const std::string& name) - { - return assign (var_pool.find (name)); - } - - std::pair<const_iterator, const_iterator> - find_namespace (const std::string& ns) const - { - auto r (m_.find_prefix (var_pool.find (ns))); - return std::make_pair (const_iterator (r.first), - const_iterator (r.second)); - } - - const_iterator - begin () const {return m_.begin ();} - - const_iterator - end () const {return m_.end ();} - - bool - empty () const {return m_.empty ();} - - size_type - size () const {return m_.size ();} - - private: - map_type m_; - }; - - // Target type/pattern-specific variables. - // - // @@ In quite a few places we assume that we can store a reference - // to the returned value (e.g., install::lookup_install()). If - // we "instantiate" the value on the fly, then we will need to - // consider its lifetime. - // - using variable_pattern_map = std::map<std::string, variable_map>; - - struct variable_type_map: std::map<std::reference_wrapper<const target_type>, - variable_pattern_map> - { - build::lookup<const value> - lookup (const target_type&, const string& name, const variable&) const; - }; -} - -#include <build/variable.ixx> -#include <build/variable.txx> - -#endif // BUILD_VARIABLE diff --git a/build/variable.cxx b/build/variable.cxx deleted file mode 100644 index 6e02816..0000000 --- a/build/variable.cxx +++ /dev/null @@ -1,452 +0,0 @@ -// file : build/variable.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <build/variable> - -#include <iterator> // make_move_iterator() - -#include <build/utility> -#include <build/diagnostics> - -using namespace std; - -namespace build -{ - // value - // - void - assign (value& v, const value_type* t, const variable& var) - { - if (v.type == nullptr) - { - v.type = t; - - if (v && t->assign != nullptr) - v.state_ = t->assign (v.data_, var) - ? value::state_type::filled - : value::state_type::empty; - } - else - fail << "variable '" << var.name << "' type mismatch" << - info << "value '" << v.data_ << "' is " << v.type->name << - info << (t == var.type ? "variable" : "new type") << " is " - << (var.type != nullptr ? var.type->name : "untyped"); - } - - value& value:: - operator= (value&& v) - { - assert (type == nullptr || type == v.type); - - // Since the types are the same, we don't need to call - // the callbacks. - // - type = v.type; - state_ = v.state_; - data_ = move (v.data_); - - return *this; - } - - value& value:: - append (value v, const variable& var) - { - assert (type == v.type); - append (move (v.data_), var); - return *this; - } - - value& value:: - prepend (value v, const variable& var) - { - assert (type == v.type); - prepend (move (v.data_), var); - return *this; - } - - void value:: - append (names v, const variable& var) - { - // Treat append to NULL as assign. - // - if (!null () && type != nullptr && type->append != nullptr) - { - state_ = type->append (data_, move (v), var) - ? state_type::filled - : state_type::empty; - return; - } - - if (data_.empty ()) - data_ = move (v); - else - data_.insert (data_.end (), - make_move_iterator (v.begin ()), - make_move_iterator (v.end ())); - - state_ = (type != nullptr && type->assign != nullptr - ? type->assign (data_, var) - : !data_.empty ()) - ? state_type::filled - : state_type::empty; - } - - void value:: - prepend (names v, const variable& var) - { - // Reduce to append. - // - if (!null () && type != nullptr && type->append != nullptr) - { - state_ = type->append (v, move (data_), var) - ? state_type::filled - : state_type::empty; - swap (data_, v); - return; - } - - if (data_.empty ()) - data_ = move (v); - else - { - v.insert (v.end (), - make_move_iterator (data_.begin ()), - make_move_iterator (data_.end ())); - swap (data_, v); - } - - state_ = (type != nullptr && type->assign != nullptr - ? type->assign (data_, var) - : !data_.empty ()) - ? state_type::filled - : state_type::empty; - } - - // bool value - // - bool value_traits<bool>:: - assign (name& n) - { - if (n.simple ()) - { - const string& s (n.value); - - if (s == "true" || s == "false") - return true; - } - - return false; - } - - static bool - bool_assign (names& v, const variable& var) - { - // Verify the value is either "true" or "false". - // - if (v.size () == 1) - { - name& n (v.front ()); - - if (value_traits<bool>::assign (n)) - return true; - } - - fail << "invalid bool variable '" << var.name << "' value '" << v << "'"; - return false; - } - - static bool - bool_append (names& v, names a, const variable& var) - { - // Translate append to OR. - // - bool_assign (a, var); // Verify "true" or "false". - - if (a.front ().value[0] == 't' && v.front ().value[0] == 'f') - v = move (a); - - return true; - } - - const value_type value_traits<bool>::value_type - { - "bool", - &bool_assign, - &bool_append - }; - - const value_type* bool_type = &value_traits<bool>::value_type; - - // string value - // - bool value_traits<string>:: - assign (name& n) - { - // The below code is quite convoluted because we don't want to - // modify the name until we know it good (if it is not, then it - // will most likely be printed by the caller in diagnostics). - - // Suspend project qualification. - // - const string* p (n.proj); - n.proj = nullptr; - - // Convert directory to string. - // - if (n.directory ()) - { - n.value = std::move (n.dir).string (); // Move string out of path. - - // Add / back to the end of the path unless it is already there. - // Note that the string cannot be empty (n.directory () would - // have been false). - // - if (!dir_path::traits::is_separator (n.value[n.value.size () - 1])) - n.value += '/'; - } - - if (!n.simple ()) - { - n.proj = p; // Restore. - return false; - } - - // Convert project qualification to its string representation. - // - if (p != nullptr) - { - string s (*p); - s += '%'; - s += n.value; - s.swap (n.value); - } - - return true; - } - - static bool - string_assign (names& v, const variable& var) - { - // Verify/convert the value is/to a single simple name. - // - if (v.empty ()) - { - v.emplace_back (name ()); // Canonical empty string representation. - return false; - } - else if (v.size () == 1) - { - name& n (v.front ()); - - if (value_traits<string>::assign (n)) - return !n.value.empty (); - } - - fail << "invalid string variable '" << var.name << "' value '" << v << "'"; - return false; - } - - static bool - string_append (names& v, names a, const variable& var) - { - // Translate append to string concatenation. - // - string_assign (a, var); // Verify/convert value is/to string. - - if (v.front ().value.empty ()) - v = move (a); - else - v.front ().value += a.front ().value; - - return !v.front ().value.empty (); - } - - const value_type value_traits<string>::value_type - { - "string", - &string_assign, - &string_append - }; - - const value_type* string_type = &value_traits<string>::value_type; - - // dir_path value - // - bool value_traits<dir_path>:: - assign (name& n) - { - if (n.directory ()) - return true; - - if (n.simple ()) - { - try - { - n.dir = n.empty () ? dir_path () : dir_path (move (n.value)); - n.value.clear (); - return true; - } - catch (const invalid_path&) {} // Fall through. - } - - return false; - } - - static bool - dir_path_assign (names& v, const variable& var) - { - // Verify/convert the value is/to a single directory name. - // - if (v.empty ()) - { - v.emplace_back (dir_path ()); // Canonical empty path representation. - return false; - } - else if (v.size () == 1) - { - name& n (v.front ()); - - if (value_traits<dir_path>::assign (n)) - return !n.dir.empty (); - } - - fail << "invalid dir_path variable '" << var.name << "' " - << "value '" << v << "'"; - return false; - } - - static bool - dir_path_append (names& v, names a, const variable& var) - { - // Translate append to path concatenation. - // - dir_path_assign (a, var); // Verify/convert value is/to dir_path. - - dir_path& d (a.front ().dir); - if (d.relative ()) - return !(v.front ().dir /= d).empty (); - else - fail << "append of absolute path '" << d << "' to dir_path variable " - << var.name; - - return false; - } - - const value_type value_traits<dir_path>::value_type - { - "dir_path", - &dir_path_assign, - &dir_path_append - }; - - const value_type* dir_path_type = &value_traits<dir_path>::value_type; - - // name value - // - static bool - name_assign (names& v, const variable& var) - { - // Verify the value is a single name. - // - if (v.size () == 1) - return v.front ().empty (); - - fail << "invalid string variable '" << var.name << "' value '" << v << "'"; - return false; - } - - static bool - name_append (names&, names, const variable& var) - { - fail << "append to name variable '" << var.name << "'"; - return false; - } - - const value_type value_traits<name>::value_type - { - "name", - &name_assign, - &name_append - }; - - const value_type* name_type = &value_traits<name>::value_type; - - // vector<T> value - // - const value_type* strings_type = &value_traits<strings>::value_type; - const value_type* dir_paths_type = &value_traits<dir_paths>::value_type; - const value_type* names_type = &value_traits<names>::value_type; - - // variable_set - // - variable_pool var_pool; - - // variable_type_map - // - lookup<const value> variable_type_map:: - lookup (const target_type& type, - const string& name, - const variable& var) const - { - using result = build::lookup<const value>; - - // Search across target type hierarchy. - // - for (auto tt (&type); tt != nullptr; tt = tt->base) - { - auto i (find (*tt)); - - if (i == end ()) - continue; - - // Try to match the pattern, starting from the longest values - // so that the more "specific" patterns (i.e., those that cover - // fewer characters with the wildcard) take precedence. See - // tests/variable/type-pattern. - // - const variable_pattern_map& m (i->second); - - for (auto j (m.rbegin ()); j != m.rend (); ++j) - { - const string& p (j->first); - - size_t nn (name.size ()); - size_t pn (p.size ()); - - if (nn < pn - 1) // One for '*'. - continue; - - size_t w (p.find ('*')); - assert (w != string::npos); - - // Compare prefix. - // - if (w != 0 && - name.compare (0, w, p, 0, w) != 0) - continue; - - ++w; // First suffix character. - pn -= w; // Suffix length. - - // Compare suffix. - // - if (pn != 0 && - name.compare (nn - pn, pn, p, w, pn) != 0) - continue; - - // Ok, this pattern matches. But is there a variable? - // - if (const value* v = j->second.find (var)) - { - //@@ TODO: should we detect ambiguity? 'foo-*' '*-foo' and - // 'foo-foo'? Right now the last defined will be used. - // - return result (v, &j->second); - } - } - } - - return result (); - } -} diff --git a/build/variable.ixx b/build/variable.ixx deleted file mode 100644 index 16ea7c0..0000000 --- a/build/variable.ixx +++ /dev/null @@ -1,398 +0,0 @@ -// file : build/variable.ixx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -namespace build -{ - // value - // - template <typename T> - inline void - assign (value& v, const variable& var) - { - auto t (&value_traits<T>::value_type); - - if (v.type != t) - assign (v, t, var); - } - - template <typename T> - inline typename value_traits<T>::type - as (value& v) - { - return value_traits<T>::as (v); - } - - template <typename T> - inline typename value_traits<T>::const_type - as (const value& v) - { - return value_traits<T>::as (v); - } - - template <typename T> - inline bool - assign (name& n) - { - return value_traits<T>::assign (n); - } - - template <typename T> - inline typename value_traits<T>::type - as (name& n) - { - return value_traits<T>::as (n); - } - - template <typename T> - inline typename value_traits<T>::const_type - as (const name& n) - { - return value_traits<T>::as (n); - } - - template <typename T> - inline value& value:: - operator= (T v) - { - value_traits<T>::assign (*this, std::move (v)); - return *this; - } - - template <typename T> - inline value& value:: - operator+= (T v) - { - value_traits<T>::append (*this, std::move (v)); - return *this; - } - - inline void value:: - assign (names v, const variable& var) - { - data_ = std::move (v); - state_ = (type != nullptr && type->assign != nullptr - ? type->assign (data_, var) - : !data_.empty ()) - ? state_type::filled - : state_type::empty; - } - - // bool value - // - inline bool_value<name> value_traits<bool>:: - as (value& v) - { - assert (v.type == bool_type); - return bool_value<name> (v.data_.front ()); - } - - inline bool_value<const name> value_traits<bool>:: - as (const value& v) - { - assert (v.type == bool_type); - return bool_value<const name> (v.data_.front ()); - } - - inline void value_traits<bool>:: - assign (value& v, bool x) - { - if (v.null ()) - { - if (v.type == nullptr) - v.type = bool_type; - v.data_.emplace_back (name ()); - v.state_ = value::state_type::empty; - } - - as (v) = x; - v.state_ = value::state_type::filled; - } - - inline void value_traits<bool>:: - append (value& v, bool x) - { - if (v.null ()) - assign (v, x); - else - as (v) += x; // Cannot be empty. - } - - // string value - // - inline std::string& value_traits<std::string>:: - as (value& v) - { - assert (v.type == string_type); - return v.data_.front ().value; - } - - inline const std::string& value_traits<std::string>:: - as (const value& v) - { - assert (v.type == string_type); - return v.data_.front ().value; - } - - inline void value_traits<std::string>:: - assign (value& v, std::string x) - { - if (v.null ()) - { - if (v.type == nullptr) - v.type = string_type; - v.data_.emplace_back (name ()); - v.state_ = value::state_type::empty; - } - - v.state_ = (as (v) = std::move (x)).empty () - ? value::state_type::empty - : value::state_type::filled; - } - - inline void value_traits<std::string>:: - append (value& v, std::string x) - { - if (v.null ()) - assign (v, std::move (x)); - else - v.state_ = (as (v) += std::move (x)).empty () - ? value::state_type::empty - : value::state_type::filled; - } - - // dir_path value - // - inline dir_path& value_traits<dir_path>:: - as (value& v) - { - assert (v.type == dir_path_type); - return v.data_.front ().dir; - } - - inline const dir_path& value_traits<dir_path>:: - as (const value& v) - { - assert (v.type == dir_path_type); - return v.data_.front ().dir; - } - - inline void value_traits<dir_path>:: - assign (value& v, dir_path x) - { - if (v.null ()) - { - if (v.type == nullptr) - v.type = dir_path_type; - v.data_.emplace_back (name ()); - v.state_ = value::state_type::empty; - } - - v.state_ = (as (v) = std::move (x)).empty () - ? value::state_type::empty - : value::state_type::filled; - } - - inline void value_traits<dir_path>:: - append (value& v, dir_path x) - { - if (v.null ()) - assign (v, std::move (x)); - else - v.state_ = (as (v) /= std::move (x)).empty () - ? value::state_type::empty - : value::state_type::filled; - } - - // name value - // - inline name& value_traits<name>:: - as (value& v) - { - assert (v.type == name_type); - return v.data_.front (); - } - - inline const name& value_traits<name>:: - as (const value& v) - { - assert (v.type == name_type); - return v.data_.front (); - } - - inline void value_traits<name>:: - assign (value& v, name x) - { - if (v.null ()) - { - if (v.type == nullptr) - v.type = name_type; - v.data_.emplace_back (name ()); - v.state_ = value::state_type::empty; - } - - v.state_ = (as (v) = std::move (x)).empty () - ? value::state_type::empty - : value::state_type::filled; - } - - // vector<T> value - // - template <typename T, typename D> - inline vector_value<T, D>& vector_value<T, D>:: - assign (std::vector<T> v) - { - d->clear (); - d->insert (d->end (), - std::make_move_iterator (v.begin ()), - std::make_move_iterator (v.end ())); - return *this; - } - - template <typename T, typename D> - template <typename D1> - inline vector_value<T, D>& vector_value<T, D>:: - assign (const vector_value<T, D1>& v) - { - d->clear (); - d->insert (d->end (), v.begin (), v.end ()); - return *this; - } - - template <typename T, typename D> - template <typename D1> - inline vector_value<T, D>& vector_value<T, D>:: - append (const vector_value<T, D1>& v) - { - d->insert (d->end (), v.begin (), v.end ()); - return *this; - } - - template <typename T> - inline vector_value<T, names> value_traits<std::vector<T>>:: - as (value& v) - { - assert (v.type == &value_traits<std::vector<T>>::value_type); - return vector_value<T, names> (v.data_); - } - - template <typename T> - inline vector_value<T, const names> value_traits<std::vector<T>>:: - as (const value& v) - { - assert (v.type == &value_traits<std::vector<T>>::value_type); - return vector_value<T, const names> (v.data_); - } - - template <typename T> - template <typename V> - inline void value_traits<std::vector<T>>:: - assign (value& v, V x) - { - if (v.null ()) - { - if (v.type == nullptr) - v.type = &value_traits<std::vector<T>>::value_type; - v.state_ = value::state_type::empty; - } - - v.state_ = (as (v).assign (std::move (x))).empty () - ? value::state_type::empty - : value::state_type::filled; - } - - template <typename T> - template <typename V> - inline void value_traits<std::vector<T>>:: - append (value& v, V x) - { - if (v.null ()) - assign (v, std::move (x)); - else - v.state_ = (as (v).append (std::move (x))).empty () - ? value::state_type::empty - : value::state_type::filled; - } - - // map<K, V> value - // - template <typename K, typename V> - inline map_value<K, V, names> value_traits<std::map<K, V>>:: - as (value& v) - { - assert ((v.type == &value_traits<std::map<K, V>>::value_type)); - return map_value<K, V, names> (v.data_); - } - - template <typename K, typename V> - inline map_value<K, V, const names> value_traits<std::map<K, V>>:: - as (const value& v) - { - assert ((v.type == &value_traits<std::map<K, V>>::value_type)); - return map_value<K, V, const names> (v.data_); - } - - template <typename K, typename V> - template <typename M> - inline void value_traits<std::map<K, V>>:: - assign (value& v, M x) - { - if (v.null ()) - { - if (v.type == nullptr) - v.type = &value_traits<std::map<K, V>>::value_type; - v.state_ = value::state_type::empty; - } - - v.state_ = (as (v).assign (std::move (x))).empty () - ? value::state_type::empty - : value::state_type::filled; - } - - template <typename K, typename V> - template <typename M> - inline void value_traits<std::map<K, V>>:: - append (value& v, M x) - { - if (v.null ()) - assign (v, std::move (x)); - else - v.state_ = (as (v).append (std::move (x))).empty () - ? value::state_type::empty - : value::state_type::filled; - } - - // variable_map::iterator_adapter - // - template <typename I> - inline typename I::reference variable_map::iterator_adapter<I>:: - operator* () const - { - auto& r (I::operator* ()); - const variable& var (r.first); - auto& val (r.second); - - // First access after being assigned a type? - // - if (var.type != nullptr && val.type != var.type) - build::assign (const_cast<value&> (val), var.type, var); - - return r; - } - - template <typename I> - inline typename I::pointer variable_map::iterator_adapter<I>:: - operator-> () const - { - auto p (I::operator-> ()); - const variable& var (p->first); - auto& val (p->second); - - // First access after being assigned a type? - // - if (var.type != nullptr && val.type != var.type) - build::assign (const_cast<value&> (val), var.type, var); - - return p; - } -} diff --git a/build/variable.txx b/build/variable.txx deleted file mode 100644 index e661879..0000000 --- a/build/variable.txx +++ /dev/null @@ -1,168 +0,0 @@ -// file : build/variable.txx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <iterator> // make_move_iterator() - -#include <build/diagnostics> - -namespace build -{ - // vector<T> value - // - template <typename T> - bool - vector_assign (names& v, const variable& var) - { - // Verify each element has valid value of T. - // - for (name& n: v) - { - if (!assign<T> (n)) - fail << "invalid " << value_traits<T>::value_type.name << " element " - << "'" << n << "' in variable '" << var.name << "'"; - } - - return !v.empty (); - } - - template <typename T> - bool - vector_append (names& v, names a, const variable& var) - { - // Verify that what we are appending is valid. - // - vector_assign<T> (a, var); - - if (v.empty ()) - v = move (a); - else - v.insert (v.end (), - std::make_move_iterator (a.begin ()), - std::make_move_iterator (a.end ())); - - return !v.empty (); - } - - template <typename T> - const std::string value_traits<std::vector<T>>::type_name = std::string ( - value_traits<T>::value_type.name) + 's'; - - template <typename T> - const value_type value_traits<std::vector<T>>::value_type - { - value_traits<std::vector<T>>::type_name.c_str (), - &vector_assign<T>, - &vector_append<T> - }; - - // map<K, V> value - // - template <typename K, typename V, typename D> - map_value<K, V, D>& map_value<K, V, D>:: - assign (std::map<K, V> m) - { - d->clear (); - for (auto& p: m) - { - d->emplace_back (p.first); // Const, can't move. - d->back ().pair = '='; - d->emplace_back (std::move (p.second)); - } - - return *this; - } - - template <typename K, typename V, typename D> - auto map_value<K, V, D>:: - find (const K& k) -> iterator - { - // @@ Scan backwards to handle duplicates. - // - for (auto i (d->rbegin ()); i != d->rend (); ++i) - if (as<K> (*++i) == k) - return iterator (--(i.base ())); - - return end (); - } - - template <typename K, typename V, typename D> - auto map_value<K, V, D>:: - find (const K& k) const -> const_iterator - { - // @@ Scan backwards to handle duplicates. - // - for (auto i (d->rbegin ()); i != d->rend (); ++i) - if (as<K> (*++i) == k) - return const_iterator (--(i.base ())); - - return end (); - } - - template <typename K, typename V> - bool - map_assign (names& v, const variable& var) - { - // Verify we have a sequence of pairs and each lhs/rhs is a valid - // value of K/V. - // - for (auto i (v.begin ()); i != v.end (); ++i) - { - if (i->pair == '\0') - fail << value_traits<std::map<K, V>>::value_type.name << " key-value " - << "pair expected instead of '" << *i << "' " - << "in variable '" << var.name << "'"; - - if (!assign<K> (*i)) - fail << "invalid " << value_traits<K>::value_type.name << " key " - << "'" << *i << "' in variable '" << var.name << "'"; - - ++i; // Got to have the second half of the pair. - - if (!assign<V> (*i)) - fail << "invalid " << value_traits<V>::value_type.name << " value " - << "'" << *i << "' in variable '" << var.name << "'"; - } - - //@@ When doing sorting, note that assign() can convert the - // value. - - //@@ Is sorting really the right trade-off (i.e., insertion - // vs search)? Perhaps linear search is ok? - - return !v.empty (); - } - - template <typename K, typename V> - bool - map_append (names& v, names a, const variable& var) - { - //@@ Not weeding out duplicates. - - // Verify that what we are appending is valid. - // - map_assign<K, V> (a, var); - - if (v.empty ()) - v = move (a); - else - v.insert (v.end (), - std::make_move_iterator (a.begin ()), - std::make_move_iterator (a.end ())); - - return !v.empty (); - } - - template <typename K, typename V> - const std::string value_traits<std::map<K, V>>::type_name = std::string ( - value_traits<K>::value_type.name) + '_' + - value_traits<V>::value_type.name + "_map"; - - template <typename K, typename V> - const value_type value_traits<std::map<K, V>>::value_type - { - value_traits<std::map<K, V>>::type_name.c_str (), - &map_assign<K, V>, - &map_append<K, V> - }; -} diff --git a/build/version b/build/version deleted file mode 100644 index 96a9274..0000000 --- a/build/version +++ /dev/null @@ -1,42 +0,0 @@ -// file : build/version -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_VERSION // Note: using the version macro itself. - -#include <butl/version> // LIBBUTL_VERSION - -// Version format is AABBCCDD where -// -// AA - major version number -// BB - minor version number -// CC - bugfix version number -// DD - alpha / beta (DD + 50) version number -// -// When DD is not 00, 1 is subtracted from AABBCC. For example: -// -// Version AABBCCDD -// 2.0.0 02000000 -// 2.1.0 02010000 -// 2.1.1 02010100 -// 2.2.0-a1 02019901 -// 3.0.0-b2 02999952 -// -#define BUILD2_VERSION 10000 -#define BUILD2_VERSION_STR "0.1.0" - -//@@ TMP -// -#define BUILD_VERSION BUILD2_VERSION -#define BUILD_VERSION_STR BUILD2_VERSION_STR - -// 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 |