diff options
author | Boris Kolpackov <boris@codesynthesis.com> | 2022-04-06 11:26:52 +0200 |
---|---|---|
committer | Boris Kolpackov <boris@codesynthesis.com> | 2022-04-06 13:51:56 +0200 |
commit | 76be0a35f6c37cda7ba65530330f1ac246fb52a8 (patch) | |
tree | f613ceafcf6c7208984d4536653061c4e0c23be7 | |
parent | 0a9dd0c7d31cbba2170fdfda4b747a1fe5ce665a (diff) |
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.
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<cli_ext_def>, 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<exe> (perform_install_id, "bash.install", install_rule_); - bs.insert_rule<exe> (perform_uninstall_id, "bash.uninstall", install_rule_); + bs.insert_rule<exe> (perform_install_id, "bash.install", install_rule_); + bs.insert_rule<exe> (perform_uninstall_id, "bash.install", install_rule_); - bs.insert_rule<bash> (perform_install_id, "bash.install", install_rule_); - bs.insert_rule<bash> (perform_uninstall_id, "bash.uninstall", install_rule_); + bs.insert_rule<bash> (perform_install_id, "bash.install", install_rule_); + bs.insert_rule<bash> (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<bash_ext_def>, 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<wasm_ext>, &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<lib> (perform_id, 0, "bin.lib", lib_); r.insert<lib> (configure_id, 0, "bin.lib", lib_); @@ -927,7 +925,7 @@ namespace build2 &target_pattern_fix<pdb_ext>, &target_print_0_ext_verb, // Fixed extension, no use printing. &file_search, - false /* see_through */})); + target_type::flag::none})); if (cast_false<bool> (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<lib> ()); 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>, 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>, 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>, 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>, 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>, 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>, 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>, 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>, 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>, 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>, 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>, 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>, 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>, nullptr, &file_search, - false + target_type::flag::none }; const target_type libs::static_type @@ -388,7 +388,7 @@ namespace build2 &target_pattern_var<nullptr>, 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>, nullptr, &file_search, - false + target_type::flag::none }; // def @@ -467,7 +470,7 @@ namespace build2 &target_pattern_fix<def_ext>, 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<exe> (perform_install_id, x_install, ir); - r.insert<exe> (perform_uninstall_id, x_uninstall, ir); + r.insert<exe> (perform_install_id, x_install, ir); + r.insert<exe> (perform_uninstall_id, x_install, ir); - r.insert<liba> (perform_install_id, x_install, ir); - r.insert<liba> (perform_uninstall_id, x_uninstall, ir); + r.insert<liba> (perform_install_id, x_install, ir); + r.insert<liba> (perform_uninstall_id, x_install, ir); if (s) { - r.insert<libs> (perform_install_id, x_install, ir); - r.insert<libs> (perform_uninstall_id, x_uninstall, ir); + r.insert<libs> (perform_install_id, x_install, ir); + r.insert<libs> (perform_uninstall_id, x_install, ir); } const libux_install_rule& lr (*this); - r.insert<libue> (perform_install_id, x_install, lr); - r.insert<libue> (perform_uninstall_id, x_uninstall, lr); + r.insert<libue> (perform_install_id, x_install, lr); + r.insert<libue> (perform_uninstall_id, x_install, lr); - r.insert<libua> (perform_install_id, x_install, lr); - r.insert<libua> (perform_uninstall_id, x_uninstall, lr); + r.insert<libua> (perform_install_id, x_install, lr); + r.insert<libua> (perform_uninstall_id, x_install, lr); if (s) { - r.insert<libus> (perform_install_id, x_install, lr); - r.insert<libus> (perform_uninstall_id, x_uninstall, lr); + r.insert<libus> (perform_install_id, x_install, lr); + r.insert<libus> (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<h_ext_def>, nullptr, &file_search, - false + target_type::flag::none }; extern const char c_ext_def[] = "c"; @@ -51,7 +51,7 @@ namespace build2 &target_pattern_var<c_ext_def>, 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<pc_ext>, &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<pca_ext>, &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<pcs_ext>, &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<mtime_target> ( configure_id, 0, "config.file", file_rule::instance); - //@@ outer rs.insert_rule<alias> (configure_id, 0, "config.alias", alias_rule::instance); // This allows a custom configure rule while doing nothing by default. // - rs.insert_rule<target> (configure_id, 0, "config", noop_rule::instance); - rs.insert_rule<file> (configure_id, 0, "config.file", noop_rule::instance); + rs.insert_rule<target> (configure_id, 0, "config.noop", noop_rule::instance); + rs.insert_rule<file> (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<alias> (perform_id, 0, "alias", alias_rule::instance); + r.insert<alias> (perform_id, 0, "build.alias", alias_rule::instance); - r.insert<fsdir> (perform_update_id, "fsdir", fsdir_rule::instance); - r.insert<fsdir> (perform_clean_id, "fsdir", fsdir_rule::instance); + r.insert<fsdir> (perform_update_id, "build.fsdir", fsdir_rule::instance); + r.insert<fsdir> (perform_clean_id, "build.fsdir", fsdir_rule::instance); - r.insert<mtime_target> (perform_update_id, "file", file_rule::instance); - r.insert<mtime_target> (perform_clean_id, "file", file_rule::instance); + r.insert<mtime_target> (perform_update_id, "build.file", file_rule::instance); + r.insert<mtime_target> (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<hxx_ext_def>, nullptr, &file_search, - false + target_type::flag::none }; extern const char ixx_ext_def[] = "ixx"; @@ -36,7 +36,7 @@ namespace build2 &target_pattern_var<ixx_ext_def>, nullptr, &file_search, - false + target_type::flag::none }; extern const char txx_ext_def[] = "txx"; @@ -50,7 +50,7 @@ namespace build2 &target_pattern_var<txx_ext_def>, nullptr, &file_search, - false + target_type::flag::none }; extern const char cxx_ext_def[] = "cxx"; @@ -64,7 +64,7 @@ namespace build2 &target_pattern_var<cxx_ext_def>, nullptr, &file_search, - false + target_type::flag::none }; extern const char mxx_ext_def[] = "mxx"; @@ -78,7 +78,7 @@ namespace build2 &target_pattern_var<mxx_ext_def>, 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<target>(dist_id, test_id) taking precedence. // rs.insert_rule<target> (dist_id, 0, "dist", rule_); - rs.insert_rule<alias> (dist_id, 0, "dist.alias", rule_); //@@ outer? + rs.insert_rule<alias> (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<alias> (perform_install_id, "install.alias", ar); - bs.insert_rule<alias> (perform_uninstall_id, "uninstall.alias", ar); + bs.insert_rule<alias> (perform_uninstall_id, "install.alias", ar); bs.insert_rule<fsdir> (perform_install_id, "install.fsdir", dr); bs.insert_rule<fsdir> (perform_uninstall_id, "install.fsdir", dr); bs.insert_rule<file> (perform_install_id, "install.file", fr); - bs.insert_rule<file> (perform_uninstall_id, "uninstall.file", fr); + bs.insert_rule<file> (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<mtime_target> (perform_install_id, "install.group", gr); - bs.insert_rule<mtime_target> (perform_uninstall_id, "uninstall.group", gr); + bs.insert_rule<mtime_target> (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<mtime_target> ( - perform_install_id, "install.file", fr); + scope& gs (rs.global_scope ()); - rs.global_scope ().insert_rule<mtime_target> ( - perform_uninstall_id, "uninstall.file", fr); + gs.insert_rule<mtime_target> (perform_install_id, "install.file", fr); + gs.insert_rule<mtime_target> (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<reference_wrapper<target>, 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<pair<optional<string>, 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<names> ()); + + 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<reference_wrapper<target>, 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<reference_wrapper<target>, 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<string, reference_wrapper<const rule>, '.'>; + // 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<string, + reference_wrapper<const rule>, + '.'> + { + // 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<const target_type*, hint_rule_map>; + using target_type_rule_map = map<const target_type*, name_rule_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 <typename T> 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 <typename T> void - insert_rule (action_id a, string hint, const rule& r) + insert_rule (action_id a, string name, const rule& r) { - rules.insert<T> (a, move (hint), r); + rules.insert<T> (a, move (name), r); } // 0 meta-operation id is treated as an (emulated) wildcard. @@ -435,7 +435,7 @@ namespace build2 // template <typename T> 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 <typename T> inline void scope:: insert_rule (meta_operation_id mid, operation_id oid, - string hint, + string name, const rule& r) { if (mid != 0) - rules.insert<T> (mid, oid, move (hint), r); + rules.insert<T> (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<T> (mid, oid, hint, r); + rules.insert<T> (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 <typename T> 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<target_type::flag> ( + static_cast<uint64_t> (x) & static_cast<uint64_t> (y)); + } + + inline target_type::flag + operator|= (target_type::flag& x, target_type::flag y) + { + return x = static_cast<target_type::flag> ( + static_cast<uint64_t> (x) | static_cast<uint64_t> (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<string> @@ -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<man1_ext>, &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<prerequisite_target>; - // A rule match is an element of hint_rule_map. + // A rule match is an element of name_rule_map. // using rule_match = pair<const string, reference_wrapper<const rule>>; + // 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<value_type> 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<bool> (rs["install.booted"])) { rs.insert_rule<manifest> ( - 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; |