From bdcd4211cf76bc75dd6f9a16fa3835632dfb7f20 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Mon, 20 Sep 2021 09:57:13 +0200 Subject: Assign pre-defined semantics to config..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. --- doc/manual.cli | 33 ++++++++++++++++++ libbuild2/config/operation.cxx | 29 +++++++++++++--- libbuild2/config/utility.cxx | 4 +-- libbuild2/config/utility.hxx | 37 +++++++++++++------- libbuild2/config/utility.ixx | 13 ++++--- libbuild2/parser.cxx | 79 +++++++++++++++++++++++++++++++----------- libbuild2/parser.hxx | 6 +++- 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..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..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..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..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 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 (*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_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 (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_config_impl (scope&, const variable&); + lookup_config_impl (scope&, const variable&, uint64_t); template pair 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..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::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&); -- cgit v1.1