From 76be0a35f6c37cda7ba65530330f1ac246fb52a8 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Wed, 6 Apr 2022 11:26:52 +0200 Subject: Add support for rule hints A rule hint is a target attribute, for example: [rule_hint=cxx] exe{hello}: c{hello} Rule hints can be used to resolve ambiguity when multiple rules match the same target as well as to override an unambiguous match. --- build2/cli/rule.cxx | 2 +- build2/cli/rule.hxx | 2 +- build2/cli/target.cxx | 4 +- doc/manual.cli | 11 ++++ libbuild2/adhoc-rule-cxx.cxx | 2 +- libbuild2/adhoc-rule-cxx.hxx | 2 +- libbuild2/algorithm.cxx | 30 +++++----- libbuild2/bash/init.cxx | 10 ++-- libbuild2/bash/rule.cxx | 7 ++- libbuild2/bash/rule.hxx | 7 ++- libbuild2/bash/target.cxx | 2 +- libbuild2/bin/def-rule.cxx | 2 +- libbuild2/bin/def-rule.hxx | 2 +- libbuild2/bin/init.cxx | 6 +- libbuild2/bin/rule.cxx | 6 +- libbuild2/bin/rule.hxx | 6 +- libbuild2/bin/target.cxx | 55 +++++++++--------- libbuild2/c/init.cxx | 1 - libbuild2/cc/common.hxx | 9 +-- libbuild2/cc/compile-rule.cxx | 2 +- libbuild2/cc/compile-rule.hxx | 2 +- libbuild2/cc/install-rule.cxx | 17 +++--- libbuild2/cc/install-rule.hxx | 8 ++- libbuild2/cc/link-rule.cxx | 6 +- libbuild2/cc/link-rule.hxx | 8 +-- libbuild2/cc/module.cxx | 24 ++++---- libbuild2/cc/target.cxx | 12 ++-- libbuild2/config/init.cxx | 5 +- libbuild2/context.cxx | 11 ++-- libbuild2/cxx/init.cxx | 1 - libbuild2/cxx/target.cxx | 10 ++-- libbuild2/dist/init.cxx | 2 +- libbuild2/dist/rule.cxx | 2 +- libbuild2/dist/rule.hxx | 2 +- libbuild2/dump.cxx | 33 ++++++++++- libbuild2/in/rule.cxx | 2 +- libbuild2/in/rule.hxx | 2 +- libbuild2/in/target.cxx | 2 +- libbuild2/install/init.cxx | 13 ++--- libbuild2/install/rule.cxx | 12 ++-- libbuild2/install/rule.hxx | 12 ++-- libbuild2/parser.cxx | 129 +++++++++++++++++++++++++++++++++++++----- libbuild2/parser.hxx | 15 +++-- libbuild2/rule-map.hxx | 56 +++++++++++++----- libbuild2/rule.cxx | 29 +++++++--- libbuild2/rule.hxx | 50 ++++++++++++---- libbuild2/scope.hxx | 6 +- libbuild2/scope.ixx | 6 +- libbuild2/target-type.hxx | 46 ++++++++++++++- libbuild2/target.cxx | 28 ++++----- libbuild2/target.hxx | 69 +++++++++++++++++++--- libbuild2/target.ixx | 87 +++++++++++++++++++++++++++- libbuild2/test/rule.cxx | 4 +- libbuild2/test/rule.hxx | 8 +-- libbuild2/test/target.cxx | 2 +- libbuild2/version/init.cxx | 2 +- libbuild2/version/rule.cxx | 6 +- libbuild2/version/rule.hxx | 4 +- 58 files changed, 648 insertions(+), 253 deletions(-) diff --git a/build2/cli/rule.cxx b/build2/cli/rule.cxx index 5c03d9c..a11380e 100644 --- a/build2/cli/rule.cxx +++ b/build2/cli/rule.cxx @@ -41,7 +41,7 @@ namespace build2 } bool compile_rule:: - match (action a, target& t, const string&) const + match (action a, target& t) const { tracer trace ("cli::compile_rule::match"); diff --git a/build2/cli/rule.hxx b/build2/cli/rule.hxx index b3ecc2c..0538c57 100644 --- a/build2/cli/rule.hxx +++ b/build2/cli/rule.hxx @@ -29,7 +29,7 @@ namespace build2 compile_rule (data&& d): data (move (d)) {} virtual bool - match (action, target&, const string&) const override; + match (action, target&) const override; virtual recipe apply (action, target&) const override; diff --git a/build2/cli/target.cxx b/build2/cli/target.cxx index ca16044..37eee97 100644 --- a/build2/cli/target.cxx +++ b/build2/cli/target.cxx @@ -23,7 +23,7 @@ namespace build2 &target_pattern_var, nullptr, &file_search, - false + target_type::flag::none }; // cli.cxx @@ -69,7 +69,7 @@ namespace build2 nullptr, nullptr, &target_search, - true // "See through" default iteration mode. + target_type::flag::see_through // Group with "see through" iteration. }; } } diff --git a/doc/manual.cli b/doc/manual.cli index 36e36bd..b72700d 100644 --- a/doc/manual.cli +++ b/doc/manual.cli @@ -5375,10 +5375,21 @@ configuration header into two, one public and installed while the other private.| + \h1#attributes|Attributes| \N{This chapter is a work in progress and is incomplete.} +The only currently recognized target attribute is \c{rule_hint} which +specifies the rule hint. Rule hints can be used to resolve ambiguity when +multiple rules match the same target as well as to override an unambiguous +match. For example, the following rule hint makes sure our executable is +linked with the C++ compiler even though it only has C sources: + +\ +[rule_hint=cxx] exe{hello}: c{hello} +\ + \h1#name-patterns|Name Patterns| diff --git a/libbuild2/adhoc-rule-cxx.cxx b/libbuild2/adhoc-rule-cxx.cxx index 0cd2ca1..72387c1 100644 --- a/libbuild2/adhoc-rule-cxx.cxx +++ b/libbuild2/adhoc-rule-cxx.cxx @@ -20,7 +20,7 @@ namespace build2 // cxx_rule_v1 // bool cxx_rule_v1:: - match (action, target&, const string&) const + match (action, target&) const { return true; } diff --git a/libbuild2/adhoc-rule-cxx.hxx b/libbuild2/adhoc-rule-cxx.hxx index 466c0e5..29d8aa1 100644 --- a/libbuild2/adhoc-rule-cxx.hxx +++ b/libbuild2/adhoc-rule-cxx.hxx @@ -53,7 +53,7 @@ namespace build2 // Return true by default. // virtual bool - match (action, target&, const string&) const override; + match (action, target&) const override; }; // Note: not exported. diff --git a/libbuild2/algorithm.cxx b/libbuild2/algorithm.cxx index 217e7af..61ab92c 100644 --- a/libbuild2/algorithm.cxx +++ b/libbuild2/algorithm.cxx @@ -447,6 +447,8 @@ namespace build2 meta_operation_id mo (a.meta_operation ()); operation_id o (a.inner () ? a.operation () : a.outer_operation ()); + const string& hint (t.find_hint (o)); // MT-safe (target locked). + for (auto tt (&t.type ()); tt != nullptr; tt = tt->base) { // Search scopes outwards, stopping at the project root. @@ -478,23 +480,11 @@ namespace build2 if (i == ttm->end () || i->second.empty ()) continue; // No rules registered for this target type. - const auto& rules (i->second); // Hint map. + const auto& rules (i->second); // Name map. - // @@ TODO hint - // - // 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. + // Filter against the hint, if any. // - string hint; - auto rs (rules.size () == 1 + auto rs (hint.empty () ? make_pair (rules.begin (), rules.end ()) : rules.find_sub (hint)); @@ -622,8 +612,14 @@ namespace build2 if (!try_match) { - diag_record dr; - dr << fail << "no rule to " << diag_do (a, t); + diag_record dr (fail); + + if (hint.empty ()) + dr << "no rule to "; + else + dr << "no rule with hint " << hint << " to "; + + dr << diag_do (a, t); // Try to give some hints of the common causes. // diff --git a/libbuild2/bash/init.cxx b/libbuild2/bash/init.cxx index a1effa1..88c88ba 100644 --- a/libbuild2/bash/init.cxx +++ b/libbuild2/bash/init.cxx @@ -20,7 +20,7 @@ namespace build2 namespace bash { static const in_rule in_rule_; - static const install_rule install_rule_ (in_rule_); + static const install_rule install_rule_ (in_rule_, "bash.in"); bool init (scope& rs, @@ -71,11 +71,11 @@ namespace build2 if (install_loaded) { - bs.insert_rule (perform_install_id, "bash.install", install_rule_); - bs.insert_rule (perform_uninstall_id, "bash.uninstall", install_rule_); + bs.insert_rule (perform_install_id, "bash.install", install_rule_); + bs.insert_rule (perform_uninstall_id, "bash.install", install_rule_); - bs.insert_rule (perform_install_id, "bash.install", install_rule_); - bs.insert_rule (perform_uninstall_id, "bash.uninstall", install_rule_); + bs.insert_rule (perform_install_id, "bash.install", install_rule_); + bs.insert_rule (perform_uninstall_id, "bash.install", install_rule_); } return true; diff --git a/libbuild2/bash/rule.cxx b/libbuild2/bash/rule.cxx index ec24226..3048d3c 100644 --- a/libbuild2/bash/rule.cxx +++ b/libbuild2/bash/rule.cxx @@ -41,7 +41,7 @@ namespace build2 // in_rule // bool in_rule:: - match (action a, target& t, const string&) const + match (action a, target& t) const { tracer trace ("bash::in_rule::match"); @@ -424,12 +424,13 @@ namespace build2 // install_rule // bool install_rule:: - match (action a, target& t, const string& hint) const + match (action a, target& t) const { // We only want to handle installation if we are also the ones building // this target. So first run in's match(). // - return in_.match (a, t, hint) && file_rule::match (a, t, ""); + return in_.sub_match (in_name_, update_id, a, t) && + file_rule::match (a, t); } recipe install_rule:: diff --git a/libbuild2/bash/rule.hxx b/libbuild2/bash/rule.hxx index f69ac3b..1a0cb36 100644 --- a/libbuild2/bash/rule.hxx +++ b/libbuild2/bash/rule.hxx @@ -32,7 +32,7 @@ namespace build2 in_rule (): rule ("bash.in 1", "bash.in", '@', false /* strict */) {} virtual bool - match (action, target&, const string&) const override; + match (action, target&) const override; virtual recipe apply (action, target&) const override; @@ -68,16 +68,17 @@ namespace build2 class LIBBUILD2_BASH_SYMEXPORT install_rule: public install::file_rule { public: - install_rule (const in_rule& in): in_ (in) {} + install_rule (const in_rule& r, const char* n): in_ (r), in_name_ (n) {} virtual bool - match (action, target&, const string&) const override; + match (action, target&) const override; virtual recipe apply (action, target&) const override; protected: const in_rule& in_; + const string in_name_; }; } } diff --git a/libbuild2/bash/target.cxx b/libbuild2/bash/target.cxx index 6fa7cf4..5240fed 100644 --- a/libbuild2/bash/target.cxx +++ b/libbuild2/bash/target.cxx @@ -23,7 +23,7 @@ namespace build2 &target_pattern_var, nullptr, &file_search, - false + target_type::flag::none }; } } diff --git a/libbuild2/bin/def-rule.cxx b/libbuild2/bin/def-rule.cxx index 63508c5..c0e82fb 100644 --- a/libbuild2/bin/def-rule.cxx +++ b/libbuild2/bin/def-rule.cxx @@ -449,7 +449,7 @@ namespace build2 } bool def_rule:: - match (action a, target& t, const string&) const + match (action a, target& t) const { tracer trace ("bin::def_rule::match"); diff --git a/libbuild2/bin/def-rule.hxx b/libbuild2/bin/def-rule.hxx index 32423a0..acdf841 100644 --- a/libbuild2/bin/def-rule.hxx +++ b/libbuild2/bin/def-rule.hxx @@ -24,7 +24,7 @@ namespace build2 def_rule () {} virtual bool - match (action, target&, const string&) const override; + match (action, target&) const override; virtual recipe apply (action, target&) const override; diff --git a/libbuild2/bin/init.cxx b/libbuild2/bin/init.cxx index ab3980a..2b1df97 100644 --- a/libbuild2/bin/init.cxx +++ b/libbuild2/bin/init.cxx @@ -547,7 +547,7 @@ namespace build2 &target_pattern_fix, &target_print_0_ext_verb, // Fixed extension, no use printing. &file_search, - false /* see_through */})); + target_type::flag::none})); if (install_loaded) { @@ -578,8 +578,6 @@ namespace build2 // Similar to alias. // - - //@@ outer r.insert (perform_id, 0, "bin.lib", lib_); r.insert (configure_id, 0, "bin.lib", lib_); @@ -927,7 +925,7 @@ namespace build2 &target_pattern_fix, &target_print_0_ext_verb, // Fixed extension, no use printing. &file_search, - false /* see_through */})); + target_type::flag::none})); if (cast_false (rs["install.loaded"])) { diff --git a/libbuild2/bin/rule.cxx b/libbuild2/bin/rule.cxx index 021a768..85cc9de 100644 --- a/libbuild2/bin/rule.cxx +++ b/libbuild2/bin/rule.cxx @@ -20,7 +20,7 @@ namespace build2 // obj_rule // bool obj_rule:: - match (action a, target& t, const string&) const + match (action a, target& t) const { const char* n (t.dynamic_type ().name); // Ignore derived type. @@ -35,7 +35,7 @@ namespace build2 // libul_rule // bool libul_rule:: - match (action a, target& t, const string&) const + match (action a, target& t) const { fail << diag_doing (a, t) << " target group" << info << "explicitly select libua{} or libus{} member" << endf; @@ -50,7 +50,7 @@ namespace build2 // our prerequisites. // bool lib_rule:: - match (action a, target& xt, const string&) const + match (action a, target& xt) const { lib& t (xt.as ()); diff --git a/libbuild2/bin/rule.hxx b/libbuild2/bin/rule.hxx index ffb975d..8bc30c7 100644 --- a/libbuild2/bin/rule.hxx +++ b/libbuild2/bin/rule.hxx @@ -24,7 +24,7 @@ namespace build2 obj_rule () {} virtual bool - match (action, target&, const string&) const override; + match (action, target&) const override; virtual recipe apply (action, target&) const override; @@ -39,7 +39,7 @@ namespace build2 libul_rule () {} virtual bool - match (action, target&, const string&) const override; + match (action, target&) const override; virtual recipe apply (action, target&) const override; @@ -53,7 +53,7 @@ namespace build2 lib_rule () {} virtual bool - match (action, target&, const string&) const override; + match (action, target&) const override; virtual recipe apply (action, target&) const override; diff --git a/libbuild2/bin/target.cxx b/libbuild2/bin/target.cxx index bf701c9..a8d015b 100644 --- a/libbuild2/bin/target.cxx +++ b/libbuild2/bin/target.cxx @@ -21,7 +21,7 @@ namespace build2 nullptr, nullptr, &target_search, - false + target_type::flag::none }; const target_type bmix::static_type @@ -34,7 +34,7 @@ namespace build2 nullptr, nullptr, &target_search, - false + target_type::flag::none }; const target_type hbmix::static_type @@ -47,7 +47,7 @@ namespace build2 nullptr, nullptr, &target_search, - false + target_type::flag::none }; const target_type libx::static_type @@ -60,7 +60,7 @@ namespace build2 nullptr, nullptr, &target_search, - false + target_type::flag::member_hint, // Use untyped hint for group members. }; const target_type libux::static_type @@ -73,7 +73,7 @@ namespace build2 nullptr, nullptr, &target_search, - false + target_type::flag::none }; // Note that we link groups during the load phase since this is often @@ -108,7 +108,7 @@ namespace build2 &target_pattern_var, nullptr, &target_search, // Note: not _file(); don't look for an existing file. - false + target_type::flag::none }; const target_type bmie::static_type @@ -121,7 +121,7 @@ namespace build2 &target_pattern_var, nullptr, &target_search, // Note: not _file(); don't look for an existing file. - false + target_type::flag::none }; const target_type hbmie::static_type @@ -134,7 +134,7 @@ namespace build2 &target_pattern_var, nullptr, &target_search, // Note: not _file(); don't look for an existing file. - false + target_type::flag::none }; const target_type obja::static_type @@ -147,7 +147,7 @@ namespace build2 &target_pattern_var, nullptr, &target_search, // Note: not _file(); don't look for an existing file. - false + target_type::flag::none }; const target_type bmia::static_type @@ -160,7 +160,7 @@ namespace build2 &target_pattern_var, nullptr, &target_search, // Note: not _file(); don't look for an existing file. - false + target_type::flag::none }; const target_type hbmia::static_type @@ -173,7 +173,7 @@ namespace build2 &target_pattern_var, nullptr, &target_search, // Note: not _file(); don't look for an existing file. - false + target_type::flag::none }; const target_type objs::static_type @@ -186,7 +186,7 @@ namespace build2 &target_pattern_var, nullptr, &target_search, // Note: not _file(); don't look for an existing file. - false + target_type::flag::none }; const target_type bmis::static_type @@ -199,7 +199,7 @@ namespace build2 &target_pattern_var, nullptr, &target_search, // Note: not _file(); don't look for an existing file. - false + target_type::flag::none }; const target_type hbmis::static_type @@ -212,7 +212,7 @@ namespace build2 &target_pattern_var, nullptr, &target_search, // Note: not _file(); don't look for an existing file. - false + target_type::flag::none }; const target_type libue::static_type @@ -225,7 +225,7 @@ namespace build2 &target_pattern_var, nullptr, &target_search, // Note: not _file(); don't look for an existing file. - false + target_type::flag::none }; const target_type libua::static_type @@ -238,7 +238,7 @@ namespace build2 &target_pattern_var, nullptr, &target_search, // Note: not _file(); don't look for an existing file. - false + target_type::flag::none }; const target_type libus::static_type @@ -251,7 +251,7 @@ namespace build2 &target_pattern_var, nullptr, &target_search, // Note: not _file(); don't look for an existing file. - false + target_type::flag::none }; // obj{}, [h]bmi{}, and libu{} group factory. @@ -292,7 +292,7 @@ namespace build2 nullptr, nullptr, &target_search, - false + target_type::flag::member_hint, // Use untyped hint for group members. }; const target_type bmi::static_type @@ -305,7 +305,7 @@ namespace build2 nullptr, nullptr, &target_search, - false + target_type::flag::member_hint, // Use untyped hint for group members. }; const target_type hbmi::static_type @@ -318,7 +318,7 @@ namespace build2 nullptr, nullptr, &target_search, - false + target_type::flag::member_hint, // Use untyped hint for group members. }; // The same as g_factory() but without E. @@ -352,7 +352,7 @@ namespace build2 nullptr, nullptr, &target_search, - false + target_type::flag::member_hint, // Use untyped hint for group members. }; // What extensions should we use? At the outset, this is platform- @@ -375,7 +375,7 @@ namespace build2 &target_pattern_var, nullptr, &file_search, - false + target_type::flag::none }; const target_type libs::static_type @@ -388,7 +388,7 @@ namespace build2 &target_pattern_var, nullptr, &file_search, - false + target_type::flag::none }; // lib @@ -435,7 +435,10 @@ namespace build2 nullptr, nullptr, &target_search, - false // Note: not see-through ("alternatives" group). + + // Note: not see-through ("alternatives" group). + // + target_type::flag::member_hint, // Use untyped hint for group members. }; // libi @@ -450,7 +453,7 @@ namespace build2 &target_pattern_var, nullptr, &file_search, - false + target_type::flag::none }; // def @@ -467,7 +470,7 @@ namespace build2 &target_pattern_fix, nullptr, &file_search, - false + target_type::flag::none }; } } diff --git a/libbuild2/c/init.cxx b/libbuild2/c/init.cxx index 6725ca7..f39114c 100644 --- a/libbuild2/c/init.cxx +++ b/libbuild2/c/init.cxx @@ -352,7 +352,6 @@ namespace build2 "c.compile", "c.link", "c.install", - "c.uninstall", cm.x_info->id.type, cm.x_info->id.variant, diff --git a/libbuild2/cc/common.hxx b/libbuild2/cc/common.hxx index 56cde19..017573d 100644 --- a/libbuild2/cc/common.hxx +++ b/libbuild2/cc/common.hxx @@ -156,10 +156,9 @@ namespace build2 struct data: config_data { - const char* x_compile; // Rule names. - const char* x_link; - const char* x_install; - const char* x_uninstall; + string x_compile; // Rule names. + string x_link; + string x_install; // Cached values for some commonly-used variables/values. // @@ -243,7 +242,6 @@ namespace build2 const char* compile, const char* link, const char* install, - const char* uninstall, compiler_type ct, const string& cv, compiler_class cl, @@ -270,7 +268,6 @@ namespace build2 x_compile (compile), x_link (link), x_install (install), - x_uninstall (uninstall), ctype (ct), cvariant (cv), cclass (cl), cmaj (mj), cmin (mi), cvmaj (vmj), cvmin (vmi), diff --git a/libbuild2/cc/compile-rule.cxx b/libbuild2/cc/compile-rule.cxx index e7e90ad..24c9b2b 100644 --- a/libbuild2/cc/compile-rule.cxx +++ b/libbuild2/cc/compile-rule.cxx @@ -406,7 +406,7 @@ namespace build2 } bool compile_rule:: - match (action a, target& t, const string&) const + match (action a, target& t) const { tracer trace (x, "compile_rule::match"); diff --git a/libbuild2/cc/compile-rule.hxx b/libbuild2/cc/compile-rule.hxx index dbb2dd5..00965fc 100644 --- a/libbuild2/cc/compile-rule.hxx +++ b/libbuild2/cc/compile-rule.hxx @@ -45,7 +45,7 @@ namespace build2 compile_rule (data&&); virtual bool - match (action, target&, const string&) const override; + match (action, target&) const override; virtual recipe apply (action, target&) const override; diff --git a/libbuild2/cc/install-rule.cxx b/libbuild2/cc/install-rule.cxx index 560b8a7..e8c87ae 100644 --- a/libbuild2/cc/install-rule.cxx +++ b/libbuild2/cc/install-rule.cxx @@ -97,7 +97,7 @@ namespace build2 { if (header_source (p)) pt = nullptr; - else if (p.type.see_through) + else if (p.type.see_through ()) { for (i.enter_group (); i.group (); ) { @@ -151,15 +151,13 @@ namespace build2 } bool install_rule:: - match (action a, target& t, const string& hint) const + match (action a, target& t, const string&, match_extra& me) 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(). // - return link_.match (a, t, hint) && file_rule::match (a, t, ""); + return link_.sub_match (x_link, update_id, a, t, me) && + file_rule::match (a, t); } recipe install_rule:: @@ -332,7 +330,7 @@ namespace build2 { if (header_source (p)) pt = nullptr; - else if (p.type.see_through) + else if (p.type.see_through ()) { for (i.enter_group (); i.group (); ) { @@ -372,12 +370,13 @@ namespace build2 } bool libux_install_rule:: - match (action a, target& t, const string& hint) const + match (action a, target& t, const string&, match_extra& me) const { // We only want to handle installation if we are also the ones building // this target. So first run link's match(). // - return link_.match (a, t, hint) && alias_rule::match (a, t, ""); + return link_.sub_match (x_link, update_id, a, t, me) && + alias_rule::match (a, t); } } } diff --git a/libbuild2/cc/install-rule.hxx b/libbuild2/cc/install-rule.hxx index acd1bd8..70ee711 100644 --- a/libbuild2/cc/install-rule.hxx +++ b/libbuild2/cc/install-rule.hxx @@ -38,8 +38,10 @@ namespace build2 filter (const scope*, action, const target&, prerequisite_iterator&) const override; + // Note: rule::match() override. + // virtual bool - match (action, target&, const string&) const override; + match (action, target&, const string&, match_extra&) const override; virtual recipe apply (action, target&) const override; @@ -71,8 +73,10 @@ namespace build2 filter (const scope*, action, const target&, prerequisite_iterator&) const override; + // Note: rule::match() override. + // virtual bool - match (action, target&, const string&) const override; + match (action, target&, const string&, match_extra&) const override; private: const link_rule& link_; diff --git a/libbuild2/cc/link-rule.cxx b/libbuild2/cc/link-rule.cxx index b5cc63b..30024ce 100644 --- a/libbuild2/cc/link-rule.cxx +++ b/libbuild2/cc/link-rule.cxx @@ -446,7 +446,7 @@ namespace build2 } bool link_rule:: - match (action a, target& t, const string& hint) const + match (action a, target& t, const string& hint, match_extra&) const { // NOTE: may be called multiple times and for both inner and outer // operations (see the install rules). @@ -495,7 +495,7 @@ namespace build2 // We will only chain a C source if there is also an X source or we were // explicitly told to. // - if (r.seen_c && !r.seen_x && hint < x) + if (r.seen_c && !r.seen_x && hint.empty ()) { l4 ([&]{trace << "C prerequisite without " << x_lang << " or hint " << "for target " << t;}); @@ -850,7 +850,7 @@ namespace build2 }; recipe link_rule:: - apply (action a, target& xt) const + apply (action a, target& xt, match_extra&) const { tracer trace (x, "link_rule::apply"); diff --git a/libbuild2/cc/link-rule.hxx b/libbuild2/cc/link-rule.hxx index c6d06d2..f0052e9 100644 --- a/libbuild2/cc/link-rule.hxx +++ b/libbuild2/cc/link-rule.hxx @@ -18,7 +18,7 @@ namespace build2 { namespace cc { - class LIBBUILD2_CC_SYMEXPORT link_rule: public simple_rule, virtual common + class LIBBUILD2_CC_SYMEXPORT link_rule: public rule, virtual common { public: link_rule (data&&); @@ -46,10 +46,10 @@ namespace build2 match (action, const target&, const target*, otype, bool) const; virtual bool - match (action, target&, const string&) const override; + match (action, target&, const string&, match_extra&) const override; virtual recipe - apply (action, target&) const override; + apply (action, target&, match_extra&) const override; target_state perform_update (action, const target&) const; @@ -57,8 +57,6 @@ namespace build2 target_state perform_clean (action, const target&) const; - using simple_rule::match; // To make Clang happy. - public: // Library handling. // diff --git a/libbuild2/cc/module.cxx b/libbuild2/cc/module.cxx index 871cfb6..c930d49 100644 --- a/libbuild2/cc/module.cxx +++ b/libbuild2/cc/module.cxx @@ -1083,30 +1083,30 @@ namespace build2 { const install_rule& ir (*this); - r.insert (perform_install_id, x_install, ir); - r.insert (perform_uninstall_id, x_uninstall, ir); + r.insert (perform_install_id, x_install, ir); + r.insert (perform_uninstall_id, x_install, ir); - r.insert (perform_install_id, x_install, ir); - r.insert (perform_uninstall_id, x_uninstall, ir); + r.insert (perform_install_id, x_install, ir); + r.insert (perform_uninstall_id, x_install, ir); if (s) { - r.insert (perform_install_id, x_install, ir); - r.insert (perform_uninstall_id, x_uninstall, ir); + r.insert (perform_install_id, x_install, ir); + r.insert (perform_uninstall_id, x_install, ir); } const libux_install_rule& lr (*this); - r.insert (perform_install_id, x_install, lr); - r.insert (perform_uninstall_id, x_uninstall, lr); + r.insert (perform_install_id, x_install, lr); + r.insert (perform_uninstall_id, x_install, lr); - r.insert (perform_install_id, x_install, lr); - r.insert (perform_uninstall_id, x_uninstall, lr); + r.insert (perform_install_id, x_install, lr); + r.insert (perform_uninstall_id, x_install, lr); if (s) { - r.insert (perform_install_id, x_install, lr); - r.insert (perform_uninstall_id, x_uninstall, lr); + r.insert (perform_install_id, x_install, lr); + r.insert (perform_uninstall_id, x_install, lr); } } } diff --git a/libbuild2/cc/target.cxx b/libbuild2/cc/target.cxx index b17e1ef..3f71eb1 100644 --- a/libbuild2/cc/target.cxx +++ b/libbuild2/cc/target.cxx @@ -21,7 +21,7 @@ namespace build2 nullptr, nullptr, &target_search, - false + target_type::flag::none }; extern const char h_ext_def[] = "h"; @@ -36,7 +36,7 @@ namespace build2 &target_pattern_var, nullptr, &file_search, - false + target_type::flag::none }; extern const char c_ext_def[] = "c"; @@ -51,7 +51,7 @@ namespace build2 &target_pattern_var, nullptr, &file_search, - false + target_type::flag::none }; extern const char pc_ext[] = "pc"; // VC14 rejects constexpr. @@ -66,7 +66,7 @@ namespace build2 &target_pattern_fix, &target_print_0_ext_verb, // Fixed extension, no use printing. &file_search, - false + target_type::flag::none }; extern const char pca_ext[] = "static.pc"; // VC14 rejects constexpr. @@ -81,7 +81,7 @@ namespace build2 &target_pattern_fix, &target_print_0_ext_verb, // Fixed extension, no use printing. &file_search, - false + target_type::flag::none }; extern const char pcs_ext[] = "shared.pc"; // VC14 rejects constexpr. @@ -96,7 +96,7 @@ namespace build2 &target_pattern_fix, &target_print_0_ext_verb, // Fixed extension, no use printing. &file_search, - false + target_type::flag::none }; } } diff --git a/libbuild2/config/init.cxx b/libbuild2/config/init.cxx index 5dd2789..774ffb8 100644 --- a/libbuild2/config/init.cxx +++ b/libbuild2/config/init.cxx @@ -571,13 +571,12 @@ namespace build2 rs.global_scope ().insert_rule ( configure_id, 0, "config.file", file_rule::instance); - //@@ outer rs.insert_rule (configure_id, 0, "config.alias", alias_rule::instance); // This allows a custom configure rule while doing nothing by default. // - rs.insert_rule (configure_id, 0, "config", noop_rule::instance); - rs.insert_rule (configure_id, 0, "config.file", noop_rule::instance); + rs.insert_rule (configure_id, 0, "config.noop", noop_rule::instance); + rs.insert_rule (configure_id, 0, "config.noop", noop_rule::instance); return true; } diff --git a/libbuild2/context.cxx b/libbuild2/context.cxx index a052481..80343bd 100644 --- a/libbuild2/context.cxx +++ b/libbuild2/context.cxx @@ -601,14 +601,13 @@ namespace build2 { rule_map& r (gs.rules); // Note: global scope! - //@@ outer - r.insert (perform_id, 0, "alias", alias_rule::instance); + r.insert (perform_id, 0, "build.alias", alias_rule::instance); - r.insert (perform_update_id, "fsdir", fsdir_rule::instance); - r.insert (perform_clean_id, "fsdir", fsdir_rule::instance); + r.insert (perform_update_id, "build.fsdir", fsdir_rule::instance); + r.insert (perform_clean_id, "build.fsdir", fsdir_rule::instance); - r.insert (perform_update_id, "file", file_rule::instance); - r.insert (perform_clean_id, "file", file_rule::instance); + r.insert (perform_update_id, "build.file", file_rule::instance); + r.insert (perform_clean_id, "build.file", file_rule::instance); } } diff --git a/libbuild2/cxx/init.cxx b/libbuild2/cxx/init.cxx index 106668a..0ebb424 100644 --- a/libbuild2/cxx/init.cxx +++ b/libbuild2/cxx/init.cxx @@ -829,7 +829,6 @@ namespace build2 "cxx.compile", "cxx.link", "cxx.install", - "cxx.uninstall", cm.x_info->id.type, cm.x_info->id.variant, diff --git a/libbuild2/cxx/target.cxx b/libbuild2/cxx/target.cxx index 982dcb4..fc50f67 100644 --- a/libbuild2/cxx/target.cxx +++ b/libbuild2/cxx/target.cxx @@ -22,7 +22,7 @@ namespace build2 &target_pattern_var, nullptr, &file_search, - false + target_type::flag::none }; extern const char ixx_ext_def[] = "ixx"; @@ -36,7 +36,7 @@ namespace build2 &target_pattern_var, nullptr, &file_search, - false + target_type::flag::none }; extern const char txx_ext_def[] = "txx"; @@ -50,7 +50,7 @@ namespace build2 &target_pattern_var, nullptr, &file_search, - false + target_type::flag::none }; extern const char cxx_ext_def[] = "cxx"; @@ -64,7 +64,7 @@ namespace build2 &target_pattern_var, nullptr, &file_search, - false + target_type::flag::none }; extern const char mxx_ext_def[] = "mxx"; @@ -78,7 +78,7 @@ namespace build2 &target_pattern_var, nullptr, &file_search, - false + target_type::flag::none }; } } diff --git a/libbuild2/dist/init.cxx b/libbuild2/dist/init.cxx index 2be4c3f..8837f77 100644 --- a/libbuild2/dist/init.cxx +++ b/libbuild2/dist/init.cxx @@ -188,7 +188,7 @@ namespace build2 // something like insert(dist_id, test_id) taking precedence. // rs.insert_rule (dist_id, 0, "dist", rule_); - rs.insert_rule (dist_id, 0, "dist.alias", rule_); //@@ outer? + rs.insert_rule (dist_id, 0, "dist.alias", rule_); // Configuration. // diff --git a/libbuild2/dist/rule.cxx b/libbuild2/dist/rule.cxx index ef144d0..76d11c9 100644 --- a/libbuild2/dist/rule.cxx +++ b/libbuild2/dist/rule.cxx @@ -15,7 +15,7 @@ namespace build2 namespace dist { bool rule:: - match (action, target&, const string&) const + match (action, target&) const { return true; // We always match. } diff --git a/libbuild2/dist/rule.hxx b/libbuild2/dist/rule.hxx index e63016d..a864015 100644 --- a/libbuild2/dist/rule.hxx +++ b/libbuild2/dist/rule.hxx @@ -29,7 +29,7 @@ namespace build2 rule () {} virtual bool - match (action, target&, const string&) const override; + match (action, target&) const override; virtual recipe apply (action, target&) const override; diff --git a/libbuild2/dump.cxx b/libbuild2/dump.cxx index b1a16ba..befd86b 100644 --- a/libbuild2/dump.cxx +++ b/libbuild2/dump.cxx @@ -259,7 +259,38 @@ namespace build2 if (t.group != nullptr) os << ind << t << " -> " << *t.group << endl; - os << ind << t << ':'; + os << ind; + + // Target attributes. + // + if (!t.rule_hints.map.empty ()) + { + os << '['; + + bool f (true); + for (const rule_hints::value_type& v: t.rule_hints.map) + { + if (f) + f = false; + else + os << ", "; + + if (v.type != nullptr) + os << v.type->name << '@'; + + os << "rule_hint="; + + if (v.operation != default_id) + os << s.root_scope ()->root_extra->operations[v.operation]->name + << '@'; + + os << v.hint; + } + + os << "] "; + } + + os << t << ':'; // First check if this is the simple case where we can print everything // as a single declaration. diff --git a/libbuild2/in/rule.cxx b/libbuild2/in/rule.cxx index dd39485..5a6db30 100644 --- a/libbuild2/in/rule.cxx +++ b/libbuild2/in/rule.cxx @@ -23,7 +23,7 @@ namespace build2 namespace in { bool rule:: - match (action a, target& xt, const string&) const + match (action a, target& xt) const { tracer trace ("in::rule::match"); diff --git a/libbuild2/in/rule.hxx b/libbuild2/in/rule.hxx index 33caea4..98ab3b4 100644 --- a/libbuild2/in/rule.hxx +++ b/libbuild2/in/rule.hxx @@ -43,7 +43,7 @@ namespace build2 null_ (move (null)) {} virtual bool - match (action, target&, const string&) const override; + match (action, target&) const override; virtual recipe apply (action, target&) const override; diff --git a/libbuild2/in/target.cxx b/libbuild2/in/target.cxx index d9bc8a7..d548453 100644 --- a/libbuild2/in/target.cxx +++ b/libbuild2/in/target.cxx @@ -53,7 +53,7 @@ namespace build2 &in_pattern, &target_print_1_ext_verb, // Same as file. &in_search, - false + target_type::flag::none }; } } diff --git a/libbuild2/install/init.cxx b/libbuild2/install/init.cxx index 1bf1623..ef9de05 100644 --- a/libbuild2/install/init.cxx +++ b/libbuild2/install/init.cxx @@ -372,19 +372,19 @@ namespace build2 const auto& gr (group_rule_); bs.insert_rule (perform_install_id, "install.alias", ar); - bs.insert_rule (perform_uninstall_id, "uninstall.alias", ar); + bs.insert_rule (perform_uninstall_id, "install.alias", ar); bs.insert_rule (perform_install_id, "install.fsdir", dr); bs.insert_rule (perform_uninstall_id, "install.fsdir", dr); bs.insert_rule (perform_install_id, "install.file", fr); - bs.insert_rule (perform_uninstall_id, "uninstall.file", fr); + bs.insert_rule (perform_uninstall_id, "install.file", fr); // Note: use mtime_target (instead of target) to take precedence over // the fallback file rules below. // bs.insert_rule (perform_install_id, "install.group", gr); - bs.insert_rule (perform_uninstall_id, "uninstall.group", gr); + bs.insert_rule (perform_uninstall_id, "install.group", gr); // Register the fallback file rule for the update-for-[un]install // operation, similar to update. @@ -392,11 +392,10 @@ namespace build2 // @@ Hm, it's a bit fuzzy why we would be updating-for-install // something outside of any project..? // - rs.global_scope ().insert_rule ( - perform_install_id, "install.file", fr); + scope& gs (rs.global_scope ()); - rs.global_scope ().insert_rule ( - perform_uninstall_id, "uninstall.file", fr); + gs.insert_rule (perform_install_id, "install.file", fr); + gs.insert_rule (perform_uninstall_id, "install.file", fr); } // Configuration. diff --git a/libbuild2/install/rule.cxx b/libbuild2/install/rule.cxx index 468fcc3..0a1b994 100644 --- a/libbuild2/install/rule.cxx +++ b/libbuild2/install/rule.cxx @@ -53,7 +53,7 @@ namespace build2 const alias_rule alias_rule::instance; bool alias_rule:: - match (action, target&, const string&) const + match (action, target&) const { // We always match. // @@ -168,7 +168,7 @@ namespace build2 const fsdir_rule fsdir_rule::instance; bool fsdir_rule:: - match (action, target&, const string&) const + match (action, target&) const { // We always match. // @@ -203,10 +203,10 @@ namespace build2 const group_rule group_rule::instance (false /* see_through_only */); bool group_rule:: - match (action a, target& t, const string& h) const + match (action a, target& t) const { - return (!see_through || t.type ().see_through) && - alias_rule::match (a, t, h); + return (!see_through_only || t.type ().see_through ()) && + alias_rule::match (a, t); } const target* group_rule:: @@ -300,7 +300,7 @@ namespace build2 const file_rule file_rule::instance; bool file_rule:: - match (action, target&, const string&) const + match (action, target&) const { // We always match, even if this target is not installable (so that we // can ignore it; see apply()). diff --git a/libbuild2/install/rule.hxx b/libbuild2/install/rule.hxx index 79cef85..ad9d6e7 100644 --- a/libbuild2/install/rule.hxx +++ b/libbuild2/install/rule.hxx @@ -22,7 +22,7 @@ namespace build2 { public: virtual bool - match (action, target&, const string&) const override; + match (action, target&) const override; // Return NULL if this prerequisite should be ignored and pointer to its // target otherwise. @@ -54,7 +54,7 @@ namespace build2 { public: virtual bool - match (action, target&, const string&) const override; + match (action, target&) const override; virtual recipe apply (action, target&) const override; @@ -78,7 +78,7 @@ namespace build2 { public: virtual bool - match (action, target&, const string&) const override; + match (action, target&) const override; // Return NULL if this group member should be ignored and pointer to its // target otherwise. @@ -103,10 +103,10 @@ namespace build2 virtual recipe apply (action, target&) const override; - group_rule (bool see_through_only): see_through (see_through_only) {} + group_rule (bool sto): see_through_only (sto) {} static const group_rule instance; - bool see_through; + bool see_through_only; }; struct install_dir; @@ -115,7 +115,7 @@ namespace build2 { public: virtual bool - match (action, target&, const string&) const override; + match (action, target&) const override; // Return NULL if this prerequisite should be ignored and pointer to its // target otherwise. diff --git a/libbuild2/parser.cxx b/libbuild2/parser.cxx index 1e8757a..f91e85e 100644 --- a/libbuild2/parser.cxx +++ b/libbuild2/parser.cxx @@ -674,10 +674,7 @@ namespace build2 if (ns.empty ()) fail (t) << "expected target before ':'"; - if (at.first) - fail (at.second) << "attributes before target"; - else - attributes_pop (); + attributes as (attributes_pop ()); // Call the specified parsing function (variable value/block) for // one/each pattern/target. We handle multiple targets by replaying @@ -790,7 +787,7 @@ namespace build2 }; auto for_each = [this, &trace, &for_one_pat, - &t, &tt, &ns, &nloc, &ans] (auto&& f) + &t, &tt, &as, &ns, &nloc, &ans] (auto&& f) { // Note: watch out for an out-qualified single target (two names). // @@ -809,6 +806,9 @@ namespace build2 // if (n.pattern) { + if (!as.empty ()) + fail (as.loc) << "attributes before target type/pattern"; + if (n.pair) fail (nloc) << "out-qualified target type/pattern"; @@ -831,6 +831,9 @@ namespace build2 nloc, trace); + if (!as.empty ()) + apply_target_attributes (*target_, as); + // Enter ad hoc members. // if (!ans.empty ()) @@ -865,6 +868,9 @@ namespace build2 // if (ns[0].pattern && ns.size () == (ns[0].pair ? 2 : 1)) { + if (!as.empty ()) + fail (as.loc) << "attributes before target type/pattern"; + name& n (ns[0]); if (n.qualified ()) @@ -1165,7 +1171,7 @@ namespace build2 for (action a: r.actions) { - // This covers both duplicate recipe actions withing the rule + // This covers both duplicate recipe actions within the rule // pattern (similar to parse_recipe()) as well as conflicts // with other rules (ad hoc or not). // @@ -1313,7 +1319,7 @@ namespace build2 // Note also that we treat this as an explicit dependency // declaration (i.e., not implied). // - enter_targets (move (ns), nloc, move (ans), 0); + enter_targets (move (ns), nloc, move (ans), 0, as); } continue; @@ -1390,7 +1396,8 @@ namespace build2 parse_dependency (t, tt, move (ns), nloc, move (ans), - move (pns), ploc); + move (pns), ploc, + as); } continue; @@ -1943,8 +1950,7 @@ namespace build2 // // TODO: handle and erase common attributes if/when we have any. // - as = move (attributes_top ()); - attributes_pop (); + as = attributes_pop (); // Handle the buildspec. // @@ -2134,7 +2140,8 @@ namespace build2 small_vector, 1> parser:: enter_targets (names&& tns, const location& tloc, // Target names. adhoc_names&& ans, // Ad hoc target names. - size_t prereq_size) + size_t prereq_size, + const attributes& tas) // Target attributes. { // Enter all the targets (normally we will have just one) and their ad hoc // groups. @@ -2163,6 +2170,9 @@ namespace build2 false /* implied */, tloc, trace); + if (!tas.empty ()) + apply_target_attributes (*target_, tas); + // Enter ad hoc members. // if (!ans.empty ()) @@ -2184,10 +2194,90 @@ namespace build2 } void parser:: + apply_target_attributes (target& t, const attributes& as) + { + const location& l (as.loc); + + for (auto& a: as) + { + const string& n (a.name); + const value& v (a.value); + + // rule_hint= + // liba@rule_hint= + // + size_t p (string::npos); + if (n == "rule_hint" || + ((p = n.find ('@')) != string::npos && + n.compare (p + 1, string::npos, "rule_hint") == 0)) + { + // Resolve target type, if specified. + // + const target_type* tt (nullptr); + if (p != string::npos) + { + string t (n, 0, p); + tt = scope_->find_target_type (t); + + if (tt == nullptr) + fail (l) << "unknown target type " << t << " in rule_hint " + << "attribute"; + } + + // The rule hint value is vector, string>> where + // the first half is the operation and the second half is the hint. + // Absent operation is used as a fallback for update/clean. + // + const names& ns (v.as ()); + + for (auto i (ns.begin ()); i != ns.end (); ++i) + { + operation_id oi (default_id); + if (i->pair) + { + const name& n (*i++); + + if (!n.simple ()) + fail (l) << "expected operation name instead of " << n + << " in rule_hint attribute"; + + const string& v (n.value); + + if (!v.empty ()) + { + oi = ctx.operation_table.find (v); + + if (oi == 0) + fail (l) << "unknown operation " << v << " in rule_hint " + << "attribute"; + + if (root_->root_extra->operations[oi] == nullptr) + fail (l) << "project " << *root_ << " does not support " + << "operation " << ctx.operation_table[oi] + << " specified in rule_hint attribute"; + } + } + + const name& n (*i); + + if (!n.simple () || n.empty ()) + fail (l) << "expected hint instead of " << n << " in rule_hint " + << "attribute"; + + t.rule_hints.insert (tt, oi, n.value); + } + } + else + fail (l) << "unknown target attribute " << a; + } + } + + void parser:: parse_dependency (token& t, token_type& tt, names&& tns, const location& tloc, // Target names. adhoc_names&& ans, // Ad hoc target names. - names&& pns, const location& ploc) // Prereq names. + names&& pns, const location& ploc, // Prereq names. + const attributes& tas) // Target attributes. { // Parse a dependency chain and/or a target/prerequisite-specific variable // assignment/block and/or recipe block(s). @@ -2200,7 +2290,7 @@ namespace build2 // First enter all the targets. // small_vector, 1> tgs ( - enter_targets (move (tns), tloc, move (ans), pns.size ())); + enter_targets (move (tns), tloc, move (ans), pns.size (), tas)); // Now enter each prerequisite into each target. // @@ -2463,6 +2553,10 @@ namespace build2 // else { + // @@ This is actually ambiguous: prerequisite or target attributes + // (or both or neither)? Perhaps this should be prerequisites for + // the same reason as below (these are prerequsites first). + // if (at.first) fail (at.second) << "attributes before prerequisites"; else @@ -2484,7 +2578,8 @@ namespace build2 parse_dependency (t, tt, move (pns), ploc, {} /* ad hoc target name */, - move (ns), loc); + move (ns), loc, + attributes () /* target attributes */); } } } @@ -5144,6 +5239,10 @@ namespace build2 // Parse the attribute name with expansion (we rely on this in some // old and hairy tests). // + // Note that the attributes lexer mode does not recognize `{}@` as + // special and we rely on that in the rule hint attributes + // (libs@rule_hint=cxx). + // const location l (get_location (t)); names ns ( @@ -5185,6 +5284,8 @@ namespace build2 } while (tt != type::rsbrace); } + else + has = false; // `[]` doesn't count. if (tt != type::rsbrace) fail (t) << "expected ']' instead of " << t; diff --git a/libbuild2/parser.hxx b/libbuild2/parser.hxx index 1474c5e..4f105e5 100644 --- a/libbuild2/parser.hxx +++ b/libbuild2/parser.hxx @@ -162,13 +162,20 @@ namespace build2 enter_adhoc_members (adhoc_names_loc&&, bool); small_vector, 1> - enter_targets (names&&, const location&, adhoc_names&&, size_t); + enter_targets (names&&, const location&, + adhoc_names&&, + size_t, + const attributes&); + + void + apply_target_attributes (target&, const attributes&); void parse_dependency (token&, token_type&, names&&, const location&, adhoc_names&&, - names&&, const location&); + names&&, const location&, + const attributes&); void parse_assert (token&, token_type&); @@ -305,8 +312,8 @@ namespace build2 // then parse the attribute sequence until ']' storing the result in the // new stack entry. Then get the next token and, if standalone is false, // verify it is not newline/eos (i.e., there is something after it). - // Return the indication of whether we have seen `[` (even if it's the - // `[]` empty list) and its location. + // Return the indication of whether we have seen any attributes (note + // that the `[]` empty list does not count) and the location of `[`. // // Note that during pre-parsing nothing is pushed into the stack. // diff --git a/libbuild2/rule-map.hxx b/libbuild2/rule-map.hxx index d4a0f33..8f6f59f 100644 --- a/libbuild2/rule-map.hxx +++ b/libbuild2/rule-map.hxx @@ -14,10 +14,38 @@ namespace build2 { - using hint_rule_map = - butl::prefix_map, '.'>; + // A rule name is used both for diagnostics as well as to match rule hints + // (see rule_hints). A rule hint is a potentially partial rule name. + // + // The recommended rule naming scheme is to start with the module name, for + // example: cxx.compile, cxx.link. This way a rule hint can be just the + // module name, for example [rule_hint=cxx]. If a module can only possibly + // have a single rule, then the rule name can be just the module name (e.g., + // `in`; though make doubly sure there is unlikely to be a need for another + // rule, for example, for documentation generation, in the future). + // + // The two common choices of names for the second component in a rule name + // is an action (e.g., cxx.compile, cxx.link) or a target type (e.g., + // bin.def, bin.lib). The latter is a good choice when the action is + // inherent to the target type (e.g., "generate def file", "see through lib + // group"). Also note that a rule for compensating operations (e.g., + // update/clean, install/uninstall) is customarily registered with the same + // name. + // + struct name_rule_map: butl::prefix_map, + '.'> + { + // Return true if the rule name matches a rule hint. + // + static bool + sub (const string& hint, const string& name) + { + return compare_type ('.').prefix (hint, name); + } + }; - using target_type_rule_map = map; + using target_type_rule_map = map; // This is an "indexed map" with operation_id being the index. Entry // with id 0 is a wildcard. @@ -33,7 +61,7 @@ namespace build2 bool insert (operation_id oid, const target_type& tt, - string hint, + string name, const rule& r) { // 3 is the number of builtin operations. @@ -41,7 +69,7 @@ namespace build2 if (oid >= map_.size ()) map_.resize ((oid < 3 ? 3 : oid) + 1); - return map_[oid][&tt].emplace (move (hint), r).second; + return map_[oid][&tt].emplace (move (name), r).second; } // Return NULL if not found. @@ -78,17 +106,17 @@ namespace build2 bool insert (action_id a, const target_type& tt, - string hint, + string name, const rule& r) { - return insert (a >> 4, a & 0x0F, tt, move (hint), r); + return insert (a >> 4, a & 0x0F, tt, move (name), r); } template bool - insert (action_id a, string hint, const rule& r) + insert (action_id a, string name, const rule& r) { - return insert (a, T::static_type, move (hint), r); + return insert (a, T::static_type, move (name), r); } // 0 oid is a wildcard. @@ -97,17 +125,17 @@ namespace build2 insert (meta_operation_id mid, operation_id oid, const target_type& tt, - string hint, + string name, const rule& r) { if (mid_ == mid) - return map_.insert (oid, tt, move (hint), r); + return map_.insert (oid, tt, move (name), r); else { if (next_ == nullptr) next_.reset (new rule_map (mid)); - return next_->insert (mid, oid, tt, move (hint), r); + return next_->insert (mid, oid, tt, move (name), r); } } @@ -115,10 +143,10 @@ namespace build2 bool insert (meta_operation_id mid, operation_id oid, - string hint, + string name, const rule& r) { - return insert (mid, oid, T::static_type, move (hint), r); + return insert (mid, oid, T::static_type, move (name), r); } // Return NULL if not found. diff --git a/libbuild2/rule.cxx b/libbuild2/rule.cxx index c573339..acb46e8 100644 --- a/libbuild2/rule.cxx +++ b/libbuild2/rule.cxx @@ -22,12 +22,20 @@ namespace build2 { } + bool rule:: + sub_match (const string& n, operation_id o, + action a, target& t, match_extra& me) const + { + const string& h (t.find_hint (o)); + return name_rule_map::sub (h, n) && match (a, t, h, me); + } + // simple_rule // bool simple_rule:: - match (action a, target& t, const string& h, match_extra&) const + match (action a, target& t, const string&, match_extra&) const { - return match (a, t, h); + return match (a, t); } recipe simple_rule:: @@ -36,6 +44,13 @@ namespace build2 return apply (a, t); } + bool simple_rule:: + sub_match (const string& n, operation_id o, + action a, target& t) const + { + return name_rule_map::sub (t.find_hint (o), n) && match (a, t); + } + // file_rule // // Note that this rule is special. It is the last, fallback rule. If @@ -46,7 +61,7 @@ namespace build2 // use it as a guide to implement your own, normal, rules. // bool file_rule:: - match (action a, target& t, const string&) const + match (action a, target& t, const string&, match_extra&) const { tracer trace ("file_rule::match"); @@ -124,7 +139,7 @@ namespace build2 } recipe file_rule:: - apply (action a, target& t) const + apply (action a, target& t, match_extra&) const { // Update triggers the update of this target's prerequisites so it would // seem natural that we should also trigger their cleanup. However, this @@ -161,7 +176,7 @@ namespace build2 // alias_rule // bool alias_rule:: - match (action, target&, const string&) const + match (action, target&) const { return true; } @@ -183,7 +198,7 @@ namespace build2 // fsdir_rule // bool fsdir_rule:: - match (action, target&, const string&) const + match (action, target&) const { return true; } @@ -318,7 +333,7 @@ namespace build2 // noop_rule // bool noop_rule:: - match (action, target&, const string&) const + match (action, target&) const { return true; } diff --git a/libbuild2/rule.hxx b/libbuild2/rule.hxx index 73492fe..1429ab2 100644 --- a/libbuild2/rule.hxx +++ b/libbuild2/rule.hxx @@ -24,6 +24,10 @@ namespace build2 // // Note: match() is only called once but may not be followed by apply(). // + // The hint argument is the rule hint, if any, that was used to select this + // rule. While normally not factored into the match decision, a rule may + // "try harder" if a hint was specified (see cc::link_rule for an example). + // // The match_extra argument (the type is defined in target.hxx) is used to // pass additional information that is only needed by some rule // implementations. It is also a way for us to later pass more information @@ -47,15 +51,29 @@ namespace build2 rule (const rule&) = delete; rule& operator= (const rule&) = delete; + + // Sometimes we want to match only if another rule of ours would match + // another operation. For example, we would want our install rule to match + // only if our update rule also matches. + // + // Arranging this, however, is not a simple matter of calling the other + // rule's match(): we also have to take into account the rule hints for + // that operation. This helper performs all the necessary steps. Note: + // should only be called from match() (see target::find_hint() for + // details). + // + bool + sub_match (const string& rule_name, operation_id hint_op, + action, target&, match_extra&) const; }; - // Simplified interface for rules that don't care about the extras. + // Simplified interface for rules that don't care about the hint or extras. // class LIBBUILD2_SYMEXPORT simple_rule: public rule { public: virtual bool - match (action, target&, const string& hint) const = 0; + match (action, target&) const = 0; virtual recipe apply (action, target&) const = 0; @@ -65,19 +83,31 @@ namespace build2 virtual recipe apply (action, target&, match_extra&) const override; + + // The simplified version of sub_match() above. + // + // Note that it calls the simplified match() directly rather than going + // through the original. + // + bool + sub_match (const string& rule_name, operation_id hint_op, + action, target&) const; }; // Fallback rule that only matches if the file exists. It will also match // an mtime_target provided it has a set timestamp. // - class LIBBUILD2_SYMEXPORT file_rule: public simple_rule + // Note: this rule is "hot" because it matches every static source file and + // so we don't use simple_rule to avoid two extra virtual calls. + // + class LIBBUILD2_SYMEXPORT file_rule: public rule { public: virtual bool - match (action, target&, const string&) const override; + match (action, target&, const string&, match_extra&) const override; virtual recipe - apply (action, target&) const override; + apply (action, target&, match_extra&) const override; file_rule () {} @@ -89,7 +119,7 @@ namespace build2 { public: virtual bool - match (action, target&, const string&) const override; + match (action, target&) const override; virtual recipe apply (action, target&) const override; @@ -105,7 +135,7 @@ namespace build2 { public: virtual bool - match (action, target&, const string&) const override; + match (action, target&) const override; virtual recipe apply (action, target&) const override; @@ -132,7 +162,7 @@ namespace build2 { public: virtual bool - match (action, target&, const string&) const override; + match (action, target&) const override; virtual recipe apply (action, target&) const override; @@ -219,8 +249,8 @@ namespace build2 // Implementation details. // public: - // The name in rule_match is used as a hint and as a name in diagnostics. - // The former does not apply to ad hoc recipes (but does apply to ad hoc + // The name in rule_match is used to match hints and in diagnostics. The + // former does not apply to ad hoc recipes (but does apply to ad hoc // rules). // const build2::rule_match rule_match; diff --git a/libbuild2/scope.hxx b/libbuild2/scope.hxx index 12fd22c..86f5e4b 100644 --- a/libbuild2/scope.hxx +++ b/libbuild2/scope.hxx @@ -422,9 +422,9 @@ namespace build2 template void - insert_rule (action_id a, string hint, const rule& r) + insert_rule (action_id a, string name, const rule& r) { - rules.insert (a, move (hint), r); + rules.insert (a, move (name), r); } // 0 meta-operation id is treated as an (emulated) wildcard. @@ -435,7 +435,7 @@ namespace build2 // template void - insert_rule (meta_operation_id, operation_id, string hint, const rule&); + insert_rule (meta_operation_id, operation_id, string name, const rule&); // Operation callbacks. // diff --git a/libbuild2/scope.ixx b/libbuild2/scope.ixx index d297720..5d76a7f 100644 --- a/libbuild2/scope.ixx +++ b/libbuild2/scope.ixx @@ -176,11 +176,11 @@ namespace build2 template inline void scope:: insert_rule (meta_operation_id mid, operation_id oid, - string hint, + string name, const rule& r) { if (mid != 0) - rules.insert (mid, oid, move (hint), r); + rules.insert (mid, oid, move (name), r); else { auto& ms (root_scope ()->root_extra->meta_operations); @@ -198,7 +198,7 @@ namespace build2 mid != info_id && mid != create_id && mid != disfigure_id) - rules.insert (mid, oid, hint, r); + rules.insert (mid, oid, name, r); } } } diff --git a/libbuild2/target-type.hxx b/libbuild2/target-type.hxx index ef3a3ed..09bc316 100644 --- a/libbuild2/target-type.hxx +++ b/libbuild2/target-type.hxx @@ -93,7 +93,25 @@ namespace build2 const target* (*search) (const target&, const prerequisite_key&); - bool see_through; // A group with the default "see through" semantics. + // Target type flags. + // + // Note that the member_hint flag should only be used on groups with + // link-up during load (see lib{}, for example). In particular, if the + // group link-up only happens during match, then the hint would be looked + // up before the group is known. + // + enum class flag: uint64_t + { + none = 0, + group = 0x01, // A (non-adhoc) group. + see_through = group | 0x02, // A group with "see through" semantics. + member_hint = group | 0x04 // Untyped rule hint applies to members. + }; + + flag flags; + + bool + see_through () const; template bool @@ -125,6 +143,32 @@ namespace build2 inline ostream& operator<< (ostream& os, const target_type& tt) {return os << tt.name;} + inline target_type::flag + operator&= (target_type::flag& x, target_type::flag y) + { + return x = static_cast ( + static_cast (x) & static_cast (y)); + } + + inline target_type::flag + operator|= (target_type::flag& x, target_type::flag y) + { + return x = static_cast ( + static_cast (x) | static_cast (y)); + } + + inline target_type::flag + operator& (target_type::flag x, target_type::flag y) {return x &= y;} + + inline target_type::flag + operator| (target_type::flag x, target_type::flag y) {return x |= y;} + + inline bool target_type:: + see_through () const + { + return (flags & flag::see_through) == flag::see_through; + } + // Target type map. // class target_type_map diff --git a/libbuild2/target.cxx b/libbuild2/target.cxx index ba1454e..92db7e9 100644 --- a/libbuild2/target.cxx +++ b/libbuild2/target.cxx @@ -1092,7 +1092,7 @@ namespace build2 nullptr, nullptr, &target_search, - false + target_type::flag::none, }; const target_type mtime_target::static_type @@ -1105,7 +1105,7 @@ namespace build2 nullptr, nullptr, &target_search, - false + target_type::flag::none }; const target_type path_target::static_type @@ -1118,7 +1118,7 @@ namespace build2 nullptr, nullptr, &target_search, - false + target_type::flag::none }; const target_type file::static_type @@ -1131,7 +1131,7 @@ namespace build2 nullptr, /* pattern */ &target_print_1_ext_verb, // Print extension even at verbosity level 0. &file_search, - false + target_type::flag::none }; static const target* @@ -1158,7 +1158,7 @@ namespace build2 nullptr, nullptr, &alias_search, - false + target_type::flag::none }; // dir @@ -1374,7 +1374,7 @@ namespace build2 &dir_pattern, nullptr, &dir_search, - false + target_type::flag::none }; const target_type fsdir::static_type @@ -1387,7 +1387,7 @@ namespace build2 &dir_pattern, nullptr, &target_search, - false + target_type::flag::none }; static optional @@ -1455,7 +1455,7 @@ namespace build2 #endif nullptr, &file_search, - false + target_type::flag::none }; static const char* @@ -1541,7 +1541,7 @@ namespace build2 &buildfile_target_pattern, nullptr, &file_search, - false + target_type::flag::none }; const target_type doc::static_type @@ -1554,7 +1554,7 @@ namespace build2 nullptr, /* pattern */ // Same as file. &target_print_1_ext_verb, // Same as file. &file_search, - false + target_type::flag::none }; const target_type legal::static_type @@ -1567,7 +1567,7 @@ namespace build2 nullptr, /* pattern */ // Same as file. &target_print_1_ext_verb, // Same as file. &file_search, - false + target_type::flag::none }; const target_type man::static_type @@ -1580,7 +1580,7 @@ namespace build2 nullptr, &target_print_1_ext_verb, // Print extension even at verbosity level 0. &file_search, - false + target_type::flag::none }; extern const char man1_ext[] = "1"; // VC14 rejects constexpr. @@ -1595,7 +1595,7 @@ namespace build2 &target_pattern_fix, &target_print_0_ext_verb, // Fixed extension, no use printing. &file_search, - false + target_type::flag::none }; static const char* @@ -1644,6 +1644,6 @@ namespace build2 &manifest_target_pattern, nullptr, &file_search, - false + target_type::flag::none }; } diff --git a/libbuild2/target.hxx b/libbuild2/target.hxx index 2820aa7..4c51e2e 100644 --- a/libbuild2/target.hxx +++ b/libbuild2/target.hxx @@ -99,15 +99,53 @@ namespace build2 }; using prerequisite_targets = vector; - // A rule match is an element of hint_rule_map. + // A rule match is an element of name_rule_map. // using rule_match = pair>; + // A map of target type plus operation ids to rule hints (see name_rule_map + // for details on rule names and hints). The default_id serves as a fallback + // for update and clean operations. + // + // Note that for now hints are tried in the order specified and the first + // that matches, used. + // + struct rule_hints + { + // Return empty string if not found. + // + const string& + find (const target_type&, operation_id, bool untyped) const; + + bool + empty () const {return map.empty ();} + + // Note that insertion of an existing entry overrides the old value. + // + void + insert (const target_type*, operation_id, string); + + struct value_type + { + const target_type* type; + operation_id operation; + string hint; + }; + + vector map; + }; + // Additional information about a rule match (see rule.hxx for details). // + // Note that passing this information to a base rule's match() as-is may or + // may not be correct. If some changes must be made (for example, the + // fallback flag must be cleared), then that should be done by modifying + // (and restoring, if necessary) the passed instance rather than making a + // copy (which would not survive to apply()). + // struct match_extra { - bool fallback; // True if matching a fallback rule. + bool fallback; // True if matching a fallback rule (see match_rule()). string buffer; // Auxiliary buffer that's reused during match/apply. // Implementation details. @@ -211,15 +249,15 @@ namespace build2 // obj{}). // // In an all-group, when a group is updated, normally all its members are - // updates (and usually with a single command), though there could be some + // updated (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 an all-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. + // rather than the group target. 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 + // flag 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. @@ -491,6 +529,19 @@ namespace build2 value& append (const variable&); + + // Rule hints. + // + public: + build2::rule_hints rule_hints; + + // Find the rule hint for the specified operation taking into account the + // target type/group. Note: racy with regards to the group link-up and + // should only be called when safe. + // + const string& + find_hint (operation_id) const; + // Ad hoc recipes. // public: @@ -545,7 +596,7 @@ namespace build2 // build2::match_extra match_extra; - // Matched rule (pointer to hint_rule_map element). Note that in case of + // Matched rule (pointer to name_rule_map element). Note that in case of // a direct recipe assignment we may not have a rule (NULL). // const rule_match* rule; @@ -1229,7 +1280,7 @@ namespace build2 { if (r_->mode_ != members_mode::never && i_ != r_->e_ && - i_->type.see_through) + i_->type.see_through ()) switch_mode (); } @@ -1257,7 +1308,7 @@ namespace build2 // // for (...; ++i) // { - // if (i->prerequisite.type.see_through) + // if (i->prerequisite.type.see_through ()) // { // for (i.enter_group (); i.group (); ) // { diff --git a/libbuild2/target.ixx b/libbuild2/target.ixx index 8f0768e..af75cd1 100644 --- a/libbuild2/target.ixx +++ b/libbuild2/target.ixx @@ -53,6 +53,91 @@ namespace build2 return r; } + // rule_hints + // + inline const string& rule_hints:: + find (const target_type& tt, operation_id o, bool ut) const + { + // Look for fallback during the same iteration. + // + const value_type* f (nullptr); + + for (const value_type& v: map) + { + if (!(v.type == nullptr ? ut : tt.is_a (*v.type))) + continue; + + if (v.operation == o) + return v.hint; + + if (f == nullptr && + v.operation == default_id && + (o == update_id || o == clean_id)) + f = &v; + } + + return f != nullptr ? f->hint : empty_string; + } + + inline void rule_hints:: + insert (const target_type* tt, operation_id o, string h) + { + auto i (find_if (map.begin (), map.end (), + [tt, o] (const value_type& v) + { + return v.operation == o && v.type == tt; + })); + + if (i == map.end ()) + map.push_back (value_type {tt, o, move (h)}); + else + i->hint = move (h); + } + + inline const string& target:: + find_hint (operation_id o) const + { + using flag = target_type::flag; + + const target_type* tt (nullptr); // Resolve lazily. + + // First check the target itself. + // + if (!rule_hints.empty ()) + { + // If this is a group that "gave" its untyped hints to the members, then + // ignore untyped entries. + // + tt = &type (); + bool ut ((tt->flags & flag::member_hint) != flag::member_hint); + + const string& r (rule_hints.find (*tt, o, ut)); + if (!r.empty ()) + return r; + } + + // Then check the group. + // + if (const target* g = group) + { + if (!g->rule_hints.empty ()) + { + // If the group "gave" its untyped hints to the members, then don't + // ignore untyped entries. + // + const target_type& gt (g->type ()); + bool ut ((gt.flags & flag::member_hint) == flag::member_hint); + + if (tt == nullptr) + tt = &type (); + + return g->rule_hints.find (*tt, o, ut); + } + } + + return empty_string; + } + // match_extra // inline void match_extra:: @@ -524,7 +609,7 @@ namespace build2 if (r_->mode_ != members_mode::never && i_ != r_->e_ && - i_->type.see_through) + i_->type.see_through ()) switch_mode (); } diff --git a/libbuild2/test/rule.cxx b/libbuild2/test/rule.cxx index 06fb12f..142bded 100644 --- a/libbuild2/test/rule.cxx +++ b/libbuild2/test/rule.cxx @@ -30,7 +30,7 @@ namespace build2 namespace test { bool rule:: - match (action, target&, const string&) const + match (action, target&) const { // We always match, even if this target is not testable (so that we can // ignore it; see apply()). @@ -66,7 +66,7 @@ namespace build2 // Resolve group members. // - if (!see_through || t.type ().see_through) + if (!see_through_only || t.type ().see_through ()) { // Remember that we are called twice: first during update for test // (pre-operation) and then during test. During the former, we rely on diff --git a/libbuild2/test/rule.hxx b/libbuild2/test/rule.hxx index e96b68b..6fcf208 100644 --- a/libbuild2/test/rule.hxx +++ b/libbuild2/test/rule.hxx @@ -20,7 +20,7 @@ namespace build2 { public: virtual bool - match (action, target&, const string&) const override; + match (action, target&) const override; virtual recipe apply (action, target&) const override; @@ -34,10 +34,10 @@ namespace build2 target_state perform_script (action, const target&, size_t) const; - rule (common_data&& d, bool see_through_only) - : common (move (d)), see_through (see_through_only) {} + rule (common_data&& d, bool sto) + : common (move (d)), see_through_only (sto) {} - bool see_through; + bool see_through_only; }; class default_rule: public rule diff --git a/libbuild2/test/target.cxx b/libbuild2/test/target.cxx index ce88baa..852abdf 100644 --- a/libbuild2/test/target.cxx +++ b/libbuild2/test/target.cxx @@ -56,7 +56,7 @@ namespace build2 &testscript_target_pattern, nullptr, &file_search, - false + target_type::flag::none }; } } diff --git a/libbuild2/version/init.cxx b/libbuild2/version/init.cxx index abcb728..b3657bc 100644 --- a/libbuild2/version/init.cxx +++ b/libbuild2/version/init.cxx @@ -390,7 +390,7 @@ namespace build2 if (cast_false (rs["install.booted"])) { rs.insert_rule ( - perform_install_id, "version.manifest", manifest_install_rule_); + perform_install_id, "version.install", manifest_install_rule_); } return true; diff --git a/libbuild2/version/rule.cxx b/libbuild2/version/rule.cxx index 3da69cc..ad26da4 100644 --- a/libbuild2/version/rule.cxx +++ b/libbuild2/version/rule.cxx @@ -47,7 +47,7 @@ namespace build2 // in_rule // bool in_rule:: - match (action a, target& xt, const string&) const + match (action a, target& xt) const { tracer trace ("version::in_rule::match"); @@ -302,7 +302,7 @@ namespace build2 // manifest_install_rule // bool manifest_install_rule:: - match (action a, target& t, const string&) const + match (action a, target& t) const { // We only match project's manifest. // @@ -315,7 +315,7 @@ namespace build2 if (s.root_scope () != &s || s.src_path () != t.dir) return false; - return file_rule::match (a, t, ""); + return file_rule::match (a, t); } auto_rmfile manifest_install_rule:: diff --git a/libbuild2/version/rule.hxx b/libbuild2/version/rule.hxx index c174f40..55b4aee 100644 --- a/libbuild2/version/rule.hxx +++ b/libbuild2/version/rule.hxx @@ -23,7 +23,7 @@ namespace build2 in_rule (): rule ("version.in 2", "version.in") {} virtual bool - match (action, target&, const string&) const override; + match (action, target&) const override; virtual string lookup (const location&, @@ -42,7 +42,7 @@ namespace build2 manifest_install_rule () {} virtual bool - match (action, target&, const string&) const override; + match (action, target&) const override; virtual auto_rmfile install_pre (const file&, const install_dir&) const override; -- cgit v1.1