From 49d5628e35593a5300d39596286c768d7aa435b6 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Tue, 7 Jul 2015 09:18:22 +0200 Subject: Rework module architecture Now the target type and rule maps are in scopes (builtins -- in global scope). We also now have the map of loaded modules in the root scope of each project. --- build/algorithm.cxx | 203 ++++++++++++++++++--------------- build/b.cxx | 103 ++--------------- build/bin/module | 4 +- build/bin/module.cxx | 59 +++++++--- build/cli/module | 4 +- build/cli/module.cxx | 72 ++++++------ build/config/module | 5 +- build/config/module.cxx | 19 ++- build/config/operation.cxx | 3 +- build/config/utility | 6 +- build/context | 1 - build/context.cxx | 38 +++++- build/cxx/module | 4 +- build/cxx/module.cxx | 91 ++++++++++++--- build/module | 37 +++++- build/module.cxx | 32 +++++- build/parser.cxx | 14 +-- build/rule | 13 --- build/rule-map | 46 ++++++++ build/rule.cxx | 3 +- build/scope | 41 ++++++- build/scope.cxx | 100 ++++++++++++++++ build/target | 30 +---- build/target-key | 38 +----- build/target-type | 70 ++++++++++++ build/target.cxx | 77 ------------- tests/cli/lib/libtest/build/root.build | 1 - tests/cli/lib/libtest/test/buildfile | 2 + 28 files changed, 673 insertions(+), 443 deletions(-) create mode 100644 build/rule-map create mode 100644 build/target-type diff --git a/build/algorithm.cxx b/build/algorithm.cxx index bec51aa..488bbb7 100644 --- a/build/algorithm.cxx +++ b/build/algorithm.cxx @@ -45,131 +45,148 @@ namespace build // t.prerequisite_targets.clear (); + size_t oi (a.operation () - 1); // Operation index in rule_map. + scope& bs (t.base_scope ()); + for (auto tt (&t.type ()); tt != nullptr && !t.recipe (a); tt = tt->base) { - auto i (current_rules->find (tt->id)); + // Search scopes outwards, stopping at the project root. + // + for (const scope* s (&bs); + s != nullptr; + s = s->root () ? global_scope : s->parent_scope ()) + { + const rule_map& om (s->rules); - if (i == current_rules->end () || i->second.empty ()) - continue; // No rules registered for this target type, try base. + if (om.size () <= oi) + continue; // No entry for this operation id. - const auto& rules (i->second); // Hint map. + const target_type_rule_map& ttm (om[oi]); - // @@ 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)); + if (ttm.empty ()) + continue; // Empty map for this operation id. - for (auto i (rs.first); i != rs.second; ++i) - { - const string& n (i->first); - const rule& ru (i->second); + auto i (ttm.find (tt->id)); - match_result m; - { - auto g ( - make_exception_guard ( - [](action a, target& t, const string& n) - { - info << "while matching rule " << n << " to " - << diag_do (a, t); - }, - a, t, n)); + if (i == ttm.end () || i->second.empty ()) + continue; // No rules registered for this target type. - m = ru.match (a, t, hint); - } + 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)); - if (m) + for (auto i (rs.first); i != rs.second; ++i) { - // Do the ambiguity test. - // - bool ambig (false); + const string& n (i->first); + const rule& ru (i->second); - diag_record dr; + match_result m; + { + auto g ( + make_exception_guard ( + [](action a, target& t, const string& n) + { + info << "while matching rule " << n << " to " + << diag_do (a, t); + }, + a, t, n)); + + m = ru.match (a, t, hint); + } - for (++i; i != rs.second; ++i) + if (m) { - const string& n1 (i->first); - const rule& ru1 (i->second); + // Do the ambiguity test. + // + bool ambig (false); - match_result m1; - { - auto g ( - make_exception_guard ( - [](action a, target& t, const string& n1) - { - info << "while matching rule " << n1 << " to " - << diag_do (a, t); - }, - a, t, n1)); - - m1 = ru1.match (a, t, hint); - } + diag_record dr; - if (m1) + for (++i; i != rs.second; ++i) { - if (!ambig) + const string& n1 (i->first); + const rule& ru1 (i->second); + + match_result m1; { - dr << fail << "multiple rules matching " << diag_doing (a, t) - << info << "rule " << n << " matches"; - ambig = true; + auto g ( + make_exception_guard ( + [](action a, target& t, const string& n1) + { + info << "while matching rule " << n1 << " to " + << diag_do (a, t); + }, + a, t, n1)); + + m1 = ru1.match (a, t, hint); } - dr << info << "rule " << n1 << " also matches"; + if (m1) + { + if (!ambig) + { + dr << fail << "multiple rules matching " << diag_doing (a, t) + << info << "rule " << n << " matches"; + ambig = true; + } + + dr << info << "rule " << n1 << " also matches"; + } } - } - if (!ambig) - { - if (apply) - { - auto g ( - make_exception_guard ( - [](action a, target& t, const string& n) - { - info << "while applying rule " << n << " to " - << diag_do (a, t); - }, - a, t, n)); - - t.recipe (a, ru.apply (a, t, m)); - break; - } - else + if (!ambig) { - r.first = &ru; - r.second = m; + if (apply) + { + auto g ( + make_exception_guard ( + [](action a, target& t, const string& n) + { + info << "while applying rule " << n << " to " + << diag_do (a, t); + }, + a, t, n)); + + t.recipe (a, ru.apply (a, t, m)); + } + else + { + r.first = &ru; + r.second = m; + } + return r; } + else + dr << info << "use rule hint to disambiguate this match"; } - else - dr << info << "use rule hint to disambiguate this match"; } } } - if (!t.recipe (a)) - { - diag_record dr; - dr << fail << "no rule to " << diag_do (a, t); + diag_record dr; + dr << fail << "no rule to " << diag_do (a, t); - if (verb < 3) - dr << info << "re-run with --verbose 3 for more information"; - } + if (verb < 3) + dr << info << "re-run with --verbose 3 for more information"; return r; } diff --git a/build/b.cxx b/build/b.cxx index d7fb9d3..0291729 100644 --- a/build/b.cxx +++ b/build/b.cxx @@ -77,15 +77,8 @@ namespace build } #include - -#include //@@ tmp -#include //@@ tmp #include - -#include //@@ tmp -#include //@@ tmp #include - #include using namespace build; @@ -131,91 +124,12 @@ main (int argc, char* argv[]) // tzset (); - // Register modules. - // - modules["config"] = &config::init; - modules["bin"] = &bin::init; - modules["cxx"] = &cxx::init; - modules["cli"] = &cli::init; - - // Register target types. - // - target_types.insert (file::static_type); - target_types.insert (dir::static_type); - target_types.insert (fsdir::static_type); - - target_types.insert (bin::obja::static_type); - target_types.insert (bin::objso::static_type); - target_types.insert (bin::obj::static_type); - target_types.insert (bin::exe::static_type); - target_types.insert (bin::liba::static_type); - target_types.insert (bin::libso::static_type); - target_types.insert (bin::lib::static_type); - - target_types.insert (cxx::h::static_type); - target_types.insert (cxx::c::static_type); - - target_types.insert (cxx::cxx::static_type); - target_types.insert (cxx::hxx::static_type); - target_types.insert (cxx::ixx::static_type); - target_types.insert (cxx::txx::static_type); - - // Register rules. + // Register builtin modules. // - bin::obj_rule obj_rule; - bin::lib_rule lib_rule; - { - using namespace bin; - - rules[default_id][typeid (obj)].emplace ("bin.obj", obj_rule); - rules[update_id][typeid (obj)].emplace ("bin.obj", obj_rule); - rules[clean_id][typeid (obj)].emplace ("bin.obj", obj_rule); - - rules[default_id][typeid (lib)].emplace ("bin.lib", lib_rule); - rules[update_id][typeid (lib)].emplace ("bin.lib", lib_rule); - rules[clean_id][typeid (lib)].emplace ("bin.lib", lib_rule); - } - - cxx::compile cxx_compile; - cxx::link cxx_link; - { - using namespace bin; - - rules[default_id][typeid (obja)].emplace ("cxx.gnu.compile", cxx_compile); - rules[update_id][typeid (obja)].emplace ("cxx.gnu.compile", cxx_compile); - rules[clean_id][typeid (obja)].emplace ("cxx.gnu.compile", cxx_compile); - - rules[default_id][typeid (objso)].emplace ("cxx.gnu.compile", cxx_compile); - rules[update_id][typeid (objso)].emplace ("cxx.gnu.compile", cxx_compile); - rules[clean_id][typeid (objso)].emplace ("cxx.gnu.compile", cxx_compile); - - rules[default_id][typeid (exe)].emplace ("cxx.gnu.link", cxx_link); - rules[update_id][typeid (exe)].emplace ("cxx.gnu.link", cxx_link); - rules[clean_id][typeid (exe)].emplace ("cxx.gnu.link", cxx_link); - - rules[default_id][typeid (liba)].emplace ("cxx.gnu.link", cxx_link); - rules[update_id][typeid (liba)].emplace ("cxx.gnu.link", cxx_link); - rules[clean_id][typeid (liba)].emplace ("cxx.gnu.link", cxx_link); - - rules[default_id][typeid (libso)].emplace ("cxx.gnu.link", cxx_link); - rules[update_id][typeid (libso)].emplace ("cxx.gnu.link", cxx_link); - rules[clean_id][typeid (libso)].emplace ("cxx.gnu.link", cxx_link); - } - - dir_rule dir_r; - rules[default_id][typeid (dir)].emplace ("dir", dir_r); - rules[update_id][typeid (dir)].emplace ("dir", dir_r); - rules[clean_id][typeid (dir)].emplace ("dir", dir_r); - - fsdir_rule fsdir_r; - rules[default_id][typeid (fsdir)].emplace ("fsdir", fsdir_r); - rules[update_id][typeid (fsdir)].emplace ("fsdir", fsdir_r); - rules[clean_id][typeid (fsdir)].emplace ("fsdir", fsdir_r); - - file_rule file_r; - rules[default_id][typeid (file)].emplace ("file", file_r); - rules[update_id][typeid (file)].emplace ("file", file_r); - rules[clean_id][typeid (file)].emplace ("file", file_r); + builtin_modules["config"] = &config::config_init; + builtin_modules["bin"] = &bin::bin_init; + builtin_modules["cxx"] = &cxx::cxx_init; + builtin_modules["cli"] = &cli::cli_init; // Figure out work and home directories. // @@ -379,7 +293,7 @@ main (int argc, char* argv[]) // Handle a few common cases as special: empty name, '.', // '..', as well as dir{foo/bar} (without trailing '/'). - // This code must be consistent with target_type_map::find(). + // This code must be consistent with find_target_type(). // if (v.empty () || v == "." || v == ".." || tn.type == "dir") out_base = dir_path (v); @@ -744,7 +658,6 @@ main (int argc, char* argv[]) current_mif = mif; current_oif = oif; current_mode = oif->mode; - current_rules = &rules[oid]; } // // Similar to meta-operations, check that all the targets in @@ -787,8 +700,10 @@ main (int argc, char* argv[]) // operation batch. // { + scope& bs (scopes.find (out_base)); + const string* e; - const target_type* ti (target_types.find (tn, e)); + const target_type* ti (bs.find_target_type (tn, e)); if (ti == nullptr) fail (l) << "unknown target type " << tn.type; diff --git a/build/bin/module b/build/bin/module index e09e742..b87a0b0 100644 --- a/build/bin/module +++ b/build/bin/module @@ -12,8 +12,8 @@ namespace build { namespace bin { - void - init (scope&, scope&, const location&); + extern "C" void + bin_init (scope&, scope&, const location&, std::unique_ptr&, bool); } } diff --git a/build/bin/module.cxx b/build/bin/module.cxx index 3775302..b2374ff 100644 --- a/build/bin/module.cxx +++ b/build/bin/module.cxx @@ -10,39 +10,70 @@ #include +#include +#include + using namespace std; namespace build { namespace bin { + static obj_rule obj_; + static lib_rule lib_; + // Default config.bin.*.lib values. // static const list_value exe_lib (names {name ("shared"), name ("static")}); static const list_value liba_lib ("static"); static const list_value libso_lib ("shared"); - void - init (scope& root, scope& base, const location& l) + extern "C" void + bin_init (scope& root, + scope& base, + const location& l, + std::unique_ptr&, + bool) { - //@@ TODO: avoid multiple inits (generally, for modules). - // tracer trace ("bin::init"); + level4 ([&]{trace << "for " << base.path ();}); + + // Register target types. + // + { + auto& tts (base.target_types); + tts.insert (); + tts.insert (); + tts.insert (); + tts.insert (); + tts.insert (); + tts.insert (); + tts.insert (); + } - //@@ Should it be this way? + // Register rules. // - if (&root != &base) - fail (l) << "bin module must be initialized in project root scope"; + { + auto& rs (base.rules); - //@@ TODO: need to register target types, rules here instead of main(). + rs.insert (default_id, "bin.obj", obj_); + rs.insert (update_id, "bin.obj", obj_); + rs.insert (clean_id, "bin.obj", obj_); - const dir_path& out_root (root.path ()); - level4 ([&]{trace << "for " << out_root;}); + rs.insert (default_id, "bin.lib", lib_); + rs.insert (update_id, "bin.lib", lib_); + rs.insert (clean_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? @@ -51,7 +82,7 @@ namespace build // config.bin.lib // { - auto v (root.vars.assign ("bin.lib")); + auto v (base.vars.assign ("bin.lib")); if (!v) v = required (root, "config.bin.lib", "shared").first; } @@ -59,7 +90,7 @@ namespace build // config.bin.exe.lib // { - auto v (root.vars.assign ("bin.exe.lib")); + auto v (base.vars.assign ("bin.exe.lib")); if (!v) v = required (root, "config.bin.exe.lib", exe_lib).first; } @@ -67,7 +98,7 @@ namespace build // config.bin.liba.lib // { - auto v (root.vars.assign ("bin.liba.lib")); + auto v (base.vars.assign ("bin.liba.lib")); if (!v) v = required (root, "config.bin.liba.lib", liba_lib).first; } @@ -75,7 +106,7 @@ namespace build // config.bin.libso.lib // { - auto v (root.vars.assign ("bin.libso.lib")); + auto v (base.vars.assign ("bin.libso.lib")); if (!v) v = required (root, "config.bin.libso.lib", libso_lib).first; } diff --git a/build/cli/module b/build/cli/module index c2bc0ab..cf6258f 100644 --- a/build/cli/module +++ b/build/cli/module @@ -12,8 +12,8 @@ namespace build { namespace cli { - void - init (scope&, scope&, const location&); + extern "C" void + cli_init (scope&, scope&, const location&, std::unique_ptr&, bool); } } diff --git a/build/cli/module.cxx b/build/cli/module.cxx index d3fa87d..c1d0aab 100644 --- a/build/cli/module.cxx +++ b/build/cli/module.cxx @@ -13,7 +13,6 @@ #include #include -#include #include @@ -29,54 +28,62 @@ namespace build { static compile compile_; - void - init (scope& root, scope& base, const location& l) + extern "C" void + cli_init (scope& root, + scope& base, + const location& l, + std::unique_ptr&, + bool first) { - //@@ TODO: avoid multiple inits (generally, for modules). - // tracer trace ("cli::init"); + level4 ([&]{trace << "for " << base.path ();}); - //@@ Should it be this way? + // 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. // - if (&root != &base) - fail (l) << "cli module must be initialized in project root scope"; + if (base.find_target_type ("cxx") == nullptr) + fail (l) << "cxx module must be initialized before cli"; - // Initialize the cxx module. We need its targets types (?xx{}). - // @@ Or better require it? + // Register target types. // - cxx::init (root, base, l); - - const dir_path& out_root (root.path ()); - level4 ([&]{trace << "for " << out_root;}); + { + auto& tts (base.target_types); - // Register our target types. - // - target_types.insert (cli::static_type); - target_types.insert (cli_cxx::static_type); + tts.insert (); + tts.insert (); + } // Register our rules. // - rules[default_id][typeid (cli_cxx)].emplace ("cli.compile", compile_); - rules[update_id][typeid (cli_cxx)].emplace ("cli.compile", compile_); - rules[clean_id][typeid (cli_cxx)].emplace ("cli.compile", compile_); + { + auto& rs (base.rules); - rules[default_id][typeid (cxx::cxx)].emplace ("cli.compile", compile_); - rules[update_id][typeid (cxx::cxx)].emplace ("cli.compile", compile_); - rules[clean_id][typeid (cxx::cxx)].emplace ("cli.compile", compile_); + rs.insert (default_id, "cli.compile", compile_); + rs.insert (update_id, "cli.compile", compile_); + rs.insert (clean_id, "cli.compile", compile_); - rules[default_id][typeid (cxx::hxx)].emplace ("cli.compile", compile_); - rules[update_id][typeid (cxx::hxx)].emplace ("cli.compile", compile_); - rules[clean_id][typeid (cxx::hxx)].emplace ("cli.compile", compile_); + rs.insert (default_id, "cli.compile", compile_); + rs.insert (update_id, "cli.compile", compile_); + rs.insert (clean_id, "cli.compile", compile_); - rules[default_id][typeid (cxx::ixx)].emplace ("cli.compile", compile_); - rules[update_id][typeid (cxx::ixx)].emplace ("cli.compile", compile_); - rules[clean_id][typeid (cxx::ixx)].emplace ("cli.compile", compile_); + rs.insert (default_id, "cli.compile", compile_); + rs.insert (update_id, "cli.compile", compile_); + rs.insert (clean_id, "cli.compile", compile_); + + rs.insert (default_id, "cli.compile", compile_); + rs.insert (update_id, "cli.compile", compile_); + rs.insert (clean_id, "cli.compile", compile_); + } // Configure. // // config.cli // + if (first) { auto r (config::required (root, "config.cli", "cli")); @@ -132,10 +139,11 @@ namespace build // config.cli.options // // This one is optional. We also merge it into the corresponding - // cli.* variables. + // cli.* variables. See the cxx module for more information on + // this merging semantics and some of its tricky aspects. // if (auto* v = config::optional (root, "config.cli.options")) - root.append ("cli.options") += *v; + base.assign ("cli.options") += *v; } } } diff --git a/build/config/module b/build/config/module index 4ab3e7e..58d9814 100644 --- a/build/config/module +++ b/build/config/module @@ -12,8 +12,9 @@ namespace build { namespace config { - void - init (scope&, scope&, const location&); + extern "C" void + config_init ( + scope&, scope&, const location&, std::unique_ptr&, bool); } } diff --git a/build/config/module.cxx b/build/config/module.cxx index 7ac9071..237547c 100644 --- a/build/config/module.cxx +++ b/build/config/module.cxx @@ -23,16 +23,23 @@ namespace build // static const path config_file ("build/config.build"); - void - init (scope& root, scope& base, const location& l) + extern "C" void + config_init (scope& root, + scope& base, + const location& l, + std::unique_ptr&, + bool first) { - //@@ TODO: avoid multiple inits (generally, for modules). - // - tracer trace ("config::init"); if (&root != &base) - fail (l) << "config module must be initialized in project root scope"; + fail (l) << "config module must be initialized in bootstrap.build"; + + if (!first) + { + warn (l) << "multiple config module initializations"; + return; + } const dir_path& out_root (root.path ()); level4 ([&]{trace << "for " << out_root;}); diff --git a/build/config/operation.cxx b/build/config/operation.cxx index ecd805f..78ad9e8 100644 --- a/build/config/operation.cxx +++ b/build/config/operation.cxx @@ -8,8 +8,9 @@ #include -#include #include +#include +#include #include #include diff --git a/build/config/utility b/build/config/utility index 8ab6afe..e1d81a7 100644 --- a/build/config/utility +++ b/build/config/utility @@ -31,9 +31,9 @@ namespace build required (scope& root, const char* name, const char* default_value); // Set, if necessary, an optional config.* variable. In particular, - // an unspecified variable is set to NULL which is used to to - // distinguish between the "configured as unspecified" and "not - // yet configured" cases. + // an unspecified variable is set to NULL which is used to distinguish + // between the "configured as unspecified" and "not yet configured" + // cases. // // Return the pointer to the value, which can be NULL. // diff --git a/build/context b/build/context index 44948ec..a52232a 100644 --- a/build/context +++ b/build/context @@ -28,7 +28,6 @@ namespace build extern const operation_info* current_oif; extern execution_mode current_mode; - extern const target_rule_map* current_rules; // Reset the dependency state. In particular, this removes all the // targets, scopes, and variable names. diff --git a/build/context.cxx b/build/context.cxx index 22b6f53..d62be61 100644 --- a/build/context.cxx +++ b/build/context.cxx @@ -9,6 +9,8 @@ #include #include +#include +#include #include using namespace std; @@ -22,7 +24,12 @@ namespace build const meta_operation_info* current_mif; const operation_info* current_oif; execution_mode current_mode; - const target_rule_map* current_rules; + + // Builtin rules. + // + static dir_rule dir_; + static fsdir_rule fsdir_; + static file_rule file_; void reset () @@ -43,6 +50,35 @@ namespace build global_scope->assign ("work") = work; global_scope->assign ("home") = home; + + // Register builtin target types. + // + { + target_type_map& tts (global_scope->target_types); + + tts.insert (); + tts.insert (); + tts.insert (); + } + + // Register builtin rules. + // + { + rule_map& rs (global_scope->rules); + + rs.insert (default_id, "dir", dir_); + rs.insert (update_id, "dir", dir_); + rs.insert (clean_id, "dir", dir_); + + rs.insert (default_id, "fsdir", fsdir_); + rs.insert (update_id, "fsdir", fsdir_); + rs.insert (clean_id, "fsdir", fsdir_); + + rs.insert (default_id, "file", file_); + rs.insert (update_id, "file", file_); + rs.insert (clean_id, "file", file_); + } + } fs_status diff --git a/build/cxx/module b/build/cxx/module index 2d50f59..dcadcdd 100644 --- a/build/cxx/module +++ b/build/cxx/module @@ -12,8 +12,8 @@ namespace build { namespace cxx { - void - init (scope&, scope&, const location&); + extern "C" void + cxx_init (scope&, scope&, const location&, std::unique_ptr&, bool); } } diff --git a/build/cxx/module.cxx b/build/cxx/module.cxx index 7e6a211..ce7c968 100644 --- a/build/cxx/module.cxx +++ b/build/cxx/module.cxx @@ -10,10 +10,13 @@ #include #include -#include - #include +#include + +#include +#include + using namespace std; using namespace butl; @@ -21,33 +24,73 @@ namespace build { namespace cxx { - void - init (scope& root, scope& base, const location& l) + compile compile_; + link link_; + + extern "C" void + cxx_init (scope& root, + scope& base, + const location& l, + std::unique_ptr&, + bool first) { - //@@ TODO: avoid multiple inits (generally, for modules). - // - tracer trace ("cxx::init"); + level4 ([&]{trace << "for " << base.path ();}); - //@@ Should it be this way? + // Initialize the bin module. Only do this if it hasn't already + // been loaded so that we don't overwrite user's bin.* settings. // - if (&root != &base) - fail (l) << "cxx module must be initialized in project root scope"; + if (base.find_target_type ("obj") == nullptr) + load_module ("bin", root, base, l); - // Initialize the bin module. + // Register target types. // - bin::init (root, base, l); + { + auto& tts (base.target_types); + + tts.insert (); + tts.insert (); - //@@ TODO: need to register target types, rules here instead of main(). + tts.insert (); + tts.insert (); + tts.insert (); + tts.insert (); + } - const dir_path& out_root (root.path ()); - level4 ([&]{trace << "for " << out_root;}); + // Register rules. + // + { + using namespace bin; + + auto& rs (base.rules); + + rs.insert (default_id, "cxx.compile", compile_); + rs.insert (update_id, "cxx.compile", compile_); + rs.insert (clean_id, "cxx.compile", compile_); + + rs.insert (default_id, "cxx.compile", compile_); + rs.insert (update_id, "cxx.compile", compile_); + rs.insert (clean_id, "cxx.compile", compile_); + + rs.insert (default_id, "cxx.link", link_); + rs.insert (update_id, "cxx.link", link_); + rs.insert (clean_id, "cxx.link", link_); + + rs.insert (default_id, "cxx.link", link_); + rs.insert (update_id, "cxx.link", link_); + rs.insert (clean_id, "cxx.link", link_); + + rs.insert (default_id, "cxx.link", link_); + rs.insert (update_id, "cxx.link", link_); + rs.insert (clean_id, "cxx.link", link_); + } // Configure. // // config.cxx // + if (first) { auto r (config::required (root, "config.cxx", "g++")); @@ -98,17 +141,27 @@ namespace build // These are optional. We also merge them into the corresponding // cxx.* variables. // + // The merging part gets a bit tricky if this module has already + // been loaded in one of the outer scopes. By doing the straight + // append we would just be repeating the same options over and + // over. So what we are going to do is only append to a value if + // it came from this scope. Then the usage for merging becomes: + // + // cxx.coptions = # Note: '='. + // using cxx + // cxx.coptions += # Note: '+='. + // if (auto* v = config::optional (root, "config.cxx.poptions")) - root.append ("cxx.poptions") += *v; + base.assign ("cxx.poptions") += *v; if (auto* v = config::optional (root, "config.cxx.coptions")) - root.append ("cxx.coptions") += *v; + base.assign ("cxx.coptions") += *v; if (auto* v = config::optional (root, "config.cxx.loptions")) - root.append ("cxx.loptions") += *v; + base.assign ("cxx.loptions") += *v; if (auto* v = config::optional (root, "config.cxx.libs")) - root.append ("cxx.libs") += *v; + base.assign ("cxx.libs") += *v; } } } diff --git a/build/module b/build/module index 54a202c..43ab1e0 100644 --- a/build/module +++ b/build/module @@ -5,18 +5,47 @@ #ifndef BUILD_MODULE #define BUILD_MODULE +#include #include -#include +#include // unique_ptr namespace build { class scope; class location; - using module_init_function = void (scope& root, scope& base, const location&); - using module_map = std::unordered_map; + class module + { + public: + virtual + ~module () = default; + }; - extern module_map modules; + extern "C" + using module_init_function = + void (scope& root, + scope& base, + const location&, + std::unique_ptr&, + bool first); // First time for this project. + + using loaded_module_map = + std::map>>; + + // Load the specified module. Used by the parser but also by some + // modules to load prerequisite modules. + // + void + load_module (const std::string& name, + scope& root, + scope& base, + const location&); + + // Builtin modules. + // + using available_module_map = std::map; + extern available_module_map builtin_modules; } #endif // BUILD_MODULE diff --git a/build/module.cxx b/build/module.cxx index 6b410e6..0f2b1b2 100644 --- a/build/module.cxx +++ b/build/module.cxx @@ -4,9 +4,39 @@ #include +#include // make_pair() + +#include +#include + using namespace std; namespace build { - module_map modules; + available_module_map builtin_modules; + + void + load_module (const string& name, scope& root, scope& base, const location& l) + { + // First see if this modules has already been loaded for this + // project. + // + loaded_module_map& lm (root.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 ()) + fail (l) << "unknown module " << name; + + i = lm.emplace (name, make_pair (j->second, nullptr)).first; + } + + i->second.first (root, base, l, i->second.second, f); + } } diff --git a/build/parser.cxx b/build/parser.cxx index c8bc241..59e3783 100644 --- a/build/parser.cxx +++ b/build/parser.cxx @@ -261,7 +261,7 @@ namespace build auto enter_target = [this, &nloc, &trace] (name&& tn) -> target& { const string* e; - const target_type* ti (target_types.find (tn, e)); + const target_type* ti (scope_->find_target_type (tn, e)); if (ti == nullptr) fail (nloc) << "unknown target type " << tn.type; @@ -335,7 +335,7 @@ namespace build for (auto& pn: pns) { const string* e; - const target_type* ti (target_types.find (pn, e)); + const target_type* ti (scope_->find_target_type (pn, e)); if (ti == nullptr) fail (ploc) << "unknown target type " << pn.type; @@ -720,16 +720,10 @@ namespace build { // For now it should be a simple name. // - if (!n.type.empty () || !n.dir.empty ()) + if (!n.simple ()) fail (l) << "module name expected instead of " << n; - const string& name (n.value); - auto i (modules.find (name)); - - if (i == modules.end ()) - fail (l) << "unknown module " << name; - - i->second (*root_, *scope_, l); + load_module (n.value, *root_, *scope_, l); } if (tt == type::newline) diff --git a/build/rule b/build/rule index eda6ed3..fbcf06b 100644 --- a/build/rule +++ b/build/rule @@ -7,11 +7,6 @@ #include #include // nullptr_t -#include -#include // reference_wrapper -#include - -#include #include #include @@ -56,14 +51,6 @@ namespace build apply (action, target&, const match_result&) const = 0; }; - using target_rule_map = std::unordered_map< - std::type_index, - butl::prefix_map, '.'>>; - - using operation_rule_map = std::unordered_map; - - extern operation_rule_map rules; - // Fallback rule that on update verifies that the file exists and is // not older than any of its prerequisites. // diff --git a/build/rule-map b/build/rule-map new file mode 100644 index 0000000..883800b --- /dev/null +++ b/build/rule-map @@ -0,0 +1,46 @@ +// 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 +#include +#include +#include +#include // reference_wrapper + +#include + +#include +#include + +namespace build +{ + class rule; + + using target_type_rule_map = std::map< + std::type_index, // Target type. + butl::prefix_map, '.'>>; + + // This is an "indexed map" with (operation_id - 1) being the + // index. + // + class rule_map: public std::vector + { + public: + template + void + insert (operation_id oid, const char* hint, rule& r) + { + if (oid > size ()) + resize (oid < 3 ? 3 : oid); // 3 is the number of builtin operations. + + (*this)[oid - 1][typeid (T)].emplace (hint, r); + } + }; +} + +#endif // BUILD_RULE_MAP diff --git a/build/rule.cxx b/build/rule.cxx index a8c77a8..eadbecb 100644 --- a/build/rule.cxx +++ b/build/rule.cxx @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -19,8 +20,6 @@ using namespace butl; namespace build { - operation_rule_map rules; - // file_rule // // Note that this rule is special. It is the last, fallback rule. If diff --git a/build/scope b/build/scope index 5266370..d1cd941 100644 --- a/build/scope +++ b/build/scope @@ -12,8 +12,11 @@ #include #include +#include #include #include +#include +#include #include namespace build @@ -60,10 +63,11 @@ namespace build return operator[] (variable_pool.find (name)); } - // Return a value_proxy suitable for assignment. 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 - // if returned. + // Return a value_proxy 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_proxy assign (const variable& var) @@ -92,6 +96,8 @@ namespace build return append (variable_pool.find (name)); } + // Prerequisite cache. + // public: prerequisite_set prerequisites; @@ -109,6 +115,33 @@ namespace build // std::unordered_set buildfiles; + // Target types. + // + public: + target_type_map target_types; + + const target_type* + find_target_type (const char*, 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 std::string*& ext) const; + + // Rules. + // + public: + rule_map rules; + + // Modules. + // + public: + loaded_module_map modules; // Only on root scope. + private: friend class scope_map; friend class temp_scope; diff --git a/build/scope.cxx b/build/scope.cxx index 5bd2b62..4645979 100644 --- a/build/scope.cxx +++ b/build/scope.cxx @@ -4,6 +4,8 @@ #include +#include + using namespace std; namespace build @@ -40,6 +42,104 @@ namespace build return r; } + const target_type* scope:: + find_target_type (const char* 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; + } + + 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 char* tt; + if (n.type.empty ()) + { + // Empty name or '.' and '..' signify a directory. + // + if (v.empty () || v == "." || v == "..") + tt = "dir"; + else + //@@ TODO: derive type from extension. + // + tt = "file"; + } + else + tt = n.type.c_str (); + + const target_type* r (find_target_type (tt)); + + if (r == nullptr) + return r; + + // Directories require special name processing. If we find that more + // targets deviate, then we should make this target-type-specific. + // + if (r->is_a () || r->is_a ()) + { + // The canonical representation of a directory name is with empty + // value. + // + if (!v.empty ()) + { + n.dir /= dir_path (v); // Move name value to dir. + v.clear (); + } + } + else + { + // Split the path into its directory part (if any) the name part, + // and the extension (if any). We cannot assume the name part is + // a valid filesystem name so we will have to do the splitting + // manually. + // + path::size_type i (path::traits::rfind_separator (v)); + + if (i != string::npos) + { + n.dir /= dir_path (v, i != 0 ? i : 1); // Special case: "/". + v = string (v, i + 1, string::npos); + } + + // Extract the extension. + // + string::size_type j (path::traits::find_extension (v)); + + if (j != string::npos) + { + ext = &extension_pool.find (v.c_str () + j + 1); + v.resize (j); + } + } + + return r; + } + // scope_map // scope_map scopes; diff --git a/build/target b/build/target index 4131338..954604c 100644 --- a/build/target +++ b/build/target @@ -10,19 +10,20 @@ #include #include // unique_ptr #include // size_t -#include // function, reference_wrapper +#include // reference_wrapper #include #include #include // move(), forward(), declval() #include #include -#include // compare_c_string, reverse_iterate() +#include // reverse_iterate() #include // map_iterator_adapter #include #include #include +#include #include #include @@ -756,31 +757,6 @@ namespace build extern target_set targets; - using target_type_map_base = std::map< - const char*, - std::reference_wrapper, - butl::compare_c_string>; - - class target_type_map: public target_type_map_base - { - public: - void - insert (const target_type& tt) {emplace (tt.name, tt);} - - using target_type_map_base::find; - - // 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 (name&, const std::string*& ext) const; - }; - - extern target_type_map target_types; - // Modification time-based target. // class mtime_target: public target diff --git a/build/target-key b/build/target-key index 54813b0..37fa6ab 100644 --- a/build/target-key +++ b/build/target-key @@ -5,45 +5,19 @@ #ifndef BUILD_TARGET_KEY #define BUILD_TARGET_KEY +#include #include -#include #include +#include +#include // reference_wrapper + +#include // compare_c_string #include +#include namespace build { - class scope; - class target; - class target_key; - class prerequisite_key; - - // Target type. - // - struct target_type - { - std::type_index id; - const char* name; - const target_type* base; - target* (*const factory) (dir_path, std::string, const std::string*); - const std::string& (*const extension) (const target_key&, scope&); - target* (*const search) (const prerequisite_key&); - bool see_through; // A group with the default "see through" semantics. - - bool - is_a (const std::type_index&) const; // Defined in target.cxx - - template - bool - is_a () const {return is_a (typeid (T));} - }; - - inline std::ostream& - operator<< (std::ostream& os, const target_type& tt) - { - return os << tt.name; - } - // Light-weight (by being shallow-pointing) target key. // class target_key diff --git a/build/target-type b/build/target-type new file mode 100644 index 0000000..d5e7971 --- /dev/null +++ b/build/target-type @@ -0,0 +1,70 @@ +// 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 +#include +#include +#include +#include // reference_wrapper + +#include // compare_c_string + +#include + +namespace build +{ + class scope; + class target; + class target_key; + class prerequisite_key; + + // Target type. + // + struct target_type + { + std::type_index id; + const char* name; + const target_type* base; + target* (*const factory) (dir_path, std::string, const std::string*); + const std::string& (*const extension) (const target_key&, scope&); + target* (*const search) (const prerequisite_key&); + bool see_through; // A group with the default "see through" semantics. + + bool + is_a (const std::type_index&) const; // Defined in target.cxx + + template + bool + is_a () const {return is_a (typeid (T));} + }; + + inline std::ostream& + operator<< (std::ostream& os, const target_type& tt) + { + return os << tt.name; + } + + // Target type map. + // + using target_type_map_base = std::map< + const char*, + std::reference_wrapper, + butl::compare_c_string>; + + class target_type_map: public target_type_map_base + { + public: + void + insert (const target_type& tt) {emplace (tt.name, tt);} + + template + void + insert () {insert (T::static_type);} + }; +} + +#endif // BUILD_TARGET_TYPE diff --git a/build/target.cxx b/build/target.cxx index f692a5e..63bb2fe 100644 --- a/build/target.cxx +++ b/build/target.cxx @@ -197,83 +197,6 @@ namespace build return os; } - // - // - target_type_map target_types; - - const target_type* target_type_map:: - find (name& n, const string*& ext) const - { - ext = nullptr; - - string& v (n.value); - - // First determine the target type. - // - const char* tt; - if (n.type.empty ()) - { - // Empty name or '.' and '..' signify a directory. - // - if (v.empty () || v == "." || v == "..") - tt = "dir"; - else - //@@ TODO: derive type from extension. - // - tt = "file"; - } - else - tt = n.type.c_str (); - - auto i (find (tt)); - if (i == end ()) - return nullptr; - - const target_type& ti (i->second); - - // Directories require special name processing. If we find that more - // targets deviate, then we should make this target-type-specific. - // - if (ti.id == dir::static_type.id || ti.id == fsdir::static_type.id) - { - // 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 &ti; - } - // path_target // void path_target:: diff --git a/tests/cli/lib/libtest/build/root.build b/tests/cli/lib/libtest/build/root.build index f2f5ca6..4dce48b 100644 --- a/tests/cli/lib/libtest/build/root.build +++ b/tests/cli/lib/libtest/build/root.build @@ -4,4 +4,3 @@ hxx.ext = ixx.ext = ipp cxx.ext = cpp -using cli diff --git a/tests/cli/lib/libtest/test/buildfile b/tests/cli/lib/libtest/test/buildfile index 226e43d..6b0d9bd 100644 --- a/tests/cli/lib/libtest/test/buildfile +++ b/tests/cli/lib/libtest/test/buildfile @@ -1,3 +1,5 @@ +using cli + lib{test}: cxx{utility} cli.cxx{test base} extra/cxx{test} cli.cxx{test}: cli{test} cli.cxx{base}: cli{base} -- cgit v1.1