aboutsummaryrefslogtreecommitdiff
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
parent9dfd8f7c6902c7b38f34ca6f87d712d6dcefe3a2 (diff)
Allow imported buildfiles to using config.* variables from own project
-rw-r--r--doc/manual.cli44
-rw-r--r--libbuild2/file.cxx29
-rw-r--r--libbuild2/parser.cxx189
-rw-r--r--libbuild2/parser.hxx9
4 files changed, 224 insertions, 47 deletions
diff --git a/doc/manual.cli b/doc/manual.cli
index f6541b9..f24b429 100644
--- a/doc/manual.cli
+++ b/doc/manual.cli
@@ -2529,9 +2529,9 @@ state identical to distributed.
\h#intro-import|Target Importation|
Recall that if we need to depend on a target defined in another \c{buildfile}
-within our project, then we simply include said \c{buildfile} and reference
-the target. For example, if our \c{hello} included both an executable and a
-library in separate subdirectories next to each other:
+within our project, then we simply include the said \c{buildfile} and
+reference the target. For example, if our \c{hello} included both an
+executable and a library in separate subdirectories next to each other:
\
hello/
@@ -4802,6 +4802,9 @@ executable target in the \c{<project>%exe{<project>\}} form, the
\c{config.<project>} variable is treated as an alias for
\c{config.import.<project>.<project>.exe}.
+For an imported \c{buildfile}, \c{<project>} may refer to either the importing
+project or the project from which the said \c{buildfile} was imported.
+
The build system core reserves \c{build} and \c{import} as the second
component in configuration variables as well as \c{configured} as the third
and subsequent components.|
@@ -5267,6 +5270,29 @@ config libhello@/tmp/libhello/
woptions -Wall -Wextra -Wno-extra -Werror
\
+The \c{config.report.module} attribute can be used to override the reporting
+module name, that is, \c{config} in the \c{config\ libhello@/tmp/libhello/}
+line above. It is primarily useful in imported \c{buildfiles} that wish to
+report non-\c{config.*} variables under their own name. For example:
+
+\
+config [string] config.rtos.board
+
+# Load the board description and report key information such as the
+# capability revoker.
+#
+...
+revoker = ...
+
+config [config.report.module=rtos] revoker
+\
+
+\
+$ b config.rtos.board=ibex-safe-simulator -v
+rtos hello@/tmp/hello/
+ board ibex-safe-simulator
+ revoker hardware
+\
\h#proj-config-propag|Configuration Propagation|
@@ -8572,7 +8598,7 @@ module implementation units appears reasonable and that's what we recommend.
A module declaration (exporting or non-exporting) starts a \i{module purview}
that extends until the end of the module translation unit. Any name declared
-in a module's purview \i{belongs} to said module. For example:
+in a module's purview \i{belongs} to the said module. For example:
\
#include <string> // Not in purview.
@@ -8742,7 +8768,7 @@ say_hello (const std::string&);
\
One way to think of a re-export is \i{as if} an import of a module also
-\"injects\" all the imports said module re-exports, recursively. That's
+\"injects\" all the imports the said module re-exports, recursively. That's
essentially how most compilers implement it.
Module re-export is the mechanism for assembling bigger modules out of
@@ -10401,11 +10427,11 @@ by searching in the \c{PATH} environment variable.
By convention, \c{bash} module libraries should use the \c{lib} name prefix,
for example, \c{libhello}. If there is also a native library (that is, one
written in C/C++) that provides the same functionality (or the \c{bash}
-library is a language binding for said library), then it is customary to add
-the \c{.bash} extension to the \c{bash} library name, for example,
+library is a language binding for the said library), then it is customary to
+add the \c{.bash} extension to the \c{bash} library name, for example,
\c{libhello.bash}. Note that in this case the top-level subdirectory within
-the project is expected to be called without the \c{bash} extension,
-for example, \c{libhello}.
+the project is expected to be called without the \c{bash} extension, for
+example, \c{libhello}.
Modules can be \i{private} or \i{public}. Private modules are implementation
details of a specific project and are not expected to be imported from other
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.
//