aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2021-09-20 09:57:13 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2021-09-20 09:57:13 +0200
commitbdcd4211cf76bc75dd6f9a16fa3835632dfb7f20 (patch)
tree6c928ff6e42b17adc9ff0745b88d5604cadd1f02
parent280c4fc46e8485d52a5faaf4c9585b865080ed7b (diff)
Assign pre-defined semantics to config.<project>.develop variables
This variable allows a project to distinguish between development and consumption builds. While normally there is no distinction between these two modes, sometimes a project may need to provide additional functionality during development. For example, a source code generator which uses its own generated code in its implementation may need to provide a bootstrap step from the pre-generated code. Normally, such a step is only needed during development. See "Project Configuration" in the manual for details.
-rw-r--r--doc/manual.cli33
-rw-r--r--libbuild2/config/operation.cxx29
-rw-r--r--libbuild2/config/utility.cxx4
-rw-r--r--libbuild2/config/utility.hxx37
-rw-r--r--libbuild2/config/utility.ixx13
-rw-r--r--libbuild2/parser.cxx79
-rw-r--r--libbuild2/parser.hxx6
7 files changed, 156 insertions, 45 deletions
diff --git a/doc/manual.cli b/doc/manual.cli
index d344f42..6ca1d8b 100644
--- a/doc/manual.cli
+++ b/doc/manual.cli
@@ -4670,6 +4670,39 @@ 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.|
+A variable in the \c{config.<project>.develop} form has pre-defined
+semantics: it allows a project to distinguish between \i{development} and
+\i{consumption} builds. While normally there is no distinction between these
+two modes, sometimes a project may need to provide additional functionality
+during development. For example, a source code generator which uses its own
+generated code in its implementation may need to provide a bootstrap step from
+the pre-generated code. Normally, such a step is only needed during
+development.
+
+\N|While some communities, such as Rust, believe that building and running
+tests is only done during development, we believe its reasonable for an
+end-user to want to run tests for all their dependencies. As a result, we
+strongly discourage restricting tests to the development mode only. Test are
+an integral part of the project and should always be available.|
+
+If used, the \c{config.<project>.develop} variable should be explicitly
+defined by the project with the \c{bool} type and the \c{false} default
+value. For example:
+
+\
+# build/root.build
+
+config [bool] config.libhello.develop ?= false
+\
+
+\N|If the \c{config.<project>.develop} variable is specified by the user of
+the project but the project does not define it (that is, the project does not
+distinguish between development and consumption), then this variable is
+silently ignored. By default \l{bdep-init(1)} configures projects being
+initialized for development. This can be overridden with explicit
+\c{config.<project>.develop=false}.|
+
+
\h#proj-config-directive|\c{config} Directive|
diff --git a/libbuild2/config/operation.cxx b/libbuild2/config/operation.cxx
index c62528f..5883d8c 100644
--- a/libbuild2/config/operation.cxx
+++ b/libbuild2/config/operation.cxx
@@ -221,17 +221,32 @@ namespace build2
if (size_t n = var->override ())
var = vp.find (string (var->name, 0, n));
+ const string& name (var->name);
+
// Skip special variables.
//
- if (var->name == "config.booted" ||
- var->name == "config.loaded" ||
- var->name == "config.configured" ||
- var->name.compare (0, 14, "config.config.") == 0)
+ if (name == "config.booted" ||
+ name == "config.loaded" ||
+ name == "config.configured" ||
+ name.compare (0, 14, "config.config.") == 0)
continue;
if (mod.find_variable (*var)) // Saved or unsaved.
continue;
+ // Skip config.**.develop variables (see parser::parse_config() for
+ // details).
+ //
+ // In a sense, this variable is always "available" but if the
+ // package does not distinguish between development and consumption,
+ // then specifying config.*.develop=true should be noop.
+ //
+ {
+ size_t p (name.rfind ('.'));
+ if (p != 6 && name.compare (p + 1, string::npos, "develop") == 0)
+ continue;
+ }
+
const value& v (p.first->second);
pair<bool, bool> r (save_config_variable (*var,
@@ -312,9 +327,13 @@ namespace build2
// inherited. We might also not have any value at all (see
// unconfigured()).
//
+ // Note that we must check for null() before attempting any
+ // further tests.
+ //
if (!l.defined () ||
(l->null ? flags & save_null_omitted :
- l->empty () ? flags & save_empty_omitted : false))
+ l->empty () ? flags & save_empty_omitted :
+ (flags & save_false_omitted) != 0 && !cast<bool> (*l)))
continue;
// Handle inherited from outer scope values.
diff --git a/libbuild2/config/utility.cxx b/libbuild2/config/utility.cxx
index 1f1ac08..928709a 100644
--- a/libbuild2/config/utility.cxx
+++ b/libbuild2/config/utility.cxx
@@ -21,7 +21,7 @@ namespace build2
namespace config
{
pair<lookup, bool>
- lookup_config_impl (scope& rs, const variable& var)
+ lookup_config_impl (scope& rs, const variable& var, uint64_t sflags)
{
// This is a stripped-down version of the default value case.
@@ -71,7 +71,7 @@ namespace build2
}
if (l.defined ())
- save_variable (rs, var);
+ save_variable (rs, var, sflags);
return pair<lookup, bool> (l, n);
}
diff --git a/libbuild2/config/utility.hxx b/libbuild2/config/utility.hxx
index cec4bc3..bafcafa 100644
--- a/libbuild2/config/utility.hxx
+++ b/libbuild2/config/utility.hxx
@@ -61,7 +61,8 @@ namespace build2
const uint64_t save_default_commented = 0x01; // Based on value::extra.
const uint64_t save_null_omitted = 0x02; // Treat NULL as undefined.
const uint64_t save_empty_omitted = 0x04; // Treat empty as undefined.
- const uint64_t save_base = 0x08; // Custom save with base.
+ const uint64_t save_false_omitted = 0x08; // Treat false as undefined.
+ const uint64_t save_base = 0x10; // Custom save with base.
inline void
save_variable (scope& rs, const variable& var, uint64_t flags = 0)
@@ -228,9 +229,11 @@ namespace build2
//
// Unlike the rest of the lookup_config() versions, this one leaves the
// unspecified value as undefined rather than setting it to a default
- // value. This can be useful when we don't have a default value or in case
- // we want the mentioning of the variable to be omitted from persistent
- // storage (e.g., a config file) if the default value is used.
+ // value (in this case it also doesn't mark the variable for saving with
+ // the specified flags). This can be useful when we don't have a default
+ // value or in case we want the mentioning of the variable to be omitted
+ // from persistent storage (e.g., a config file) if the default value is
+ // used.
//
// Note also that we can first do the lookup without the default value and
// then, if there is no value, call the version with the default value and
@@ -239,27 +242,37 @@ namespace build2
// expensive. It is also ok to call both versions multiple times provided
// the flags are the same.
//
- // @@ Should we pass flags and interpret save_null_omitted to treat null
- // as undefined? Sounds logical.
+ // @@ Should save_null_omitted be interpreted to treat null as undefined?
+ // Sounds logical.
//
lookup
- lookup_config (scope& rs, const variable&);
+ lookup_config (scope& rs,
+ const variable&,
+ uint64_t save_flags = 0);
lookup
- lookup_config (bool& new_value, scope& rs, const variable&);
+ lookup_config (bool& new_value,
+ scope& rs,
+ const variable&,
+ uint64_t save_flags = 0);
// Note that the variable is expected to have already been entered.
//
inline lookup
- lookup_config (scope& rs, const string& var)
+ lookup_config (scope& rs,
+ const string& var,
+ uint64_t save_flags = 0)
{
- return lookup_config (rs, rs.ctx.var_pool[var]);
+ return lookup_config (rs, rs.ctx.var_pool[var], save_flags);
}
inline lookup
- lookup_config (bool& new_value, scope& rs, const string& var)
+ lookup_config (bool& new_value,
+ scope& rs,
+ const string& var,
+ uint64_t save_flags = 0)
{
- return lookup_config (new_value, rs, rs.ctx.var_pool[var]);
+ return lookup_config (new_value, rs, rs.ctx.var_pool[var], save_flags);
}
// Lookup a config.* variable value and, if the value is undefined, set it
diff --git a/libbuild2/config/utility.ixx b/libbuild2/config/utility.ixx
index 79d5470..d8348bd 100644
--- a/libbuild2/config/utility.ixx
+++ b/libbuild2/config/utility.ixx
@@ -6,22 +6,25 @@ namespace build2
namespace config
{
LIBBUILD2_SYMEXPORT pair<lookup, bool>
- lookup_config_impl (scope&, const variable&);
+ lookup_config_impl (scope&, const variable&, uint64_t);
template <typename T>
pair<lookup, bool>
lookup_config_impl (scope&, const variable&, T&&, uint64_t, bool);
inline lookup
- lookup_config (scope& rs, const variable& var)
+ lookup_config (scope& rs, const variable& var, uint64_t sflags)
{
- return lookup_config_impl (rs, var).first;
+ return lookup_config_impl (rs, var, sflags).first;
}
inline lookup
- lookup_config (bool& new_value, scope& rs, const variable& var)
+ lookup_config (bool& new_value,
+ scope& rs,
+ const variable& var,
+ uint64_t sflags)
{
- auto r (lookup_config_impl (rs, var));
+ auto r (lookup_config_impl (rs, var, sflags));
new_value = new_value || r.second;
return r.first;
}
diff --git a/libbuild2/parser.cxx b/libbuild2/parser.cxx
index f8f463f..a0d1d7a 100644
--- a/libbuild2/parser.cxx
+++ b/libbuild2/parser.cxx
@@ -2780,6 +2780,8 @@ namespace build2
proj = n.variable ();
}
+ const location loc (get_location (t));
+
// We are now in the normal lexing mode and we let the lexer handle `?=`.
//
next_with_attributes (t, tt);
@@ -2839,6 +2841,7 @@ namespace build2
fail (t) << "expected configuration variable name instead of " << t;
string name (move (t.value));
+ bool config (name.compare (0, 7, "config.") == 0);
// As a way to print custom (discovered, computed, etc) configuration
// information we allow specifying a non config.* variable provided it is
@@ -2847,9 +2850,7 @@ namespace build2
bool new_val (false);
lookup l;
- if (report &&
- *report != "false" &&
- name.compare (0, 7, "config.") != 0)
+ if (report && *report != "false" && !config)
{
if (!as.empty ())
fail (as.loc) << "unexpected attributes for report-only variable";
@@ -2880,7 +2881,7 @@ namespace build2
{
diag_record dr;
- if (name.compare (0, 7, "config.") != 0)
+ if (!config)
dr << fail (t) << "configuration variable '" << name
<< "' does not start with 'config.'";
@@ -2915,24 +2916,57 @@ namespace build2
<< var.visibility << " visibility";
}
- // We have to lookup the value whether we have the default part or not
- // in order to mark it as saved. We also have to do this to get the new
- // value status.
+ // See if we have the default value part.
//
- using config::lookup_config;
+ next (t, tt);
+ bool def_val (tt != type::newline && tt != type::eos);
- l = lookup_config (new_val, *root_, var);
+ if (def_val && tt != type::default_assign)
+ fail (t) << "expected '?=' instead of " << t << " after "
+ << "configuration variable name";
- // See if we have the default value part.
+ // If this is the special config.<project>.develop variable, verify it
+ // is of type bool and has false as the default value. We also only save
+ // it in config.build if it's true and suppress any unused warnings in
+ // config::save_config() if specified but not used by the project.
//
- next (t, tt);
+ // Here we also have the unnamed project issues (see above for details)
+ // and so we actually recognize any config.**.develop.
+ //
+ bool dev;
+ {
+ size_t p (var.name.rfind ('.'));
+ dev = p != 6 && var.name.compare (p + 1, string::npos, "develop") == 0;
+ }
- if (tt != type::newline && tt != type::eos)
+ uint64_t sflags (0);
+ if (dev)
{
- if (tt != type::default_assign)
- fail (t) << "expected '?=' instead of " << t << " after "
- << "configuration variable name";
+ if (var.type != &value_traits<bool>::value_type)
+ fail (loc) << var << " variable must be of type bool";
+ // This is quite messy: below we don't always parse the value (plus it
+ // may be computed) so here we just peek at the next token. But we
+ // have to do this in the same mode as parse_variable_value().
+ //
+ if (!def_val ||
+ peek (lexer_mode::value, '@') != type::word ||
+ peeked ().value != "false")
+ fail (loc) << var << " variable default value must be literal false";
+
+ sflags |= config::save_false_omitted;
+ }
+
+ // We have to lookup the value whether we have the default part or not
+ // in order to mark it as saved. We also have to do this to get the new
+ // value status.
+ //
+ l = config::lookup_config (new_val, *root_, var, sflags);
+
+ // Handle the default value.
+ //
+ if (def_val)
+ {
// The rest is the default value which we should parse in the value
// mode. But before switching check whether we need to evaluate it at
// all.
@@ -2941,9 +2975,9 @@ namespace build2
skip_line (t, tt);
else
{
- value lhs, rhs (parse_variable_value (t, tt));
+ value lhs, rhs (parse_variable_value (t, tt, !dev /* mode */));
apply_value_attributes (&var, lhs, move (rhs), type::assign);
- l = lookup_config (new_val, *root_, var, move (lhs));
+ l = config::lookup_config (new_val, *root_, var, move (lhs), sflags);
}
}
}
@@ -4314,10 +4348,15 @@ namespace build2
}
value parser::
- parse_variable_value (token& t, type& tt)
+ parse_variable_value (token& t, type& tt, bool m)
{
- mode (lexer_mode::value, '@');
- next_with_attributes (t, tt);
+ if (m)
+ {
+ mode (lexer_mode::value, '@');
+ next_with_attributes (t, tt);
+ }
+ else
+ next (t, tt);
// Parse value attributes if any. Note that it's ok not to have anything
// after the attributes (e.g., foo=[null]).
diff --git a/libbuild2/parser.hxx b/libbuild2/parser.hxx
index b1ac8b2..4cf52e9 100644
--- a/libbuild2/parser.hxx
+++ b/libbuild2/parser.hxx
@@ -241,8 +241,12 @@ namespace build2
// Note: calls attributes_push() that the caller must pop.
//
+ // If mode is false, assume the appropriate mode has already been switched
+ // to (value, `@` as pair separator, with attributes recognition). This
+ // can be useful, for example, if need to call peek().
+ //
value
- parse_variable_value (token&, token_type&);
+ parse_variable_value (token&, token_type&, bool mode = true);
void
apply_variable_attributes (const variable&);