From 8ecc09653d70fe8df4fc9fcd7214ba85e6db9c9c Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Fri, 2 Sep 2022 10:29:15 +0200 Subject: Add ability to specify `in` rule substitution as key-value pairs --- doc/manual.cli | 45 ++++++++++++++++++++++++++++++++++++++++++++ libbuild2/bash/rule.cxx | 3 ++- libbuild2/bash/rule.hxx | 1 + libbuild2/in/init.cxx | 16 ++++++++++++++++ libbuild2/in/rule.cxx | 47 +++++++++++++++++++++++++++++++++++++--------- libbuild2/in/rule.hxx | 5 +++++ libbuild2/version/rule.cxx | 4 ++-- libbuild2/version/rule.hxx | 1 + tests/in/testscript | 29 ++++++++++++++++++++++++++-- 9 files changed, 137 insertions(+), 14 deletions(-) diff --git a/doc/manual.cli b/doc/manual.cli index 72611bc..ed96915 100644 --- a/doc/manual.cli +++ b/doc/manual.cli @@ -8871,6 +8871,42 @@ target-specific variables. Typed variable values are converted to string using the corresponding \c{builtin.string()} function overload before substitution. +While specifying substitution values as \c{buildfile} variables is usually +natural, sometimes this may not be possible or convenient. Specifically, we +may have substitution names that cannot be specified as \c{buildfile} +variables, for example, because they start with an underscore (and are thus +reserved) or because they refer to one of the predefined variables. Also, we +may need to have different groups of substitution values for different cases, +for example, for different platforms, and it would be convenient to pass such +groups around as a single value. + +To support these requirements the substitution values can alternatively be +specified as key-value pairs in the \c{in.substitutions} variable. Note that +entries in this substitution map take precedence over the \c{buildfile} +variables. For example: + +\ +/* config.h.in */ + +#define _GNU_SOURCE @_GNU_SOURCE@ +#define _POSIX_SOURCE @_POSIX_SOURCE@ +\ + +\ +# buildfile + +h{config}: in{config} +{ + in.symbol = '@' + in.mode = lax + + in.substitutions = _GNU_SOURCE@0 _POSIX_SOURCE@1 +} +\ + +\N|In the above example, the \c{@} characters in \c{in.symbol} and +\c{in.substitutions} are unrelated.| + Using an undefined variable in a substitution is an error. Using a \c{null} value in a substitution is also an error unless the fallback value is specified with the \c{in.null} variable. For example: @@ -8884,6 +8920,15 @@ h{config}: in{config} } \ +\N|To specify a \c{null} value using the \c{in.substitutions} mechanism omit +the value, for example: + +\ +in.substitutions = _GNU_SOURCE +\ + +| + A number of other build system modules, for example, \l{https://github.com/build2/libbuild2-autoconf/ \c{autoconf}}, \l{#module-version \c{version}}, and \l{#module-bash \c{bash}}, are based on diff --git a/libbuild2/bash/rule.cxx b/libbuild2/bash/rule.cxx index 2abddc3..6e287e0 100644 --- a/libbuild2/bash/rule.cxx +++ b/libbuild2/bash/rule.cxx @@ -214,13 +214,14 @@ namespace build2 const string& n, optional flags, bool strict, + const substitution_map* smap, const optional& null) const { assert (!flags); return n.compare (0, 6, "import") == 0 && (n[6] == ' ' || n[6] == '\t') ? substitute_import (l, a, t, trim (string (n, 7))) - : rule::substitute (l, a, t, n, nullopt, strict, null); + : rule::substitute (l, a, t, n, nullopt, strict, smap, null); } string in_rule:: diff --git a/libbuild2/bash/rule.hxx b/libbuild2/bash/rule.hxx index f7ce5cd..5bd7fa7 100644 --- a/libbuild2/bash/rule.hxx +++ b/libbuild2/bash/rule.hxx @@ -52,6 +52,7 @@ namespace build2 const string&, optional, bool, + const substitution_map*, const optional&) const override; string diff --git a/libbuild2/in/init.cxx b/libbuild2/in/init.cxx index 6743e31..8bd5909 100644 --- a/libbuild2/in/init.cxx +++ b/libbuild2/in/init.cxx @@ -64,6 +64,22 @@ namespace build2 // vp.insert_alias (im, "in.substitution"); + // Substitution map. Substitutions can be specified as key-value pairs + // rather than buildfile variables. This map is checked before the + // variables. An absent value in key-value has the NULL semantics. + // + // This mechanism has two primary uses: Firstly, it allows us to have + // substitution names that cannot be specified as buildfile variables. + // For example, a name may start with an underscore and thus be + // reserved or it may refer to one of the predefined variables such a + // `include` or `extension` that may have a wrong visibility and/or + // type. + // + // Secondly, this mechanism allows us to encapsulate a group of + // substitutions and pass this group around as a single value. + // + vp.insert>> ("in.substitutions"); + // Fallback value to use for NULL value substitutions. If unspecified, // NULL substitutions are an error. // diff --git a/libbuild2/in/rule.cxx b/libbuild2/in/rule.cxx index 3aa92a2..d07adfc 100644 --- a/libbuild2/in/rule.cxx +++ b/libbuild2/in/rule.cxx @@ -116,6 +116,11 @@ namespace build2 fail << "invalid substitution mode '" << *s << "'"; } + // Substitution map. + // + const substitution_map* smap ( + cast_null>> (t["in.substitutions"])); + // NULL substitutions. // optional null; @@ -251,7 +256,7 @@ namespace build2 substitute (location (ip, ln), a, t, name, flags, - strict, null)); + strict, smap, null)); assert (v); // Rule semantics change without version increment? @@ -379,7 +384,7 @@ namespace build2 a, t, dd, dd_skip, s, 0, - nl, sym, strict, null); + nl, sym, strict, smap, null); ofs << s; } @@ -445,6 +450,7 @@ namespace build2 const char* nl, char sym, bool strict, + const substitution_map* smap, const optional& null) const { // Scan the line looking for substiutions in the $$ form. In the @@ -500,8 +506,7 @@ namespace build2 dd, dd_skip, string (s, b + 1, e - b -1), nullopt /* flags */, - strict, - null)) + strict, smap, null)) { replace_newlines (*val, nl); @@ -522,9 +527,10 @@ namespace build2 const string& n, optional flags, bool strict, + const substitution_map* smap, const optional& null) const { - optional val (substitute (l, a, t, n, flags, strict, null)); + optional val (substitute (l, a, t, n, flags, strict, smap, null)); if (val) { @@ -561,6 +567,7 @@ namespace build2 const string& n, optional flags, bool strict, + const substitution_map* smap, const optional& null) const { // In the lax mode scan the fragment to make sure it is a variable name @@ -585,7 +592,7 @@ namespace build2 } } - return lookup (l, a, t, n, flags, null); + return lookup (l, a, t, n, flags, smap, null); } string rule:: @@ -593,10 +600,32 @@ namespace build2 action, const target& t, const string& n, optional flags, + const substitution_map* smap, const optional& null) const { assert (!flags); + // First look in the substitution map. + // + if (smap != nullptr) + { + auto i (smap->find (n)); + + if (i != smap->end ()) + { + if (i->second) + return *i->second; + + if (null) + return *null; + + fail (loc) << "null value in substitution map entry '" << n << "'" << + info << "use in.null to specify null value substiution string"; + } + } + + // Next look for the buildfile variable. + // auto l (t[n]); if (l.defined ()) @@ -607,9 +636,9 @@ namespace build2 { if (null) return *null; - else - fail (loc) << "null value in variable '" << n << "'" << - info << "use in.null to specify null value substiution string"; + + fail (loc) << "null value in variable '" << n << "'" << + info << "use in.null to specify null value substiution string"; } // For typed values call string() for conversion. diff --git a/libbuild2/in/rule.hxx b/libbuild2/in/rule.hxx index bd0d15a..369fd93 100644 --- a/libbuild2/in/rule.hxx +++ b/libbuild2/in/rule.hxx @@ -54,6 +54,7 @@ namespace build2 // Customization hooks and helpers. // + using substitution_map = map>; // Perform prerequisite search. // @@ -89,6 +90,7 @@ namespace build2 action, const target&, const string& name, optional flags, + const substitution_map*, const optional& null) const; // Perform variable substitution. Return nullopt if it should be @@ -100,6 +102,7 @@ namespace build2 const string& name, optional flags, bool strict, + const substitution_map*, const optional& null) const; // Call the above version and do any necessary depdb saving. @@ -111,6 +114,7 @@ namespace build2 const string& name, optional flags, bool strict, + const substitution_map*, const optional& null) const; // Process a line of input from the specified position performing any @@ -124,6 +128,7 @@ namespace build2 const char* newline, char sym, bool strict, + const substitution_map*, const optional& null) const; // Replace newlines in a multi-line value with the given newline diff --git a/libbuild2/version/rule.cxx b/libbuild2/version/rule.cxx index 1799666..f810ce7 100644 --- a/libbuild2/version/rule.cxx +++ b/libbuild2/version/rule.cxx @@ -115,6 +115,7 @@ namespace build2 const target& t, const string& n, optional flags, + const substitution_map* smap, const optional& null) const { assert (!flags); @@ -138,8 +139,7 @@ namespace build2 a, t, p == string::npos ? n : string (n, p + 1), - nullopt, - null); + nullopt, smap, null); } string pn (n, 0, p); diff --git a/libbuild2/version/rule.hxx b/libbuild2/version/rule.hxx index ba673e5..2903dbf 100644 --- a/libbuild2/version/rule.hxx +++ b/libbuild2/version/rule.hxx @@ -34,6 +34,7 @@ namespace build2 const target&, const string&, optional, + const substitution_map*, const optional&) const override; }; diff --git a/tests/in/testscript b/tests/in/testscript index c4f534c..5dce1d3 100644 --- a/tests/in/testscript +++ b/tests/in/testscript @@ -17,7 +17,9 @@ cat <=test.in; EOI cat <=buildfile; file{test}: in{test} - file{test}: foo = FOO + { + foo = FOO + } EOI $* <<>EOO; @@ -25,6 +27,27 @@ cat test >>EOO; EOO $* clean <<=test.in; + foo = $_foo$ + bar = $bar$ + EOI +cat <=buildfile; + file{test}: in{test} + { + in.substitutions = _foo@FOO + in.substitutions += bar@BAR + bar = wrong + } + EOI +$* <<>EOO; + foo = FOO + bar = BAR + EOO +$* clean <<=test.in; @@ -33,7 +56,9 @@ cat <=test.in; EOI $* <>EOO $10 -- cgit v1.1