aboutsummaryrefslogtreecommitdiff
path: root/libbuild2
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2024-01-09 09:21:12 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2024-01-09 09:21:12 +0200
commit16f5b5d540950f882ee20b13114917f420f41cbb (patch)
treeb8fea9a18c73bdc34af83d189eae3b34c82642a9 /libbuild2
parent9dfd8f7c6902c7b38f34ca6f87d712d6dcefe3a2 (diff)
Allow imported buildfiles to using config.* variables from own project
Diffstat (limited to 'libbuild2')
-rw-r--r--libbuild2/file.cxx29
-rw-r--r--libbuild2/parser.cxx189
-rw-r--r--libbuild2/parser.hxx9
3 files changed, 189 insertions, 38 deletions
diff --git a/libbuild2/file.cxx b/libbuild2/file.cxx
index 1e6cb4c..e62e607 100644
--- a/libbuild2/file.cxx
+++ b/libbuild2/file.cxx
@@ -1636,12 +1636,17 @@ namespace build2
init_modules (module_boot_init::after);
}
- // Print the project configuration report, similar to how we do it in
+ // Print the project configuration report(s), similar to how we do it in
// build system modules.
//
- if (!p.config_report.empty () && verb >= (p.config_report_new ? 2 : 3))
+ const project_name* proj (nullptr); // Resolve lazily.
+ for (const parser::config_report& cr: p.config_reports)
{
- const project_name& proj (named_project (root)); // Can be empty.
+ if (verb < (cr.new_value ? 2 : 3))
+ continue;
+
+ if (proj == nullptr)
+ proj = &named_project (root); // Can be empty.
// @@ TODO/MAYBE:
//
@@ -1659,12 +1664,19 @@ namespace build2
// config @/tmp/tests
// libhello.tests.remote true
//
- string stem (!proj.empty () ? '.' + proj.variable () + '.' : string ());
+ // If the module name is not empty then it means the config variables
+ // are from the imported project and so we use that for <project>.
+ //
+ string stem (!cr.module.empty ()
+ ? '.' + cr.module.variable () + '.'
+ : (!proj->empty ()
+ ? '.' + proj->variable () + '.'
+ : string ()));
// Calculate max name length.
//
size_t pad (10);
- for (const pair<lookup, string>& lf: p.config_report)
+ for (const pair<lookup, string>& lf: cr.values)
{
lookup l (lf.first);
@@ -1686,13 +1698,14 @@ namespace build2
}
// Use the special `config` module name (which doesn't have its own
- // report) for project configuration.
+ // report) for project's own configuration.
//
diag_record dr (text);
- dr << "config " << proj << '@' << root;
+ dr << (cr.module.empty () ? "config" : cr.module.string ().c_str ())
+ << ' ' << *proj << '@' << root;
names storage;
- for (const pair<lookup, string>& lf: p.config_report)
+ for (const pair<lookup, string>& lf: cr.values)
{
lookup l (lf.first);
const string& f (lf.second);
diff --git a/libbuild2/parser.cxx b/libbuild2/parser.cxx
index 3caf097..0a524cc 100644
--- a/libbuild2/parser.cxx
+++ b/libbuild2/parser.cxx
@@ -3541,14 +3541,16 @@ namespace build2
// which case it will be duplicating them in its root.build file). So
// for now we allow this trusting the user knows what they are doing.
//
- string proj;
- {
- const project_name& n (named_project (*root_));
-
- if (!n.empty ())
- proj = n.variable ();
- }
-
+ // There is another special case: a buildfile imported from another
+ // project. In this case we also allow <project> to be the imported
+ // project name in addition to importing. The thinking here is that an
+ // imported buildfile is in a sense like a module (may provide rules which
+ // may require configuration, etc) and should be able to use its own
+ // project name (which is often the corresponding tool name) in the
+ // configuration variables, just like modules. In this case we use the
+ // imported project name as the reporting module name (but which can
+ // be overridden with config.report.module attribute).
+ //
const location loc (get_location (t));
// We are now in the normal lexing mode and we let the lexer handle `?=`.
@@ -3566,6 +3568,11 @@ namespace build2
optional<string> report;
string report_var;
+ // Reporting module name. Empty means the config module reporting
+ // project's own configuration.
+ //
+ project_name report_module;
+
for (auto i (as.begin ()); i != as.end (); )
{
if (i->name == "null")
@@ -3602,6 +3609,20 @@ namespace build2
fail (as.loc) << "invalid " << i->name << " attribute value: " << e;
}
}
+ else if (i->name == "config.report.module")
+ {
+ try
+ {
+ report_module = convert<project_name> (move (i->value));
+
+ if (!report)
+ report = string ("true");
+ }
+ catch (const invalid_argument& e)
+ {
+ fail (as.loc) << "invalid " << i->name << " attribute value: " << e;
+ }
+ }
else
{
++i;
@@ -3653,24 +3674,117 @@ namespace build2
// config prefix and the project substring.
//
{
- diag_record dr;
+ string proj;
+ {
+ const project_name& n (named_project (*root_));
- if (!config)
- dr << fail (t) << "configuration variable '" << name
- << "' does not start with 'config.'";
+ if (!n.empty ())
+ proj = n.variable ();
+ }
- if (!proj.empty ())
+ diag_record dr;
+ do // Breakout loop.
{
- size_t p (name.find ('.' + proj));
+ if (!config)
+ {
+ dr << fail (t) << "configuration variable '" << name
+ << "' does not start with 'config.'";
+ break;
+ }
+
+ auto match = [&name] (const string& proj)
+ {
+ size_t p (name.find ('.' + proj));
+ return (p != string::npos &&
+ ((p += proj.size () + 1) == name.size () || // config.<proj>
+ name[p] == '.')); // config.<proj>.
+ };
+
+ if (!proj.empty () && match (proj))
+ break;
- if (p == string::npos ||
- ((p += proj.size () + 1) != name.size () && // config.<proj>
- name[p] != '.')) // config.<proj>.
+ // See if this buildfile belongs to a different project. If so, use
+ // the project name as the reporting module name.
+ //
+ if (path_->path != nullptr)
{
+ // Note: all sourced/included/imported paths are absolute and
+ // normalized.
+ //
+ const path& f (*path_->path);
+ dir_path d (f.directory ());
+
+ auto p (ctx->scopes.find (d)); // Note: never empty.
+ if (*p.first != &ctx->global_scope)
+ {
+ // The buildfile will most likely be in src which means we may
+ // end up with multiple scopes (see scope_map for background).
+ // First check if one of them is us. If not, then we can extract
+ // the project name from any one of them.
+ //
+ const scope& bs (**p.first); // Save.
+
+ for (; p.first != p.second; ++p.first)
+ {
+ if (root_ == (*p.first)->root_scope ())
+ break;
+ }
+
+ if (p.first == p.second)
+ {
+ // Note: we expect the project itself to be named.
+ //
+ const project_name& n (project (*bs.root_scope ()));
+
+ if (!n.empty ())
+ {
+ // If the buildfile comes from a different project, then
+ // it's more likely to use the imported project's config
+ // variables. So replace proj with that for diagnostics
+ // below.
+ //
+ proj = n.variable ();
+
+ if (*report != "false" && verb >= 2)
+ report_module = n;
+ }
+ }
+ }
+ else
+ {
+ // If the buildfile is not in any project, then it could be
+ // installed.
+ //
+ // Per import2_buildfile(), exported buildfiles are installed
+ // into $install.buildfile/<proj>/....
+ //
+ const dir_path& id (build_install_buildfile);
+
+ if (!id.empty () && d.sub (id))
+ {
+ dir_path l (d.leaf (id));
+ if (!l.empty ())
+ {
+ project_name n (*l.begin ());
+ proj = n.variable ();
+
+ if (*report != "false" && verb >= 2)
+ report_module = move (n);
+ }
+ }
+ }
+ }
+
+ if (!proj.empty () && match (proj))
+ break;
+
+ // Note: only if proj not empty (see above).
+ //
+ if (!proj.empty ())
dr << fail (t) << "configuration variable '" << name
<< "' does not include project name";
- }
}
+ while (false);
if (!dr.empty ())
dr << info << "expected variable name in the 'config[.**]."
@@ -3794,13 +3908,32 @@ namespace build2
}
// We will be printing the report at either level 2 (-v) or 3 (-V)
- // depending on the final value of config_report_new.
+ // depending on the final value of config_report::new_value.
//
- // Note that for the config_report_new calculation we only incorporate
- // variables that we are actually reporting.
+ // Note that for the config_report::new_value calculation we only
+ // incorporate variables that we are actually reporting.
//
if (*report != "false" && verb >= 2)
{
+ // Find existing or insert new config_report entry for this module.
+ //
+ auto i (find_if (config_reports.begin (),
+ config_reports.end (),
+ [&report_module] (const config_report& r)
+ {
+ return r.module == report_module;
+ }));
+
+ if (i == config_reports.end ())
+ {
+ config_reports.push_back (
+ config_report {move (report_module), {}, false});
+ i = config_reports.end () - 1;
+ }
+
+ auto& report_values (i->values);
+ bool& report_new_value (i->new_value);
+
// We don't want to lookup the report variable value here since it's
// most likely not set yet.
//
@@ -3825,19 +3958,19 @@ namespace build2
// multiple config directives to "probe" the value before calculating
// the default; see lookup_config() for details).
//
- auto i (find_if (config_report.begin (),
- config_report.end (),
+ auto i (find_if (report_values.begin (),
+ report_values.end (),
[&l] (const pair<lookup, string>& p)
{
return p.first.var == l.var;
}));
- if (i == config_report.end ())
- config_report.push_back (move (r));
+ if (i == report_values.end ())
+ report_values.push_back (move (r));
else
*i = move (r);
- config_report_new = config_report_new || new_val;
+ report_new_value = report_new_value || new_val;
}
}
@@ -4142,9 +4275,9 @@ namespace build2
ifdstream ifs (p);
auto df = make_diag_frame (
- [this, &loc] (const diag_record& dr)
+ [this, &p, &loc] (const diag_record& dr)
{
- dr << info (loc) << "imported from here";
+ dr << info (loc) << p << " imported from here";
});
// @@ Do we want to enter this buildfile? What's the harm (one
diff --git a/libbuild2/parser.hxx b/libbuild2/parser.hxx
index 54735d5..7263e59 100644
--- a/libbuild2/parser.hxx
+++ b/libbuild2/parser.hxx
@@ -134,8 +134,13 @@ namespace build2
// config directive result.
//
- vector<pair<lookup, string>> config_report; // Config value and format.
- bool config_report_new = false; // One of values is new.
+ struct config_report
+ {
+ project_name module; // Reporting module name.
+ vector<pair<lookup, string>> values; // Config value and format.
+ bool new_value; // One of values is new.
+ };
+ small_vector<config_report, 1> config_reports;
// Misc utilities.
//