From 16f5b5d540950f882ee20b13114917f420f41cbb Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Tue, 9 Jan 2024 09:21:12 +0200 Subject: Allow imported buildfiles to using config.* variables from own project --- doc/manual.cli | 44 +++++++++--- libbuild2/file.cxx | 29 +++++--- libbuild2/parser.cxx | 189 +++++++++++++++++++++++++++++++++++++++++++-------- libbuild2/parser.hxx | 9 ++- 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{%exe{\}} form, the \c{config.} variable is treated as an alias for \c{config.import...exe}. +For an imported \c{buildfile}, \c{} 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 // 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 . + // + string stem (!cr.module.empty () + ? '.' + cr.module.variable () + '.' + : (!proj->empty () + ? '.' + proj->variable () + '.' + : string ())); // Calculate max name length. // size_t pad (10); - for (const pair& lf: p.config_report) + for (const pair& 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& lf: p.config_report) + for (const pair& 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 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 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 (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. + name[p] == '.')); // config.. + }; + + if (!proj.empty () && match (proj)) + break; - if (p == string::npos || - ((p += proj.size () + 1) != name.size () && // config. - name[p] != '.')) // config.. + // 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//.... + // + 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& 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> 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> values; // Config value and format. + bool new_value; // One of values is new. + }; + small_vector config_reports; // Misc utilities. // -- cgit v1.1