diff options
Diffstat (limited to 'libbuild2/build')
-rw-r--r-- | libbuild2/build/script/builtin-options.cxx | 72 | ||||
-rw-r--r-- | libbuild2/build/script/builtin-options.hxx | 68 | ||||
-rw-r--r-- | libbuild2/build/script/builtin-options.ixx | 120 | ||||
-rw-r--r-- | libbuild2/build/script/builtin.cli | 72 | ||||
-rw-r--r-- | libbuild2/build/script/parser.cxx | 908 | ||||
-rw-r--r-- | libbuild2/build/script/parser.hxx | 55 | ||||
-rw-r--r-- | libbuild2/build/script/runner.cxx | 35 | ||||
-rw-r--r-- | libbuild2/build/script/script.cxx | 44 | ||||
-rw-r--r-- | libbuild2/build/script/script.hxx | 3 |
9 files changed, 1234 insertions, 143 deletions
diff --git a/libbuild2/build/script/builtin-options.cxx b/libbuild2/build/script/builtin-options.cxx index f7ba0e7..dba3c59 100644 --- a/libbuild2/build/script/builtin-options.cxx +++ b/libbuild2/build/script/builtin-options.cxx @@ -188,6 +188,56 @@ namespace build2 } }; + template <typename K, typename V, typename C> + struct parser<std::multimap<K, V, C> > + { + static void + parse (std::multimap<K, V, C>& m, bool& xs, scanner& s) + { + const char* o (s.next ()); + + if (s.more ()) + { + std::size_t pos (s.position ()); + std::string ov (s.next ()); + std::string::size_type p = ov.find ('='); + + K k = K (); + V v = V (); + std::string kstr (ov, 0, p); + std::string vstr (ov, (p != std::string::npos ? p + 1 : ov.size ())); + + int ac (2); + char* av[] = + { + const_cast<char*> (o), + 0 + }; + + bool dummy; + if (!kstr.empty ()) + { + av[1] = const_cast<char*> (kstr.c_str ()); + argv_scanner s (0, ac, av, false, pos); + parser<K>::parse (k, dummy, s); + } + + if (!vstr.empty ()) + { + av[1] = const_cast<char*> (vstr.c_str ()); + argv_scanner s (0, ac, av, false, pos); + parser<V>::parse (v, dummy, s); + } + + m.insert (typename std::multimap<K, V, C>::value_type (k, v)); + } + else + throw missing_value (o); + + xs = true; + } + }; + template <typename X, typename T, T X::*M> void thunk (X& x, scanner& s) @@ -239,7 +289,15 @@ namespace build2 adhoc_ (), cwd_ (), cwd_specified_ (false), - drop_cycles_ () + drop_cycles_ (), + target_what_ (), + target_what_specified_ (false), + target_default_type_ (), + target_default_type_specified_ (false), + target_extension_type_ (), + target_extension_type_specified_ (false), + target_cwd_ (), + target_cwd_specified_ (false) { } @@ -341,6 +399,18 @@ namespace build2 &depdb_dyndep_options::cwd_specified_ >; _cli_depdb_dyndep_options_map_["--drop-cycles"] = &::build2::build::cli::thunk< depdb_dyndep_options, &depdb_dyndep_options::drop_cycles_ >; + _cli_depdb_dyndep_options_map_["--target-what"] = + &::build2::build::cli::thunk< depdb_dyndep_options, string, &depdb_dyndep_options::target_what_, + &depdb_dyndep_options::target_what_specified_ >; + _cli_depdb_dyndep_options_map_["--target-default-type"] = + &::build2::build::cli::thunk< depdb_dyndep_options, string, &depdb_dyndep_options::target_default_type_, + &depdb_dyndep_options::target_default_type_specified_ >; + _cli_depdb_dyndep_options_map_["--target-extension-type"] = + &::build2::build::cli::thunk< depdb_dyndep_options, map<string, string>, &depdb_dyndep_options::target_extension_type_, + &depdb_dyndep_options::target_extension_type_specified_ >; + _cli_depdb_dyndep_options_map_["--target-cwd"] = + &::build2::build::cli::thunk< depdb_dyndep_options, dir_path, &depdb_dyndep_options::target_cwd_, + &depdb_dyndep_options::target_cwd_specified_ >; } }; diff --git a/libbuild2/build/script/builtin-options.hxx b/libbuild2/build/script/builtin-options.hxx index 590d3b2..a8c3440 100644 --- a/libbuild2/build/script/builtin-options.hxx +++ b/libbuild2/build/script/builtin-options.hxx @@ -174,6 +174,66 @@ namespace build2 void drop_cycles (const bool&); + const string& + target_what () const; + + string& + target_what (); + + void + target_what (const string&); + + bool + target_what_specified () const; + + void + target_what_specified (bool); + + const string& + target_default_type () const; + + string& + target_default_type (); + + void + target_default_type (const string&); + + bool + target_default_type_specified () const; + + void + target_default_type_specified (bool); + + const map<string, string>& + target_extension_type () const; + + map<string, string>& + target_extension_type (); + + void + target_extension_type (const map<string, string>&); + + bool + target_extension_type_specified () const; + + void + target_extension_type_specified (bool); + + const dir_path& + target_cwd () const; + + dir_path& + target_cwd (); + + void + target_cwd (const dir_path&); + + bool + target_cwd_specified () const; + + void + target_cwd_specified (bool); + // Implementation details. // protected: @@ -201,6 +261,14 @@ namespace build2 dir_path cwd_; bool cwd_specified_; bool drop_cycles_; + string target_what_; + bool target_what_specified_; + string target_default_type_; + bool target_default_type_specified_; + map<string, string> target_extension_type_; + bool target_extension_type_specified_; + dir_path target_cwd_; + bool target_cwd_specified_; }; } } diff --git a/libbuild2/build/script/builtin-options.ixx b/libbuild2/build/script/builtin-options.ixx index ea06a0f..20847c2 100644 --- a/libbuild2/build/script/builtin-options.ixx +++ b/libbuild2/build/script/builtin-options.ixx @@ -233,6 +233,126 @@ namespace build2 { this->drop_cycles_ = x; } + + inline const string& depdb_dyndep_options:: + target_what () const + { + return this->target_what_; + } + + inline string& depdb_dyndep_options:: + target_what () + { + return this->target_what_; + } + + inline void depdb_dyndep_options:: + target_what (const string& x) + { + this->target_what_ = x; + } + + inline bool depdb_dyndep_options:: + target_what_specified () const + { + return this->target_what_specified_; + } + + inline void depdb_dyndep_options:: + target_what_specified (bool x) + { + this->target_what_specified_ = x; + } + + inline const string& depdb_dyndep_options:: + target_default_type () const + { + return this->target_default_type_; + } + + inline string& depdb_dyndep_options:: + target_default_type () + { + return this->target_default_type_; + } + + inline void depdb_dyndep_options:: + target_default_type (const string& x) + { + this->target_default_type_ = x; + } + + inline bool depdb_dyndep_options:: + target_default_type_specified () const + { + return this->target_default_type_specified_; + } + + inline void depdb_dyndep_options:: + target_default_type_specified (bool x) + { + this->target_default_type_specified_ = x; + } + + inline const map<string, string>& depdb_dyndep_options:: + target_extension_type () const + { + return this->target_extension_type_; + } + + inline map<string, string>& depdb_dyndep_options:: + target_extension_type () + { + return this->target_extension_type_; + } + + inline void depdb_dyndep_options:: + target_extension_type (const map<string, string>& x) + { + this->target_extension_type_ = x; + } + + inline bool depdb_dyndep_options:: + target_extension_type_specified () const + { + return this->target_extension_type_specified_; + } + + inline void depdb_dyndep_options:: + target_extension_type_specified (bool x) + { + this->target_extension_type_specified_ = x; + } + + inline const dir_path& depdb_dyndep_options:: + target_cwd () const + { + return this->target_cwd_; + } + + inline dir_path& depdb_dyndep_options:: + target_cwd () + { + return this->target_cwd_; + } + + inline void depdb_dyndep_options:: + target_cwd (const dir_path& x) + { + this->target_cwd_ = x; + } + + inline bool depdb_dyndep_options:: + target_cwd_specified () const + { + return this->target_cwd_specified_; + } + + inline void depdb_dyndep_options:: + target_cwd_specified (bool x) + { + this->target_cwd_specified_ = x; + } } } } diff --git a/libbuild2/build/script/builtin.cli b/libbuild2/build/script/builtin.cli index 7d0936f..5aea034 100644 --- a/libbuild2/build/script/builtin.cli +++ b/libbuild2/build/script/builtin.cli @@ -17,8 +17,8 @@ namespace build2 // class depdb_dyndep_options { - // Note that --byproduct, if any, must be the first option and is - // handled ad hoc, kind of as a sub-command. + // Note that --byproduct or --dyn-target, if any, must be the first + // option and is handled ad hoc. // // Similarly, --update-{include,exclude} are handled ad hoc and must // be literals, similar to the -- separator. They specify prerequisite @@ -40,23 +40,48 @@ namespace build2 // with support for generated files (and thus -I) at least in the make // format where we use relative paths for non-existent files. // + // Currently Supported dependency formats (--format) are `make` + // (default) and `lines`. + // + // The `make` format is the make dependency declaration in the + // `<target>...: [<prerequisite>...]` form. In the non-byproduct mode + // a relative prerequisite path is considered non-existent. + // + // The `lines` format lists targets and/or prerequisites one per line. + // If the --dyn-target option is specified then the target list is + // expected to come first separated from the prerequisites list with a + // blank line. If there are no prerequisites, then the blank line can + // be omitted. If the --dyn-target option is not specified, then all + // lines are treated as prerequisites and there should be no blank + // lines. In the non-byproduct mode a prerequisite line that starts + // with a leading space is considered a non-existent prerequisite. + // Currently only relative non-existent prerequisites are supported. + // Finally, in this mode, if the prerequisite is syntactically a + // directory (that is, it ends with a trailing directory separator), + // then it is added as fsdir{}. This can be used to handle situations + // where the dynamic targets are placed into subdirectories. + // // Note on naming: whenever we (may) have two options, one for target // and the other for prerequisite, we omit "prerequisite" as that's // what we extract by default and most commonly. For example: // - // --what --what-target - // --default-type --default-target-type + // --what --target-what + // --default-type --target-default-type // path --file; // Read from file rather than stdin. - string --format; // Dependency format: make (default). + string --format; // Dependency format: `make` (default), + // or `lines`. - string --what; // Dependency kind, e.g., "header". + // Dynamic dependency extraction options. + // + string --what; // Prerequisite kind, e.g., "header". - dir_paths --include-path|-I; // Search paths for generated files. + dir_paths --include-path|-I; // Search paths for generated + // prerequisites. - string --default-type; // Default prerequisite type to use - // if none could be derived from ext. + string --default-type; // Default prerequisite type to use if + // none could be derived from extension. bool --adhoc; // Treat dynamically discovered // prerequisites as ad hoc (so they @@ -64,14 +89,39 @@ namespace build2 // normal mode). dir_path --cwd; // Builtin's working directory used - // to complete relative paths (only - // in --byproduct mode). + // to complete relative paths of + // prerequisites (only in --byproduct + // mode, lines format for existing + // paths). bool --drop-cycles; // Drop prerequisites that are also // targets. Only use if you are sure // such cycles are harmless, that is, // the output is not affected by such // prerequisites' content. + + // Dynamic target extraction options. + // + // This functionality is enabled with the --dyn-target option. Only + // the make format is supported, where the listed targets are added as + // ad hoc group members (unless already specified as static members). + // This functionality is not available in the byproduct mode. + // + string --target-what; // Target kind, e.g., "source". + + string --target-default-type; // Default target type to use if none + // could be derived from extension. + + map<string, string> // Extension to target type mapping in + --target-extension-type; // the <ext>=<type> form, for example, + // h=hxx. This mapping is considered + // before attempting to automatically + // map the extension and so can be used + // to resolve ambiguities. + + dir_path --target-cwd; // Builtin's working directory used to + // complete relative paths of targets. + }; } } diff --git a/libbuild2/build/script/parser.cxx b/libbuild2/build/script/parser.cxx index df0a419..3ecf23d 100644 --- a/libbuild2/build/script/parser.cxx +++ b/libbuild2/build/script/parser.cxx @@ -47,7 +47,7 @@ namespace build2 { path_ = &pn; - pre_parse_ = true; + top_pre_parse_ = pre_parse_ = true; lexer l (is, *path_, line, lexer_mode::command_line); set_lexer (&l); @@ -61,7 +61,7 @@ namespace build2 pbase_ = scope_->src_path_; - file_based_ = tt.is_a<file> (); + file_based_ = tt.is_a<file> () || tt.is_a<group> (); perform_update_ = find (as.begin (), as.end (), perform_update_id) != as.end (); @@ -158,6 +158,7 @@ namespace build2 { s.depdb_dyndep = depdb_dyndep_->second; s.depdb_dyndep_byproduct = depdb_dyndep_byproduct_; + s.depdb_dyndep_dyn_target = depdb_dyndep_dyn_target_; } s.depdb_preamble = move (depdb_preamble_); @@ -747,8 +748,8 @@ namespace build2 } if (!file_based_) - fail (l) << "'depdb' builtin can only be used for file-based " - << "targets"; + fail (l) << "'depdb' builtin can only be used for file- or " + << "file group-based targets"; if (!diag_preamble_.empty ()) fail (diag_loc ()) << "'diag' builtin call before 'depdb' call" << @@ -830,8 +831,18 @@ namespace build2 fail (l) << "multiple 'depdb dyndep' calls" << info (depdb_dyndep_->first) << "previous call is here"; - if (peek () == type::word && peeked ().value == "--byproduct") - depdb_dyndep_byproduct_ = true; + if (peek () == type::word) + { + const string& v (peeked ().value); + + // Note: --byproduct and --dyn-target are mutually + // exclusive. + // + if (v == "--byproduct") + depdb_dyndep_byproduct_ = true; + else if (v == "--dyn-target") + depdb_dyndep_dyn_target_ = true; + } } else { @@ -962,6 +973,11 @@ namespace build2 if (!skip_diag) { + // Sanity check: we should not be suspending the pre-parse mode + // turned on by the base parser. + // + assert (top_pre_parse_); + pre_parse_ = false; // Make parse_names() perform expansions. pre_parse_suspended_ = true; } @@ -1136,6 +1152,8 @@ namespace build2 { if (!qs) { + // This could be a script from src so search like a prerequisite. + // if (const target* t = search_existing ( ns[0], *scope_, ns[0].pair ? ns[1].dir : empty_dir_path)) { @@ -1276,10 +1294,11 @@ namespace build2 } void parser:: - exec_depdb_preamble (action a, const scope& bs, const file& t, + exec_depdb_preamble (action a, const scope& bs, const target& t, environment& e, const script& s, runner& r, lines_iterator begin, lines_iterator end, depdb& dd, + dynamic_targets* dyn_targets, bool* update, optional<timestamp> mt, bool* deferred_failure, @@ -1302,18 +1321,23 @@ namespace build2 action a; const scope& bs; - const file& t; + const target& t; environment& env; const script& scr; depdb& dd; + dynamic_targets* dyn_targets; bool* update; bool* deferred_failure; optional<timestamp> mt; dyndep_byproduct* byp; - } data {trace, a, bs, t, e, s, dd, update, deferred_failure, mt, byp}; + } data { + trace, + a, bs, t, + e, s, + dd, dyn_targets, update, deferred_failure, mt, byp}; auto exec_cmd = [this, &data] (token& t, build2::script::token_type& tt, @@ -1343,8 +1367,9 @@ namespace build2 // exec_depdb_dyndep (t, tt, li, ll, - data.a, data.bs, const_cast<file&> (data.t), + data.a, data.bs, const_cast<target&> (data.t), data.dd, + *data.dyn_targets, *data.update, *data.mt, *data.deferred_failure, @@ -1354,35 +1379,29 @@ namespace build2 { names ns (exec_special (t, tt, true /* skip <cmd> */)); + string v; + const char* w (nullptr); if (cmd == "hash") { sha256 cs; for (const name& n: ns) to_checksum (cs, n); - if (data.dd.expect (cs.string ()) != nullptr) - l4 ([&] { - data.trace (ll) - << "'depdb hash' argument change forcing update of " - << data.t;}); + v = cs.string (); + w = "argument"; } else if (cmd == "string") { - string s; try { - s = convert<string> (move (ns)); + v = convert<string> (move (ns)); } catch (const invalid_argument& e) { fail (ll) << "invalid 'depdb string' argument: " << e; } - if (data.dd.expect (s) != nullptr) - l4 ([&] { - data.trace (ll) - << "'depdb string' argument change forcing update of " - << data.t;}); + w = "argument"; } else if (cmd == "env") { @@ -1403,14 +1422,32 @@ namespace build2 fail (ll) << pf << e; } - if (data.dd.expect (cs.string ()) != nullptr) - l4 ([&] { - data.trace (ll) - << "'depdb env' environment change forcing update of " - << data.t;}); + v = cs.string (); + w = "environment"; } else assert (false); + + // Prefix the value with the type letter. This serves two + // purposes: + // + // 1. It makes sure the result is never a blank line. We use + // blank lines as anchors to skip directly to certain entries + // (e.g., dynamic targets). + // + // 2. It allows us to detect the beginning of prerequisites + // since an absolute path will be distinguishable from these + // entries (in the future we may want to add an explicit + // blank after such custom entries to make this easier). + // + v.insert (0, 1, ' '); + v.insert (0, 1, cmd[0]); // `h`, `s`, or `e` + + if (data.dd.expect (v) != nullptr) + l4 ([&] { + data.trace (ll) + << "'depdb " << cmd << "' " << w << " change forcing " + << "update of " << data.t;}); } } else @@ -1515,7 +1552,7 @@ namespace build2 { path_ = nullptr; // Set by replays. - pre_parse_ = false; + top_pre_parse_ = pre_parse_ = false; set_lexer (nullptr); @@ -1615,8 +1652,9 @@ namespace build2 void parser:: exec_depdb_dyndep (token& lt, build2::script::token_type& ltt, size_t li, const location& ll, - action a, const scope& bs, file& t, + action a, const scope& bs, target& t, depdb& dd, + dynamic_targets& dyn_targets, bool& update, timestamp mt, bool& deferred_failure, @@ -1629,6 +1667,7 @@ namespace build2 depdb_dyndep_options ops; bool prog (false); bool byprod (false); + bool dyn_tgt (false); // Prerequisite update filter (--update-*). // @@ -1671,11 +1710,9 @@ namespace build2 next (t, tt); // Skip the 'dyndep' command. - if (tt == type::word && t.value == "--byproduct") - { - byprod = true; + if (tt == type::word && ((byprod = (t.value == "--byproduct")) || + (dyn_tgt = (t.value == "--dyn-target")))) next (t, tt); - } assert (byprod == (byprod_result != nullptr)); @@ -1894,10 +1931,23 @@ namespace build2 continue; } - // Handle --byproduct in the wrong place. + // Handle --byproduct and --dyn-target in the wrong place. // if (strcmp (a, "--byproduct") == 0) - fail (ll) << "depdb dyndep: --byproduct must be first option"; + { + fail (ll) << "depdb dyndep: " + << (dyn_tgt + ? "--byproduct specified with --dyn-target" + : "--byproduct must be first option"); + } + + if (strcmp (a, "--dyn-target") == 0) + { + fail (ll) << "depdb dyndep: " + << (byprod + ? "--dyn-target specified with --byproduct" + : "--dyn-target must be first option"); + } // Handle non-literal --update-*. // @@ -1922,29 +1972,31 @@ namespace build2 } } - // --what - // - const char* what (ops.what_specified () - ? ops.what ().c_str () - : "file"); - // --format // dyndep_format format (dyndep_format::make); - if (ops.format_specified ()) { const string& f (ops.format ()); - if (f != "make") + if (f == "lines") format = dyndep_format::lines; + else if (f != "make") fail (ll) << "depdb dyndep: invalid --format option value '" << f << "'"; } + // Prerequisite-specific options. + // + + // --what + // + const char* what (ops.what_specified () + ? ops.what ().c_str () + : "file"); + // --cwd // optional<dir_path> cwd; - if (ops.cwd_specified ()) { if (!byprod) @@ -1964,28 +2016,6 @@ namespace build2 fail (ll) << "depdb dyndep: -I specified with --byproduct"; } - // --file - // - // Note that if --file is specified without a program, then we assume - // it is one of the static prerequisites. - // - optional<path> file; - - if (ops.file_specified ()) - { - file = move (ops.file ()); - - if (file->relative ()) - { - if (!cwd) - fail (ll) << "depdb dyndep: relative path specified with --file"; - - *file = *cwd / *file; - } - } - else if (!prog) - fail (ll) << "depdb dyndep: program or --file expected"; - // --default-type // // Get the default prerequisite type falling back to file{} if not @@ -1997,7 +2027,7 @@ namespace build2 // translation unit would want to make sure it resolves extracted // system headers to h{} targets analogous to the c module's rule. // - const target_type* def_pt; + const target_type* def_pt (&file::static_type); if (ops.default_type_specified ()) { const string& t (ops.default_type ()); @@ -2005,10 +2035,8 @@ namespace build2 def_pt = bs.find_target_type (t); if (def_pt == nullptr) fail (ll) << "depdb dyndep: unknown target type '" << t - << "' specific with --default-type"; + << "' specified with --default-type"; } - else - def_pt = &file::static_type; // --adhoc // @@ -2018,6 +2046,93 @@ namespace build2 fail (ll) << "depdb dyndep: --adhoc specified with --byproduct"; } + // Target-specific options. + // + + // --target-what + // + const char* what_tgt ("file"); + if (ops.target_what_specified ()) + { + if (!dyn_tgt) + fail (ll) << "depdb dyndep: --target-what specified without " + << "--dyn-target"; + + what_tgt = ops.target_what ().c_str (); + } + + // --target-cwd + // + optional<dir_path> cwd_tgt; + if (ops.target_cwd_specified ()) + { + if (!dyn_tgt) + fail (ll) << "depdb dyndep: --target-cwd specified without " + << "--dyn-target"; + + cwd_tgt = move (ops.target_cwd ()); + + if (cwd_tgt->relative ()) + fail (ll) << "depdb dyndep: relative path specified with " + << "--target-cwd"; + } + + // --target-default-type + // + const target_type* def_tt (&file::static_type); + if (ops.target_default_type_specified ()) + { + if (!dyn_tgt) + fail (ll) << "depdb dyndep: --target-default-type specified " + << "without --dyn-target"; + + const string& t (ops.target_default_type ()); + + def_tt = bs.find_target_type (t); + if (def_tt == nullptr) + fail (ll) << "depdb dyndep: unknown target type '" << t + << "' specified with --target-default-type"; + } + + map<string, const target_type*> map_tt; + if (ops.target_extension_type_specified ()) + { + if (!dyn_tgt) + fail (ll) << "depdb dyndep: --target-extension-type specified " + << "without --dyn-target"; + + for (pair<const string, string>& p: ops.target_extension_type ()) + { + const target_type* tt (bs.find_target_type (p.second)); + if (tt == nullptr) + fail (ll) << "depdb dyndep: unknown target type '" << p.second + << "' specified with --target-extension-type"; + + map_tt[p.first] = tt; + } + } + + // --file (last since need --*cwd) + // + // Note that if --file is specified without a program, then we assume + // it is one of the static prerequisites. + // + optional<path> file; + if (ops.file_specified ()) + { + file = move (ops.file ()); + + if (file->relative ()) + { + if (!cwd && !cwd_tgt) + fail (ll) << "depdb dyndep: relative path specified with --file"; + + *file = (cwd ? *cwd : *cwd_tgt) / *file; + } + } + else if (!prog) + fail (ll) << "depdb dyndep: program or --file expected"; + // Update prerequisite targets. // using dyndep = dyndep_rule; @@ -2148,6 +2263,10 @@ namespace build2 return; } + const scope& rs (*bs.root_scope ()); + + group* g (t.is_a<group> ()); // If not group then file. + // This code is based on the prior work in the cc module (specifically // extract_headers()) where you can often find more detailed rationale // for some of the steps performed. @@ -2245,9 +2364,29 @@ namespace build2 command_expr cmd; srcout_map so_map; + // Save/restore script cleanups. + // + struct cleanups + { + build2::script::cleanups ordinary; + paths special; + }; + optional<cleanups> script_cleanups; + + auto cleanups_guard = make_guard ( + [this, &script_cleanups] () + { + if (script_cleanups) + { + swap (environment_->cleanups, script_cleanups->ordinary); + swap (environment_->special_cleanups, script_cleanups->special); + } + }); + auto init_run = [this, &ctx, <, <t, &ll, - prog, &file, &ops, &cmd, &so_map] () + prog, &file, &ops, + &cmd, &so_map, &script_cleanups] () { // Populate the srcout map with the -I$out_base -I$src_base pairs. // @@ -2260,6 +2399,10 @@ namespace build2 if (prog) { + script_cleanups = cleanups {}; + swap (environment_->cleanups, script_cleanups->ordinary); + swap (environment_->special_cleanups, script_cleanups->special); + cmd = parse_command_line (lt, static_cast<token_type&> (ltt)); // If the output goes to stdout, then this should be a single @@ -2275,15 +2418,10 @@ namespace build2 // they include the line index in their names to avoid clashes // between lines). // - // Cleanups are not an issue, they will simply replaced. And + // Cleanups are not an issue, they will simply be replaced. And // overriding the contents of the special files seems harmless and // consistent with what would happen if the command redirects its // output to a non-special file. - // - if (file) - environment_->clean ( - {build2::script::cleanup_type::always, *file}, - true /* implicit */); } }; @@ -2293,7 +2431,7 @@ namespace build2 size_t skip_count (0); auto add = [this, &trace, what, - a, &bs, &t, &pts, pts_n = pts.size (), + a, &bs, &t, g, &pts, pts_n = pts.size (), &ops, &map_ext, def_pt, &pfx_map, &so_map, &dd, &skip_count] (path fp, size_t* skip, @@ -2303,6 +2441,61 @@ namespace build2 bool cache (skip == nullptr); + // Handle fsdir{} prerequisite separately. + // + // Note: inspired by inject_fsdir(). + // + if (fp.to_directory ()) + { + if (!cache) + { + // Note: already absolute since cannot be non-existent. + // + fp.normalize (); + } + + const fsdir* dt (&search<fsdir> (t, + path_cast<dir_path> (fp), + dir_path (), + string (), nullptr, nullptr)); + + // Subset of code for file below. + // + if (!cache) + { + for (size_t i (0); i != pts_n; ++i) + { + const prerequisite_target& p (pts[i]); + + if (const target* pt = + (p.target != nullptr ? p.target : + p.adhoc () ? reinterpret_cast<target*> (p.data) : + nullptr)) + { + if (dt == pt) + return false; + } + } + + if (*skip != 0) + { + --(*skip); + return false; + } + } + + match_sync (a, *dt); + pts.push_back ( + prerequisite_target ( + nullptr, true /* adhoc */, reinterpret_cast<uintptr_t> (dt))); + + if (!cache) + dd.expect (fp.representation ()); + + skip_count++; + return false; + } + // We can only defer the failure if we will be running the recipe // body. // @@ -2357,13 +2550,26 @@ namespace build2 // Skip if this is one of the targets. // + // Note that for dynamic targets this only works if we see the + // targets before prerequisites (like in the make dependency + // format). + // if (ops.drop_cycles ()) { - for (const target* m (&t); m != nullptr; m = m->adhoc_member) + if (g != nullptr) { - if (ft == m) + auto& ms (g->members); + if (find (ms.begin (), ms.end (), ft) != ms.end ()) return false; } + else + { + for (const target* m (&t); m != nullptr; m = m->adhoc_member) + { + if (ft == m) + return false; + } + } } // Skip until where we left off. @@ -2392,10 +2598,15 @@ namespace build2 { prerequisite_target& pt (pts.back ()); + // Note: set the include_target flag for consistency (the + // updated_during_match() check does not apply since it's a + // dynamic prerequisite). + // if (pt.adhoc ()) { pt.data = reinterpret_cast<uintptr_t> (pt.target); pt.target = nullptr; + pt.include |= prerequisite_target::include_target; } else pt.data = 1; // Already updated. @@ -2429,13 +2640,27 @@ namespace build2 << t; }); + // While in the make format targets come before prerequisites, in + // depdb we store them after since any change to prerequisites can + // invalidate the set of targets. So we save them first and process + // later. + // + // Note also that we need to return them to the caller in case we are + // updating. + // If nothing so far has invalidated the dependency database, then try // the cached data before running the program. // bool cache (!update); + bool skip_blank (false); for (bool restart (true), first_run (true); restart; cache = false) { + // Clear the state in case we are restarting. + // + if (dyn_tgt) + dyn_targets.clear (); + restart = false; if (cache) @@ -2444,7 +2669,8 @@ namespace build2 // assert (skip_count == 0); - // We should always end with a blank line. + // We should always end with a blank line after the list of + // dynamic prerequisites. // for (;;) { @@ -2458,8 +2684,11 @@ namespace build2 break; } - if (l->empty ()) // Done, nothing changed. - return; + if (l->empty ()) // Done with prerequisites, nothing changed. + { + skip_blank = true; + break; + } if (optional<bool> r = add (path (move (*l)), nullptr, mt)) { @@ -2481,6 +2710,52 @@ namespace build2 return; } } + + if (!restart) // Nothing changed. + { + if (dyn_tgt) + { + // We should always end with a blank line after the list of + // dynamic targets. + // + for (;;) + { + string* l (dd.read ()); + + // If the line is invalid, run the compiler. + // + if (l == nullptr) + { + restart = true; + break; + } + + if (l->empty ()) // Done with targets. + break; + + // Split into type and path (see below for background). + // + size_t p (l->find (' ')); + if (p == string::npos || // Invalid format. + p == 0 || // Empty type. + p + 1 == l->size ()) // Empty path. + { + dd.write (); // Invalidate this line. + restart = true; + break; + } + + string t (*l, 0, p); + l->erase (0, p + 1); + + dyn_targets.push_back ( + dynamic_target {move (t), path (move (*l))}); + } + } + + if (!restart) // Done, nothing changed. + break; // Break earliy to keep cache=true. + } } else { @@ -2489,9 +2764,16 @@ namespace build2 init_run (); first_run = false; } - else if (!prog) + else { - fail (ll) << "generated " << what << " without program to retry"; + if (!prog) + fail (ll) << "generated " << what << " without program to retry"; + + // Drop dyndep cleanups accumulated on the previous run. + // + assert (script_cleanups); // Sanity check. + environment_->cleanups.clear (); + environment_->special_cleanups.clear (); } // Save the timestamp just before we run the command. If we depend @@ -2545,8 +2827,17 @@ namespace build2 iss.exceptions (istream::badbit); } else + { build2::script::run ( *environment_, cmd, nullptr /* iteration_index */, li, ll); + + // Note: make it a maybe-cleanup in case the command cleans it + // up itself. + // + environment_->clean ( + {build2::script::cleanup_type::maybe, *file}, + true /* implicit */); + } } ifdstream ifs (ifdstream::badbit); @@ -2614,32 +2905,72 @@ namespace build2 if (r.second.empty ()) continue; - // @@ TODO: what should we do about targets? + // Skip targets unless requested to extract. // - // Note that if we take GCC as an example, things are + // BTW, if you are wondering why don't we extract targets + // by default, take GCC as an example, where things are // quite messed up: by default it ignores -o and just // takes the source file name and replaces the extension // with a platform-appropriate object file extension. One // can specify a custom target (or even multiple targets) - // with -MT or with -MQ (quoting). Though MinGW GCC still - // does not quote `:` with -MQ. So in this case it's + // with -MT or with -MQ (quoting). So in this case it's // definitely easier for the user to ignore the targets // and just specify everything in the buildfile. // - // On the other hand, other tools are likely to produce - // more sensible output (except perhaps for quoting). - // - // @@ Maybe in the lax mode we should only recognize `:` - // if it's separated on at least one side? - // - // Alternatively, we could detect Windows drives in - // paths and "handle" them (I believe this is what GNU - // make does). Maybe we should have three formats: - // make-lax, make, make-strict? - // if (r.first == make_type::target) + { + // NOTE: similar code below. + // + if (dyn_tgt) + { + path& f (r.second); + + if (f.relative ()) + { + if (!cwd_tgt) + fail (il) << "relative " << what_tgt + << " target path '" << f + << "' in make dependency declaration" << + info << "consider using --target-cwd to specify " + << "relative path base"; + + f = *cwd_tgt / f; + } + + // Note that unlike prerequisites, here we don't need + // normalize_external() since we expect the targets to + // be within this project. + // + try + { + f.normalize (); + } + catch (const invalid_path&) + { + fail (il) << "invalid " << what_tgt << " target " + << "path '" << f.string () << "'"; + } + + // The target must be within this project. + // + if (!f.sub (rs.out_path ())) + { + fail (il) << what_tgt << " target path " << f + << " must be inside project output " + << "directory " << rs.out_path (); + } + + // Note: type is resolved later. + // + dyn_targets.push_back ( + dynamic_target {string (), move (f)}); + } + continue; + } + // NOTE: similar code below. + // if (optional<bool> u = add (move (r.second), &skip, rmt)) { restart = *u; @@ -2667,20 +2998,380 @@ namespace build2 break; } - break; + break; // case + } + case dyndep_format::lines: + { + bool tgt (dyn_tgt); // Reading targets or prerequisites. + + for (string l; !restart; ++il.line) // Reuse the buffer. + { + if (eof (getline (is, l))) + break; + + if (l.empty ()) + { + if (!tgt) + fail (il) << "blank line in prerequisites list"; + + tgt = false; // Targets/prerequisites separating blank. + continue; + } + + // See if this line start with space to indicate a non- + // existent prerequisite. This variable serves both as a + // flag and as a position of the beginning of the path. + // + size_t n (l.front () == ' ' ? 1 : 0); + + if (tgt) + { + // NOTE: similar code above. + // + path f; + try + { + // Non-existent target doesn't make sense. + // + if (n) + throw invalid_path (""); + + f = path (l); + + if (f.relative ()) + { + if (!cwd_tgt) + fail (il) << "relative " << what_tgt + << " target path '" << f + << "' in lines dependency declaration" << + info << "consider using --target-cwd to specify " + << "relative path base"; + + f = *cwd_tgt / f; + } + + // Note that unlike prerequisites, here we don't need + // normalize_external() since we expect the targets to + // be within this project. + // + f.normalize (); + } + catch (const invalid_path&) + { + fail (il) << "invalid " << what_tgt << " target path '" + << l << "'"; + } + + // The target must be within this project. + // + if (!f.sub (rs.out_path ())) + { + fail (il) << what_tgt << " target path " << f + << " must be inside project output directory " + << rs.out_path (); + } + + // Note: type is resolved later. + // + dyn_targets.push_back ( + dynamic_target {string (), move (f)}); + } + else + { + path f; + try + { + f = path (l.c_str () + n, l.size () - n); + + if (f.empty () || + (n && f.to_directory ())) // Non-existent fsdir{}. + throw invalid_path (""); + + if (f.relative ()) + { + if (!n) + { + if (!cwd) + fail (il) << "relative " << what + << " prerequisite path '" << f + << "' in lines dependency declaration" << + info << "consider using --cwd to specify " + << "relative path base"; + + f = *cwd / f; + } + } + else if (n) + { + // @@ TODO: non-existent absolute paths. + // + throw invalid_path (""); + } + } + catch (const invalid_path&) + { + fail (il) << "invalid " << what << " prerequisite path '" + << l << "'"; + } + + // NOTE: similar code above. + // + if (optional<bool> u = add (move (f), &skip, rmt)) + { + restart = *u; + + if (restart) + { + update = true; + l6 ([&]{trace << "restarting";}); + } + } + else + { + // Trigger recompilation, mark as expected to fail, and + // bail out. + // + update = true; + deferred_failure = true; + break; + } + } + } + + break; // case } } + if (file) + ifs.close (); + // Bail out early if we have deferred a failure. // if (deferred_failure) return; + + // Clean after each depdb-dyndep execution. + // + if (prog) + clean (*environment_, ll); } } - // Add the terminating blank line (we are updating depdb). + // Add the dynamic prerequisites terminating blank line if we are + // updating depdb and unless it's already there. + // + if (!cache && !skip_blank) + dd.expect (""); + + // Handle dynamic targets. // - dd.expect (""); + if (dyn_tgt) + { + if (g != nullptr && g->members_static == 0 && dyn_targets.empty ()) + fail (ll) << "group " << *g << " has no static or dynamic members"; + + // There is one more level (at least that we know of) to this rabbit + // hole: if the set of dynamic targets changes between clean and + // update and we do a `clean update` batch, then we will end up with + // old targets (as entered by clean from old depdb information) + // being present during update. So we need to clean them out. + // + // Optimize this for a first/single batch (common case) by noticing + // that there are only real targets to start with. + // + // Note that this doesn't affect explicit groups where we reset the + // members on each update (see adhoc_rule_buildscript::apply()). + // + optional<vector<const target*>> dts; + if (g == nullptr) + { + for (const target* m (&t); m != nullptr; m = m->adhoc_member) + { + if (m->decl != target_decl::real) + dts = vector<const target*> (); + } + } + + struct map_ext_data + { + const char* what_tgt; + const map<string, const target_type*>& map_tt; + const path* f; // Updated on each iteration. + } d {what_tgt, map_tt, nullptr}; + + function<dyndep::map_extension_func> map_ext ( + [this, &d] (const scope& bs, const string& n, const string& e) + { + small_vector<const target_type*, 2> tts; + + // Check the custom mapping first. + // + auto i (d.map_tt.find (e)); + if (i != d.map_tt.end ()) + tts.push_back (i->second); + else + { + tts = dyndep::map_extension (bs, n, e, nullptr); + + // Issue custom diagnostics suggesting --target-extension-type. + // + if (tts.size () > 1) + { + diag_record dr (fail); + + dr << "mapping of " << d.what_tgt << " target path " << *d.f + << " to target type is ambiguous"; + + for (const target_type* tt: tts) + dr << info << "can be " << tt->name << "{}"; + + dr << info << "use --target-extension-type to provide custom " + << "mapping"; + } + } + + return tts; + }); + + function<dyndep::group_filter_func> filter; + if (g != nullptr) + { + // Skip static/duplicate members in explicit group. + // + filter = [] (mtime_target& g, const build2::file& m) + { + auto& ms (g.as<group> ().members); + return find (ms.begin (), ms.end (), &m) == ms.end (); + }; + } + + // Unlike for prerequisites, for targets we store in depdb both the + // resolved target type and path. The target type is used in clean + // (see adhoc_rule_buildscript::apply()) where we cannot easily get + // hold of all the dyndep options to map the path to target type. + // So the format of the target line is: + // + // <type> <path> + // + string l; // Reuse the buffer. + for (dynamic_target& dt: dyn_targets) + { + const path& f (dt.path); + + d.f = &f; // Current file being mapped. + + // Note that this logic should be consistent with what we have in + // adhoc_buildscript_rule::apply() for perform_clean. + // + const build2::file* ft (nullptr); + if (g != nullptr) + { + pair<const build2::file&, bool> r ( + dyndep::inject_group_member ( + what_tgt, + a, bs, *g, + f, // Can't move since need to return dyn_targets. + map_ext, *def_tt, filter)); + + // Note: no target_decl shenanigans since reset the members on + // each update. + // + if (!r.second) + { + dt.type.clear (); // Static indicator. + continue; + } + + ft = &r.first; + + // Note: we only currently support dynamic file members so it + // will be file if first. + // + g->members.push_back (ft); + } + else + { + pair<const build2::file&, bool> r ( + dyndep::inject_adhoc_group_member ( + what_tgt, + a, bs, t, + f, // Can't move since need to return dyn_targets. + map_ext, *def_tt)); + + // Note that we have to track the dynamic target even if it was + // already a member (think `b update && b clean update`). + // + if (!r.second && r.first.decl == target_decl::real) + { + dt.type.clear (); // Static indicator. + continue; + } + + ft = &r.first; + + if (dts) + dts->push_back (ft); + } + + const char* tn (ft->type ().name); + + if (dt.type.empty ()) + dt.type = tn; + else if (dt.type != tn) + { + // This can, for example, happen if the user changed the + // extension to target type mapping. Say swapped extension + // variable values of two target types. + // + fail << "mapping of " << what_tgt << " target path " << f + << " to target type has changed" << + info << "previously mapped to " << dt.type << "{}" << + info << "now mapped to " << tn << "{}" << + info << "perform from scratch rebuild of " << t; + } + + if (!cache) + { + l = dt.type; + l += ' '; + l += f.string (); + dd.expect (l); + } + } + + // Add the dynamic targets terminating blank line. + // + if (!cache) + dd.expect (""); + + // Clean out old dynamic targets (skip the primary member). + // + if (dts) + { + assert (g == nullptr); + + for (target* p (&t); p->adhoc_member != nullptr; ) + { + target* m (p->adhoc_member); + + if (m->decl != target_decl::real) + { + // While there could be quite a few dynamic targets (think + // something like Doxygen), this will hopefully be optimized + // down to a contiguous memory region scan for an integer and + // so should be fast. + // + if (find (dts->begin (), dts->end (), m) == dts->end ()) + { + p->adhoc_member = m->adhoc_member; // Drop m. + continue; + } + } + + p = m; + } + } + } // Reload $< and $> to make sure they contain the newly discovered // prerequisites and targets. @@ -2711,6 +3402,12 @@ namespace build2 { lookup r; + // Note that pre-parse can be switched on by the base parser even + // during execute. + // + if (!top_pre_parse_) + return r; + // Add the variable name skipping special variables and suppressing // duplicates, unless the default variables change tracking is // canceled with `depdb clear`. While at it, check if the script @@ -2793,7 +3490,10 @@ namespace build2 void parser:: lookup_function (string&& name, const location& loc) { - if (perform_update_ && file_based_ && !impure_func_) + // Note that pre-parse can be switched on by the base parser even + // during execute. + // + if (top_pre_parse_ && perform_update_ && file_based_ && !impure_func_) { const function_overloads* f (ctx->functions.find (name)); diff --git a/libbuild2/build/script/parser.hxx b/libbuild2/build/script/parser.hxx index 70e24aa..8f86b24 100644 --- a/libbuild2/build/script/parser.hxx +++ b/libbuild2/build/script/parser.hxx @@ -103,8 +103,10 @@ namespace build2 // runner's enter() function is called before the first preamble/body // command execution and leave() -- after the last command. // + // Note: target must be file or group. + // void - execute_depdb_preamble (action a, const scope& base, const file& t, + execute_depdb_preamble (action a, const scope& base, const target& t, environment& e, const script& s, runner& r, depdb& dd) { @@ -119,18 +121,28 @@ namespace build2 dd); } + struct dynamic_target + { + string type; // Target type name (absent if static member). + build2::path path; + }; + + using dynamic_targets = vector<dynamic_target>; + void execute_depdb_preamble_dyndep ( - action a, const scope& base, file& t, + action a, const scope& base, target& t, environment& e, const script& s, runner& r, - depdb& dd, bool& update, timestamp mt, bool& deferred_failure) + depdb& dd, + dynamic_targets& dyn_targets, + bool& update, timestamp mt, bool& deferred_failure) { exec_depdb_preamble ( a, base, t, e, s, r, s.depdb_preamble.begin () + *s.depdb_dyndep, s.depdb_preamble.end (), - dd, &update, mt, &deferred_failure); + dd, &dyn_targets, &update, mt, &deferred_failure); } // This version doesn't actually execute the depdb-dyndep builtin (but @@ -139,7 +151,7 @@ namespace build2 // depdb-dyndep --byproduct logic (which fits better into the rule // implementation). // - enum class dyndep_format {make}; + enum class dyndep_format {make, lines}; struct dyndep_byproduct { @@ -154,14 +166,17 @@ namespace build2 dyndep_byproduct execute_depdb_preamble_dyndep_byproduct ( - action a, const scope& base, const file& t, + action a, const scope& base, const target& t, environment& e, const script& s, runner& r, depdb& dd, bool& update, timestamp mt) { + // Dummies. + // // This is getting a bit ugly (we also don't really need to pass // depdb here). One day we will find a better way... // - bool deferred_failure; // Dymmy. + dynamic_targets dyn_targets; + bool deferred_failure; dyndep_byproduct v; exec_depdb_preamble ( @@ -169,7 +184,7 @@ namespace build2 e, s, r, s.depdb_preamble.begin () + *s.depdb_dyndep, s.depdb_preamble.end (), - dd, &update, mt, &deferred_failure, &v); + dd, &dyn_targets, &update, mt, &deferred_failure, &v); return v; } @@ -216,21 +231,27 @@ namespace build2 names exec_special (token&, build2::script::token_type&, bool skip_first); + // Note: target must be file or group. + // void - exec_depdb_preamble (action, const scope& base, const file&, + exec_depdb_preamble (action, const scope& base, const target&, environment&, const script&, runner&, lines_iterator begin, lines_iterator end, depdb&, + dynamic_targets* dyn_targets = nullptr, bool* update = nullptr, optional<timestamp> mt = nullopt, bool* deferred_failure = nullptr, dyndep_byproduct* = nullptr); + // Note: target must be file or group. + // void exec_depdb_dyndep (token&, build2::script::token_type&, size_t line_index, const location&, - action, const scope& base, file&, + action, const scope& base, target&, depdb&, + dynamic_targets& dyn_targets, bool& update, timestamp, bool& deferred_failure, @@ -271,9 +292,9 @@ namespace build2 script* script_; const small_vector<action, 1>* actions_; // Non-NULL during pre-parse. - // True if this script is for file-based targets and performing update - // is one of the actions, respectively. Only set for the pre-parse - // mode. + // True if this script is for file- or file group-based targets and + // performing update is one of the actions, respectively. Only set for + // the pre-parse mode. // bool file_based_; bool perform_update_; @@ -355,6 +376,7 @@ namespace build2 optional<pair<location, size_t>> depdb_dyndep_; // depdb-dyndep location/position. bool depdb_dyndep_byproduct_ = false; // --byproduct + bool depdb_dyndep_dyn_target_ = false; // --dyn-target lines depdb_preamble_; // Note: excluding depdb-clear. // If present, the first impure function called in the body of the @@ -374,7 +396,12 @@ namespace build2 // optional<location> computed_var_; - // True during pre-parsing when the pre-parse mode is temporarily + // True if we (rather than the base parser) turned on the pre-parse + // mode. + // + bool top_pre_parse_; + + // True during top-pre-parsing when the pre-parse mode is temporarily // suspended to perform expansion. // bool pre_parse_suspended_ = false; diff --git a/libbuild2/build/script/runner.cxx b/libbuild2/build/script/runner.cxx index c52ef66..5d9764b 100644 --- a/libbuild2/build/script/runner.cxx +++ b/libbuild2/build/script/runner.cxx @@ -28,12 +28,37 @@ namespace build2 // for (auto i (env.cleanups.begin ()); i != env.cleanups.end (); ) { - const target* m (&env.target); - for (; m != nullptr; m = m->adhoc_member) + const target* m (nullptr); + if (const group* g = env.target.is_a<group> ()) { - if (const path_target* pm = m->is_a<path_target> ()) - if (i->path == pm->path ()) - break; + for (const target* gm: g->members) + { + if (const path_target* pm = gm->is_a<path_target> ()) + { + if (i->path == pm->path ()) + { + m = gm; + break; + } + } + } + } + else if (const fsdir* fd = env.target.is_a<fsdir> ()) + { + // Compare ignoring the trailing directory separator. + // + if (path_traits::compare (i->path.string (), + fd->dir.string ()) == 0) + m = fd; + } + else + { + for (m = &env.target; m != nullptr; m = m->adhoc_member) + { + if (const path_target* pm = m->is_a<path_target> ()) + if (i->path == pm->path ()) + break; + } } if (m != nullptr) diff --git a/libbuild2/build/script/script.cxx b/libbuild2/build/script/script.cxx index 9d9b5a8..0d96cc3 100644 --- a/libbuild2/build/script/script.cxx +++ b/libbuild2/build/script/script.cxx @@ -7,6 +7,8 @@ #include <libbuild2/target.hxx> +#include <libbuild2/adhoc-rule-buildscript.hxx> // include_unmatch* + #include <libbuild2/script/timeout.hxx> #include <libbuild2/build/script/parser.hxx> @@ -58,11 +60,27 @@ namespace build2 { // $> // + // What should it contain for an explicit group? While it may seem + // that just the members should be enough (and analogous to the ad + // hoc case), this won't let us get the group name for diagnostics. + // So the group name followed by all the members seems like the + // logical choice. + // names ns; - for (const target_type* m (&target); - m != nullptr; - m = m->adhoc_member) - m->as_name (ns); + + if (const group* g = target.is_a<group> ()) + { + g->as_name (ns); + for (const target_type* m: g->members) + m->as_name (ns); + } + else + { + for (const target_type* m (&target); + m != nullptr; + m = m->adhoc_member) + m->as_name (ns); + } assign (var_ts) = move (ns); } @@ -75,13 +93,25 @@ namespace build2 // much sense, they could be handy to exclude certain prerequisites // from $< while still treating them as such, especially in rule. // + // While initially we treated update=unmatch prerequisites as + // implicitly ad hoc, this turned out to be not quite correct, so + // now we add them unless they are explicitly marked ad hoc. + // names ns; - for (const prerequisite_target& pt: target.prerequisite_targets[a]) + for (const prerequisite_target& p: target.prerequisite_targets[a]) { // See adhoc_buildscript_rule::execute_update_prerequisites(). // - if (pt.target != nullptr && !pt.adhoc ()) - pt.target->as_name (ns); + if (const target_type* pt = + p.target != nullptr ? (p.adhoc () ? nullptr : p.target) : + (p.include & adhoc_buildscript_rule::include_unmatch) != 0 && + (p.include & prerequisite_target::include_adhoc) == 0 && + (p.include & adhoc_buildscript_rule::include_unmatch_adhoc) == 0 + ? reinterpret_cast<target_type*> (p.data) + : nullptr) + { + pt->as_name (ns); + } } assign (var_ps) = move (ns); diff --git a/libbuild2/build/script/script.hxx b/libbuild2/build/script/script.hxx index 57a893e..08f1bf4 100644 --- a/libbuild2/build/script/script.hxx +++ b/libbuild2/build/script/script.hxx @@ -82,8 +82,9 @@ namespace build2 bool depdb_value; // String or hash. optional<size_t> depdb_dyndep; // Pos of first dyndep. bool depdb_dyndep_byproduct = false; // dyndep --byproduct + bool depdb_dyndep_dyn_target = false;// dyndep --dyn-target lines depdb_preamble; // Note include vars. - bool depdb_preamble_temp_dir = false; // True if refs $~. + bool depdb_preamble_temp_dir = false;// True if refs $~. location start_loc; location end_loc; |