aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2022-06-21 10:04:07 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2022-06-21 10:04:07 +0200
commitbbe8cbd13c40a1309e0d7724319c5487a5df0879 (patch)
treebdd1e00d9605ec7d5d3d99f44f7eafaf7249a64c
parent2f29c7fbe758ffb53e4de9983df8b1cc927dad05 (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.cxx6
-rw-r--r--doc/manual.cli27
-rw-r--r--libbuild2/algorithm.cxx96
-rw-r--r--libbuild2/algorithm.ixx16
-rw-r--r--libbuild2/b-options.cxx70
-rw-r--r--libbuild2/b-options.hxx32
-rw-r--r--libbuild2/b-options.ixx48
-rw-r--r--libbuild2/b.cli32
-rw-r--r--libbuild2/build/script/parser.cxx6
-rw-r--r--libbuild2/context.hxx11
-rw-r--r--libbuild2/name.hxx3
-rw-r--r--libbuild2/parser.cxx104
-rw-r--r--libbuild2/parser.hxx26
-rw-r--r--libbuild2/test/script/parser.cxx28
-rw-r--r--libbuild2/types-parsers.cxx43
-rw-r--r--libbuild2/types-parsers.hxx10
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