aboutsummaryrefslogtreecommitdiff
path: root/libbuild2
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2020-03-20 09:51:01 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2020-03-20 09:51:01 +0200
commitda89d944bbc3cca9fd36a4a360f94023134a9a8c (patch)
tree1c31738b128b0628ff2e82817e43df9f9461beb0 /libbuild2
parentce29e3d72ded432b9ac9354ac92c588142de9b89 (diff)
Initial implementation of config directive for project-specific configuration
Diffstat (limited to 'libbuild2')
-rw-r--r--libbuild2/parser.cxx136
-rw-r--r--libbuild2/parser.hxx3
-rw-r--r--libbuild2/scope.hxx12
3 files changed, 144 insertions, 7 deletions
diff --git a/libbuild2/parser.cxx b/libbuild2/parser.cxx
index ec5e161..f20a4c4 100644
--- a/libbuild2/parser.cxx
+++ b/libbuild2/parser.cxx
@@ -20,6 +20,8 @@
#include <libbuild2/diagnostics.hxx>
#include <libbuild2/prerequisite.hxx>
+#include <libbuild2/config/utility.hxx> // lookup_config
+
using namespace std;
using namespace butl;
@@ -411,6 +413,10 @@ namespace build2
{
f = &parser::parse_for;
}
+ else if (n == "config")
+ {
+ f = &parser::parse_config;
+ }
if (f != nullptr)
{
@@ -1640,6 +1646,134 @@ namespace build2
}
void parser::
+ parse_config (token& t, type& tt)
+ {
+ tracer trace ("parser::parse_config", &path_);
+
+ // General config format:
+ //
+ // config [<var-attrs>] <var>[?=[<val-attrs>]<default-val>]
+ //
+
+ // @@ TODO: enforce appears in root.build
+ //
+ if (root_ != scope_)
+ fail (t) << "configuration variable in non-root scope";
+
+ // We enforce the config.<project> prefix.
+ //
+ // Note that this could be a subproject and it could be unnamed (e.g., the
+ // tests subproject). The current thinking is to use hierarchical names
+ // like config.<project>.tests.remote for subprojects, similar to how we
+ // do the same for submodules (e.g., cxx.config). Of course, the
+ // subproject could also be some named third-party top-level project that
+ // we just happened to amalgamate. So what we are going to do is enforce
+ // the config[.**].<project>.** pattern where <project> is the innermost
+ // named project.
+ //
+ string proj;
+ for (auto r (root_), a (root_->strong_scope ());
+ ;
+ r = r->parent_scope ()->root_scope ())
+ {
+ const project_name& n (project (*r));
+ if (!n.empty ())
+ {
+ proj = n.variable ();
+ break;
+ }
+
+ if (r == a)
+ break;
+ }
+
+ if (proj.empty ())
+ fail (t) << "configuration variable in unnamed project";
+
+ // We are now in the normal lexing mode. Since we always have <var> we
+ // don't have to resort to manual parsing (as in import) and can just let
+ // the lexer handle `?=`.
+ //
+ next_with_attributes (t, tt);
+
+ // Get variable attributes, if any.
+ //
+ attributes_push (t, tt);
+
+ if (tt != type::word)
+ fail (t) << "expected configuration variable name instead of " << t;
+
+ string name (move (t.value));
+
+ // Enforce the variable name pattern. The simplest is to check for the
+ // config prefix and the project substring.
+ //
+ {
+ diag_record dr;
+
+ if (name.compare (0, 7, "config.") != 0)
+ dr << fail (t) << "configuration variable '" << name
+ << "' does not start with 'config.'";
+
+ if (name.find ('.' + proj + '.') == string::npos)
+ dr << fail (t) << "configuration variable '" << name
+ << "' does not include project name";
+
+ if (!dr.empty ())
+ dr << info << "expected variable name in the 'config[.**]." << proj
+ << ".**' form";
+ }
+
+ const variable& var (
+ scope_->var_pool ().insert (move (name), true /* overridable */));
+
+ apply_variable_attributes (var);
+
+ // Note that even though we are relying on the config.** variable pattern
+ // to set global visibility, let's make sure as a sanity check.
+ //
+ if (var.visibility != variable_visibility::normal)
+ {
+ fail (t) << "configuration variable " << var << " has " << 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 would also have to do this to get the new
+ // value status if we need it).
+ //
+ using config::lookup_config;
+
+ auto l (lookup_config (*root_, var));
+
+ // See if we have the default value part.
+ //
+ next (t, tt);
+
+ if (tt != type::newline && tt != type::eos)
+ {
+ if (tt != type::default_assign)
+ fail (t) << "expected '?=' instead of " << t << " after configuration "
+ << "variable name";
+
+ // 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.
+ //
+ if (l.defined ())
+ skip_line (t, tt);
+ else
+ {
+ value lhs, rhs (parse_variable_value (t, tt));
+ apply_value_attributes (&var, lhs, move (rhs), type::assign);
+ lookup_config (*root_, var, move (lhs));
+ }
+ }
+
+ next_after_newline (t, tt);
+ }
+
+ void parser::
parse_import (token& t, type& tt)
{
tracer trace ("parser::parse_import", &path_);
@@ -1653,7 +1787,7 @@ namespace build2
//
type atype; // Assignment type.
value* val (nullptr);
- const build2::variable* var (nullptr);
+ const variable* var (nullptr);
// We are now in the normal lexing mode and here is the problem: we need
// to switch to the value mode so that we don't treat certain characters
diff --git a/libbuild2/parser.hxx b/libbuild2/parser.hxx
index f1cd976..dd5cbda 100644
--- a/libbuild2/parser.hxx
+++ b/libbuild2/parser.hxx
@@ -128,6 +128,9 @@ namespace build2
parse_run (token&, token_type&);
void
+ parse_config (token&, token_type&);
+
+ void
parse_import (token&, token_type&);
void
diff --git a/libbuild2/scope.hxx b/libbuild2/scope.hxx
index c49778f..fdc660d 100644
--- a/libbuild2/scope.hxx
+++ b/libbuild2/scope.hxx
@@ -58,11 +58,11 @@ namespace build2
scope* root_scope () {return root_;}
const scope* root_scope () const {return root_;}
- // Root scope of a strong amalgamation of this scope or NULL if
- // this scope is not (yet) in any (known) project. If there is
- // no strong amalgamation, then this function returns the root
- // scope of the project (in other words, in this case a project
- // is treated as its own strong amalgamation).
+ // Root scope of the outermost "strong" (source-based) amalgamation of
+ // this scope or NULL if this scope is not (yet) in any (known) project.
+ // If there is no strong amalgamation, then this function returns the root
+ // scope of the project (in other words, in this case a project is treated
+ // as its own strong amalgamation).
//
scope* strong_scope ();
const scope* strong_scope () const;
@@ -491,7 +491,7 @@ namespace build2
out_src (const dir_path& src,
const dir_path& out_root, const dir_path& src_root);
- // Return the project name or empty string if unnamed.
+ // Return the project name or empty if unnamed.
//
const project_name&
project (const scope& root);