diff options
author | Boris Kolpackov <boris@codesynthesis.com> | 2022-06-21 10:04:07 +0200 |
---|---|---|
committer | Boris Kolpackov <boris@codesynthesis.com> | 2022-06-21 10:04:07 +0200 |
commit | bbe8cbd13c40a1309e0d7724319c5487a5df0879 (patch) | |
tree | bdd1e00d9605ec7d5d3d99f44f7eafaf7249a64c | |
parent | 2f29c7fbe758ffb53e4de9983df8b1cc927dad05 (diff) |
Add --trace-{match,execute} options
These options can be used to understand which dependency chain causes matching
or execution of a particular target.
-rw-r--r-- | build2/b.cxx | 6 | ||||
-rw-r--r-- | doc/manual.cli | 27 | ||||
-rw-r--r-- | libbuild2/algorithm.cxx | 96 | ||||
-rw-r--r-- | libbuild2/algorithm.ixx | 16 | ||||
-rw-r--r-- | libbuild2/b-options.cxx | 70 | ||||
-rw-r--r-- | libbuild2/b-options.hxx | 32 | ||||
-rw-r--r-- | libbuild2/b-options.ixx | 48 | ||||
-rw-r--r-- | libbuild2/b.cli | 32 | ||||
-rw-r--r-- | libbuild2/build/script/parser.cxx | 6 | ||||
-rw-r--r-- | libbuild2/context.hxx | 11 | ||||
-rw-r--r-- | libbuild2/name.hxx | 3 | ||||
-rw-r--r-- | libbuild2/parser.cxx | 104 | ||||
-rw-r--r-- | libbuild2/parser.hxx | 26 | ||||
-rw-r--r-- | libbuild2/test/script/parser.cxx | 28 | ||||
-rw-r--r-- | libbuild2/types-parsers.cxx | 43 | ||||
-rw-r--r-- | libbuild2/types-parsers.hxx | 10 |
16 files changed, 449 insertions, 109 deletions
diff --git a/build2/b.cxx b/build2/b.cxx index 470aade..f666b86 100644 --- a/build2/b.cxx +++ b/build2/b.cxx @@ -436,6 +436,12 @@ main (int argc, char* argv[]) ops.dry_run (), !ops.serial_stop () /* keep_going */, cmdl.cmd_vars)); + + if (ops.trace_match_specified ()) + pctx->trace_match = &ops.trace_match (); + + if (ops.trace_execute_specified ()) + pctx->trace_execute = &ops.trace_execute (); }; new_context (); diff --git a/doc/manual.cli b/doc/manual.cli index b72700d..ab832fe 100644 --- a/doc/manual.cli +++ b/doc/manual.cli @@ -1418,7 +1418,7 @@ if ($cc.class == 'gcc') } if ($c.target.class != 'windows') - c.libs += -lpthread # only C + c.libs += -ldl # only C \ Additionally, as we will see in \l{#intro-operations-config Configuring}, @@ -4443,12 +4443,12 @@ Instead of printing the entire scope, we can also print individual targets by specifying one or more target names in \c{dump}. To make things more interesting, let's convert our \c{hello} project to use a utility library, similar to the unit testing setup (\l{#intro-unit-test Implementing Unit -Testing}). We will also link to the \c{pthread} library to see an example of a +Testing}). We will also link to the \c{dl} library to see an example of a target-specific variable being dumped: \ exe{hello}: libue{hello}: bin.whole = false -exe{hello}: cxx.libs += -lpthread +exe{hello}: cxx.libs += -ldl libue{hello}: {hxx cxx}{**} dump exe{hello} @@ -4460,7 +4460,7 @@ The output will look along these lines: buildfile:5:1: dump: /tmp/hello/hello/exe{hello.?}: { - [strings] cxx.libs = -lpthread + [strings] cxx.libs = -ldl } /tmp/hello/hello/exe{hello.?}: /tmp/hello/hello/:libue{hello.?}: { @@ -4561,6 +4561,25 @@ Higher verbosity levels result in more and more tracing statements being printed. These include \c{buildfile} loading and parsing, prerequisite to target resolution, as well as build system module and rule-specific logic. +While the tracing statements can be helpful in understanding what is +happening, they don't make it easy to see why things are happening a +certain way. In particular, one question that is often encountered during +build troubleshooting is which dependency chain causes matching or execution +of a particular target. These questions can be answered with the help of +the \c{--trace-match} and \c{--trace-execute} options. For example, if we +want to understand what causes the update of \c{obje{hello\}} in the +\c{hello} project above: + +\ +$ b -s --trace-execute 'obje{hello}' +info: updating hello/obje{hello} + info: using rule cxx.compile + info: while updating hello/libue{hello} + info: while updating hello/exe{hello} + info: while updating dir{hello/} + info: while updating dir{./} +\ + Another useful diagnostics option is \c{--mtime-check}. When specified, the build system performs a number of file modification time sanity checks that can be helpful in diagnosing spurious rebuilds. diff --git a/libbuild2/algorithm.cxx b/libbuild2/algorithm.cxx index 355e633..564105c 100644 --- a/libbuild2/algorithm.cxx +++ b/libbuild2/algorithm.cxx @@ -357,6 +357,70 @@ namespace build2 return *m; }; + static bool + trace_target (const target& t, const vector<name>& ns) + { + for (const name& n: ns) + { + if (n.untyped () || n.qualified () || n.pattern) + fail << "unsupported trace target name '" << n << "'" << + info << "unqualified, typed, non-pattern name expected"; + + if (!n.dir.empty ()) + { + if (n.dir.relative () || !n.dir.normalized ()) + fail << "absolute and normalized trace target directory expected"; + + if (t.dir != n.dir) + continue; + } + + if (n.type == t.type ().name && n.value == t.name) + return true; + } + + return false; + } + + void + set_rule_trace (target_lock& l, const rule_match* rm) + { + action a (l.action); + target& t (*l.target); + + // Note: see similar code in execute_impl() for execute. + // + if (trace_target (t, *t.ctx.trace_match)) + { + diag_record dr (info); + + dr << "matching to " << diag_do (a, t); + + if (rm != nullptr) + { + const rule& r (rm->second); + + if (const adhoc_rule* ar = dynamic_cast<const adhoc_rule*> (&r)) + { + dr << info (ar->loc); + + if (ar->pattern != nullptr) + dr << "using ad hoc pattern rule "; + else + dr << "using ad hoc recipe "; + } + else + dr << info << "using rule "; + + dr << rm->first; + } + else + dr << info << "using directly-assigned recipe"; + } + + t[a].rule = rm; + } + // Return the matching rule or NULL if no match and try_match is true. // const rule_match* @@ -837,7 +901,7 @@ namespace build2 return make_pair (false, target_state::unknown); } - s.rule = r; + set_rule (l, r); l.offset = target::offset_matched; if (step) @@ -1899,6 +1963,36 @@ namespace build2 backlink_clean_pre (a, t, *blm); } + // Note: see similar code in set_rule_trace() for match. + // + if (ctx.trace_execute != nullptr && trace_target (t, *ctx.trace_execute)) + { + diag_record dr (info); + + dr << diag_doing (a, t); + + if (s.rule != nullptr) + { + const rule& r (s.rule->second); + + if (const adhoc_rule* ar = dynamic_cast<const adhoc_rule*> (&r)) + { + dr << info (ar->loc); + + if (ar->pattern != nullptr) + dr << "using ad hoc pattern rule "; + else + dr << "using ad hoc recipe "; + } + else + dr << info << "using rule "; + + dr << s.rule->first; + } + else + dr << info << "using directly-assigned recipe"; + } + ts = execute_recipe (a, t, s.recipe); if (blm) diff --git a/libbuild2/algorithm.ixx b/libbuild2/algorithm.ixx index 2637349..10ed754 100644 --- a/libbuild2/algorithm.ixx +++ b/libbuild2/algorithm.ixx @@ -498,6 +498,18 @@ namespace build2 t.clear_data (a); } + LIBBUILD2_SYMEXPORT void + set_rule_trace (target_lock&, const rule_match*); + + inline void + set_rule (target_lock& l, const rule_match* r) + { + if (l.target->ctx.trace_match == nullptr) + (*l.target)[l.action].rule = r; + else + set_rule_trace (l, r); + } + inline void set_recipe (target_lock& l, recipe&& r) { @@ -549,7 +561,7 @@ namespace build2 l.target->ctx.phase == run_phase::match); clear_target (l.action, *l.target); - (*l.target)[l.action].rule = nullptr; // No rule. + set_rule (l, nullptr); // No rule. set_recipe (l, move (r)); l.offset = target::offset_applied; } @@ -562,7 +574,7 @@ namespace build2 l.target->ctx.phase == run_phase::match); clear_target (l.action, *l.target); - (*l.target)[l.action].rule = &r; + set_rule (l, &r); l.offset = target::offset_matched; } diff --git a/libbuild2/b-options.cxx b/libbuild2/b-options.cxx index 223233a..531e453 100644 --- a/libbuild2/b-options.cxx +++ b/libbuild2/b-options.cxx @@ -247,8 +247,6 @@ namespace build2 verbose_ (1), verbose_specified_ (false), stat_ (), - dump_ (), - dump_specified_ (false), progress_ (), no_progress_ (), jobs_ (), @@ -269,6 +267,12 @@ namespace build2 structured_result_specified_ (false), mtime_check_ (), no_mtime_check_ (), + dump_ (), + dump_specified_ (false), + trace_match_ (), + trace_match_specified_ (false), + trace_execute_ (), + trace_execute_specified_ (false), no_column_ (), no_line_ (), buildfile_ (), @@ -403,13 +407,6 @@ namespace build2 this->stat_, a.stat_); } - if (a.dump_specified_) - { - ::build2::build::cli::parser< std::set<string>>::merge ( - this->dump_, a.dump_); - this->dump_specified_ = true; - } - if (a.progress_) { ::build2::build::cli::parser< bool>::merge ( @@ -500,6 +497,27 @@ namespace build2 this->no_mtime_check_, a.no_mtime_check_); } + if (a.dump_specified_) + { + ::build2::build::cli::parser< std::set<string>>::merge ( + this->dump_, a.dump_); + this->dump_specified_ = true; + } + + if (a.trace_match_specified_) + { + ::build2::build::cli::parser< std::vector<name>>::merge ( + this->trace_match_, a.trace_match_); + this->trace_match_specified_ = true; + } + + if (a.trace_execute_specified_) + { + ::build2::build::cli::parser< std::vector<name>>::merge ( + this->trace_execute_, a.trace_execute_); + this->trace_execute_specified_ = true; + } + if (a.no_column_) { ::build2::build::cli::parser< bool>::merge ( @@ -628,12 +646,6 @@ namespace build2 << "\033[1m--stat\033[0m Display build statistics." << ::std::endl; os << std::endl - << "\033[1m--dump\033[0m \033[4mphase\033[0m Dump the build system state after the specified phase." << ::std::endl - << " Valid \033[4mphase\033[0m values are \033[1mload\033[0m (after loading \033[1mbuildfiles\033[0m)" << ::std::endl - << " and \033[1mmatch\033[0m (after matching rules to targets). Repeat" << ::std::endl - << " this option to dump the state after multiple phases." << ::std::endl; - - os << std::endl << "\033[1m--progress\033[0m Display build progress. If printing to a terminal the" << ::std::endl << " progress is displayed by default for low verbosity" << ::std::endl << " levels. Use \033[1m--no-progress\033[0m to suppress." << ::std::endl; @@ -805,6 +817,22 @@ namespace build2 << " \033[1m--mtime-check\033[0m for details." << ::std::endl; os << std::endl + << "\033[1m--dump\033[0m \033[4mphase\033[0m Dump the build system state after the specified phase." << ::std::endl + << " Valid \033[4mphase\033[0m values are \033[1mload\033[0m (after loading \033[1mbuildfiles\033[0m)" << ::std::endl + << " and \033[1mmatch\033[0m (after matching rules to targets). Repeat" << ::std::endl + << " this option to dump the state after multiple phases." << ::std::endl; + + os << std::endl + << "\033[1m--trace-match\033[0m \033[4mtarget\033[0m Trace rule matching for the specified target. This is" << ::std::endl + << " primarily useful during troubleshooting. Repeat this" << ::std::endl + << " option to trace multiple targets." << ::std::endl; + + os << std::endl + << "\033[1m--trace-execute\033[0m \033[4mtarget\033[0m Trace rule execution for the specified target. This is" << ::std::endl + << " primarily useful during troubleshooting. Repeat this" << ::std::endl + << " option to trace multiple targets." << ::std::endl; + + os << std::endl << "\033[1m--no-column\033[0m Don't print column numbers in diagnostics." << ::std::endl; os << std::endl @@ -915,9 +943,6 @@ namespace build2 &b_options::verbose_specified_ >; _cli_b_options_map_["--stat"] = &::build2::build::cli::thunk< b_options, bool, &b_options::stat_ >; - _cli_b_options_map_["--dump"] = - &::build2::build::cli::thunk< b_options, std::set<string>, &b_options::dump_, - &b_options::dump_specified_ >; _cli_b_options_map_["--progress"] = &::build2::build::cli::thunk< b_options, bool, &b_options::progress_ >; _cli_b_options_map_["--no-progress"] = @@ -965,6 +990,15 @@ namespace build2 &::build2::build::cli::thunk< b_options, bool, &b_options::mtime_check_ >; _cli_b_options_map_["--no-mtime-check"] = &::build2::build::cli::thunk< b_options, bool, &b_options::no_mtime_check_ >; + _cli_b_options_map_["--dump"] = + &::build2::build::cli::thunk< b_options, std::set<string>, &b_options::dump_, + &b_options::dump_specified_ >; + _cli_b_options_map_["--trace-match"] = + &::build2::build::cli::thunk< b_options, std::vector<name>, &b_options::trace_match_, + &b_options::trace_match_specified_ >; + _cli_b_options_map_["--trace-execute"] = + &::build2::build::cli::thunk< b_options, std::vector<name>, &b_options::trace_execute_, + &b_options::trace_execute_specified_ >; _cli_b_options_map_["--no-column"] = &::build2::build::cli::thunk< b_options, bool, &b_options::no_column_ >; _cli_b_options_map_["--no-line"] = diff --git a/libbuild2/b-options.hxx b/libbuild2/b-options.hxx index d8d85d3..2780a8d 100644 --- a/libbuild2/b-options.hxx +++ b/libbuild2/b-options.hxx @@ -98,12 +98,6 @@ namespace build2 const bool& stat () const; - const std::set<string>& - dump () const; - - bool - dump_specified () const; - const bool& progress () const; @@ -164,6 +158,24 @@ namespace build2 const bool& no_mtime_check () const; + const std::set<string>& + dump () const; + + bool + dump_specified () const; + + const std::vector<name>& + trace_match () const; + + bool + trace_match_specified () const; + + const std::vector<name>& + trace_execute () const; + + bool + trace_execute_specified () const; + const bool& no_column () const; @@ -249,8 +261,6 @@ namespace build2 uint16_t verbose_; bool verbose_specified_; bool stat_; - std::set<string> dump_; - bool dump_specified_; bool progress_; bool no_progress_; size_t jobs_; @@ -271,6 +281,12 @@ namespace build2 bool structured_result_specified_; bool mtime_check_; bool no_mtime_check_; + std::set<string> dump_; + bool dump_specified_; + std::vector<name> trace_match_; + bool trace_match_specified_; + std::vector<name> trace_execute_; + bool trace_execute_specified_; bool no_column_; bool no_line_; path buildfile_; diff --git a/libbuild2/b-options.ixx b/libbuild2/b-options.ixx index b7944ba..f43dce2 100644 --- a/libbuild2/b-options.ixx +++ b/libbuild2/b-options.ixx @@ -68,18 +68,6 @@ namespace build2 return this->stat_; } - inline const std::set<string>& b_options:: - dump () const - { - return this->dump_; - } - - inline bool b_options:: - dump_specified () const - { - return this->dump_specified_; - } - inline const bool& b_options:: progress () const { @@ -200,6 +188,42 @@ namespace build2 return this->no_mtime_check_; } + inline const std::set<string>& b_options:: + dump () const + { + return this->dump_; + } + + inline bool b_options:: + dump_specified () const + { + return this->dump_specified_; + } + + inline const std::vector<name>& b_options:: + trace_match () const + { + return this->trace_match_; + } + + inline bool b_options:: + trace_match_specified () const + { + return this->trace_match_specified_; + } + + inline const std::vector<name>& b_options:: + trace_execute () const + { + return this->trace_execute_; + } + + inline bool b_options:: + trace_execute_specified () const + { + return this->trace_execute_specified_; + } + inline const bool& b_options:: no_column () const { diff --git a/libbuild2/b.cli b/libbuild2/b.cli index 3ae6e0b..dc5198e 100644 --- a/libbuild2/b.cli +++ b/libbuild2/b.cli @@ -537,15 +537,6 @@ namespace build2 "Display build statistics." } - std::set<string> --dump - { - "<phase>", - "Dump the build system state after the specified phase. Valid <phase> - values are \cb{load} (after loading \cb{buildfiles}) and \cb{match} - (after matching rules to targets). Repeat this option to dump the - state after multiple phases." - } - bool --progress { "Display build progress. If printing to a terminal the progress is @@ -742,6 +733,29 @@ namespace build2 \cb{--mtime-check} for details." } + std::set<string> --dump + { + "<phase>", + "Dump the build system state after the specified phase. Valid <phase> + values are \cb{load} (after loading \cb{buildfiles}) and \cb{match} + (after matching rules to targets). Repeat this option to dump the + state after multiple phases." + } + + std::vector<name> --trace-match + { + "<target>", + "Trace rule matching for the specified target. This is primarily useful + during troubleshooting. Repeat this option to trace multiple targets." + } + + std::vector<name> --trace-execute + { + "<target>", + "Trace rule execution for the specified target. This is primarily useful + during troubleshooting. Repeat this option to trace multiple targets." + } + bool --no-column { "Don't print column numbers in diagnostics." diff --git a/libbuild2/build/script/parser.cxx b/libbuild2/build/script/parser.cxx index d9bfef6..9f04102 100644 --- a/libbuild2/build/script/parser.cxx +++ b/libbuild2/build/script/parser.cxx @@ -478,8 +478,8 @@ namespace build2 { if (a != perform_update_id) fail (l) << "'depdb' builtin cannot be used to " - << ctx.meta_operation_table[a.meta_operation ()].name - << ' ' << ctx.operation_table[a.operation ()]; + << ctx->meta_operation_table[a.meta_operation ()].name + << ' ' << ctx->operation_table[a.operation ()]; } if (!file_based_) @@ -2344,7 +2344,7 @@ namespace build2 { if (perform_update_ && file_based_ && !impure_func_) { - const function_overloads* f (ctx.functions.find (name)); + const function_overloads* f (ctx->functions.find (name)); if (f != nullptr && !f->pure) impure_func_ = make_pair (move (name), loc); diff --git a/libbuild2/context.hxx b/libbuild2/context.hxx index ad7fdff..d4cf9ff 100644 --- a/libbuild2/context.hxx +++ b/libbuild2/context.hxx @@ -219,6 +219,14 @@ namespace build2 // bool keep_going; + // Targets to trace (see the --trace-* options). + // + // Note that these must be set after construction and must remain valid + // for the lifetime of the context instance. + // + const vector<name>* trace_match = nullptr; + const vector<name>* trace_execute = nullptr; + // In order to perform each operation the build system goes through the // following phases: // @@ -588,6 +596,9 @@ namespace build2 // properly setup context (including, normally, a self-reference in // modules_context). // + // Note: see also the trace_* data members that, if needed, must be set + // separately, after construction. + // explicit context (scheduler&, global_mutexes&, diff --git a/libbuild2/name.hxx b/libbuild2/name.hxx index 7d179aa..f5cb2c5 100644 --- a/libbuild2/name.hxx +++ b/libbuild2/name.hxx @@ -178,7 +178,8 @@ namespace build2 // trailing directory separator then it is stored as a directory, otherwise // as a simple name. Note that the returned name is never a pattern. // - // NOTE: this function does not parse the full name syntax. + // NOTE: this function does not parse the full name syntax. See context-less + // parser::parse_names() for a heavy-weight way to achieve this. // name to_name (string); diff --git a/libbuild2/parser.cxx b/libbuild2/parser.cxx index cc3fba7..da20a48 100644 --- a/libbuild2/parser.cxx +++ b/libbuild2/parser.cxx @@ -162,7 +162,7 @@ namespace build2 tracer& tr) { auto r (p.scope_->find_target_type (n, o, loc)); - return p.ctx.targets.insert ( + return p.ctx->targets.insert ( r.first, // target type move (n.dir), move (o.dir), @@ -182,12 +182,12 @@ namespace build2 tracer& tr) { auto r (p.scope_->find_target_type (n, o, loc)); - return p.ctx.targets.find (r.first, // target type - n.dir, - o.dir, - n.value, - r.second, // extension - tr); + return p.ctx->targets.find (r.first, // target type + n.dir, + o.dir, + n.value, + r.second, // extension + tr); } ~enter_target () @@ -355,6 +355,37 @@ namespace build2 return make_pair (move (lhs), move (t)); } + names parser:: + parse_names (lexer& l, + const dir_path* b, + pattern_mode pmode, + const char* what, + const string* separators) + { + path_ = &l.name (); + lexer_ = &l; + + root_ = nullptr; + scope_ = nullptr; + target_ = nullptr; + prerequisite_ = nullptr; + + pbase_ = b; + + token t; + type tt; + + mode (lexer_mode::value, '@'); + next (t, tt); + + names r (parse_names (t, tt, pmode, what, separators)); + + if (tt != type::eos) + fail (t) << "unexpected " << t; + + return r; + } + value parser:: parse_eval (lexer& l, scope& rs, scope& bs, pattern_mode pmode) { @@ -1870,7 +1901,7 @@ namespace build2 for (metaopspec& m: d.bs) { - meta_operation_id mi (ctx.meta_operation_table.find (m.name)); + meta_operation_id mi (ctx->meta_operation_table.find (m.name)); if (mi == 0) fail (l) << "unknown meta-operation " << m.name; @@ -1880,7 +1911,7 @@ namespace build2 if (mf == nullptr) fail (l) << "project " << *root_ << " does not support meta-" - << "operation " << ctx.meta_operation_table[mi].name; + << "operation " << ctx->meta_operation_table[mi].name; for (opspec& o: m) { @@ -1896,7 +1927,7 @@ namespace build2 fail (l) << "default operation in recipe action" << endf; } else - oi = ctx.operation_table.find (o.name); + oi = ctx->operation_table.find (o.name); if (oi == 0) fail (l) << "unknown operation " << o.name; @@ -1905,7 +1936,7 @@ namespace build2 if (of == nullptr) fail (l) << "project " << *root_ << " does not support " - << "operation " << ctx.operation_table[oi]; + << "operation " << ctx->operation_table[oi]; // Note: for now always inner (see match_rule() for details). // @@ -2295,7 +2326,7 @@ namespace build2 if (!v.empty ()) { - oi = ctx.operation_table.find (v); + oi = ctx->operation_table.find (v); if (oi == 0) fail (l) << "unknown operation " << v << " in rule_hint " @@ -2303,7 +2334,7 @@ namespace build2 if (root_->root_extra->operations[oi] == nullptr) fail (l) << "project " << *root_ << " does not support " - << "operation " << ctx.operation_table[oi] + << "operation " << ctx->operation_table[oi] << " specified in rule_hint attribute"; } } @@ -3976,7 +4007,7 @@ namespace build2 if (!e.arg.empty ()) args.push_back (value (e.arg)); - value r (ctx.functions.call (scope_, *e.func, args, l)); + value r (ctx->functions.call (scope_, *e.func, args, l)); // We support two types of functions: matchers and extractors: // a matcher returns a statically-typed bool value while an @@ -4708,10 +4739,10 @@ namespace build2 // attributes). if (type || vis || ovr) - ctx.var_pool.update (const_cast<variable&> (var), - type, - vis ? &*vis : nullptr, - ovr ? &*ovr : nullptr); + ctx->var_pool.update (const_cast<variable&> (var), + type, + vis ? &*vis : nullptr, + ovr ? &*ovr : nullptr); } @@ -6097,7 +6128,7 @@ namespace build2 bool concat_quoted_first (false); name concat_data; - auto concat_typed = [this, &vnull, &vtype, + auto concat_typed = [this, what, &vnull, &vtype, &concat, &concat_data] (value&& rhs, const location& loc) { @@ -6132,7 +6163,10 @@ namespace build2 dr << info << "use quoting to force untyped concatenation"; }); - p = ctx.functions.try_call ( + if (ctx == nullptr) + fail << "literal " << what << " expected"; + + p = ctx->functions.try_call ( scope_, "builtin.concat", vector_view<value> (a), loc); } @@ -6860,6 +6894,9 @@ namespace build2 // if (tt == type::dollar || tt == type::lparen) { + if (ctx == nullptr) + fail << "literal " << what << " expected"; + // These cases are pretty similar in that in both we quickly end up // with a list of names that we need to splice into the result. // @@ -7056,7 +7093,7 @@ namespace build2 // if (!pre_parse_) { - result_data = ctx.functions.call (scope_, name, args, loc); + result_data = ctx->functions.call (scope_, name, args, loc); what = "function call"; } else @@ -7250,7 +7287,10 @@ namespace build2 dr << info (loc) << "while converting " << t << " to string"; }); - p = ctx.functions.try_call ( + if (ctx == nullptr) + fail << "literal " << what << " expected"; + + p = ctx->functions.try_call ( scope_, "string", vector_view<value> (&result_data, 1), loc); } @@ -7585,7 +7625,7 @@ namespace build2 lexer l (is, *path_, 1 /* line */, "\'\"\\$("); lexer_ = &l; - root_ = &ctx.global_scope.rw (); + root_ = &ctx->global_scope.rw (); scope_ = root_; target_ = nullptr; prerequisite_ = nullptr; @@ -7978,13 +8018,13 @@ namespace build2 target& dt (*default_target_); target* ct ( - const_cast<target*> ( // Ok (serial execution). - ctx.targets.find (dir::static_type, // Explicit current dir target. - scope_->out_path (), - dir_path (), // Out tree target. - string (), - nullopt, - trace))); + const_cast<target*> ( // Ok (serial execution). + ctx->targets.find (dir::static_type, // Explicit current dir target. + scope_->out_path (), + dir_path (), // Out tree target. + string (), + nullopt, + trace))); if (ct == nullptr) { @@ -7993,7 +8033,7 @@ namespace build2 // While this target is not explicitly mentioned in the buildfile, we // say that we behave as if it were. Thus not implied. // - ct = &ctx.targets.insert (dir::static_type, + ct = &ctx->targets.insert (dir::static_type, scope_->out_path (), dir_path (), string (), @@ -8031,7 +8071,7 @@ namespace build2 out = out_src (d, *root_); } - ctx.targets.insert<buildfile> ( + ctx->targets.insert<buildfile> ( move (d), move (out), p.leaf ().base ().string (), diff --git a/libbuild2/parser.hxx b/libbuild2/parser.hxx index a71d671..9e9f926 100644 --- a/libbuild2/parser.hxx +++ b/libbuild2/parser.hxx @@ -48,7 +48,7 @@ namespace build2 explicit parser (context& c, stage s = stage::rest) : fail ("error", &path_), info ("info", &path_), - ctx (c), + ctx (&c), stage_ (s) {} // Pattern expansion mode. @@ -106,6 +106,25 @@ namespace build2 void reset (); + // Special, context-less mode that can only be used to parse literal + // names. + // + public: + static const string name_separators; + + explicit + parser (context* c) + : fail ("error", &path_), info ("info", &path_), + ctx (c), + stage_ (stage::rest) {} + + names + parse_names (lexer&, + const dir_path* base, + pattern_mode pmode, + const char* what = "name", + const string* separators = &name_separators); + // Ad hoc parsing results for some cases. // // Note that these are not touched by reset(). @@ -363,9 +382,6 @@ namespace build2 // project separator. Note that even if it is NULL, the result may still // contain non-simple names due to variable expansions. // - - static const string name_separators; - names parse_names (token& t, token_type& tt, pattern_mode pmode, @@ -869,7 +885,7 @@ namespace build2 // NOTE: remember to update reset() if adding anything here. // protected: - context& ctx; + context* ctx; stage stage_; bool pre_parse_ = false; diff --git a/libbuild2/test/script/parser.cxx b/libbuild2/test/script/parser.cxx index bf04a30..2cc1b65 100644 --- a/libbuild2/test/script/parser.cxx +++ b/libbuild2/test/script/parser.cxx @@ -1593,24 +1593,24 @@ namespace build2 // UBSan workaround. // const diag_frame* df (diag_frame::stack ()); - if (!ctx.sched.async (task_count, - [] (const diag_frame* ds, - scope& s, - script& scr, - runner& r) - { - diag_frame::stack_guard dsg (ds); - execute_impl (s, scr, r); - }, - df, - ref (*chain), - ref (*script_), - ref (*runner_))) + if (!ctx->sched.async (task_count, + [] (const diag_frame* ds, + scope& s, + script& scr, + runner& r) + { + diag_frame::stack_guard dsg (ds); + execute_impl (s, scr, r); + }, + df, + ref (*chain), + ref (*script_), + ref (*runner_))) { // Bail out if the scope has failed and we weren't instructed // to keep going. // - if (chain->state == scope_state::failed && !ctx.keep_going) + if (chain->state == scope_state::failed && !ctx->keep_going) throw failed (); } } diff --git a/libbuild2/types-parsers.cxx b/libbuild2/types-parsers.cxx index 7b4a65d..d220541 100644 --- a/libbuild2/types-parsers.cxx +++ b/libbuild2/types-parsers.cxx @@ -3,6 +3,11 @@ #include <libbuild2/types-parsers.hxx> +#include <sstream> + +#include <libbuild2/lexer.hxx> +#include <libbuild2/parser.hxx> + namespace build2 { namespace build @@ -47,6 +52,44 @@ namespace build2 parse_path (x, s); } + void parser<name>:: + parse (name& x, bool& xs, scanner& s) + { + const char* o (s.next ()); + + if (!s.more ()) + throw missing_value (o); + + const char* v (s.next ()); + + try + { + using build2::parser; + using std::istringstream; + + istringstream is (v); + is.exceptions (istringstream::failbit | istringstream::badbit); + + // @@ TODO: currently this issues diagnostics to diag_stream. + // Perhaps we should redirect it? + // + path_name in (o); + lexer l (is, in, 1 /* line */, "\'\"\\$("); // Effective. + parser p (nullptr); + names r (p.parse_names (l, nullptr, parser::pattern_mode::preserve)); + + if (r.size () != 1) + throw invalid_value (o, v); + + x = move (r.front ()); + xs = true; + } + catch (const failed&) + { + throw invalid_value (o, v); + } + } + void parser<structured_result_format>:: parse (structured_result_format& x, bool& xs, scanner& s) { diff --git a/libbuild2/types-parsers.hxx b/libbuild2/types-parsers.hxx index aef00ca..ebd2a02 100644 --- a/libbuild2/types-parsers.hxx +++ b/libbuild2/types-parsers.hxx @@ -44,6 +44,16 @@ namespace build2 }; template <> + struct parser<name> + { + static void + parse (name&, bool&, scanner&); + + static void + merge (name& b, const name& a) {b = a;} + }; + + template <> struct parser<structured_result_format> { static void |