aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2015-12-07 12:16:29 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2015-12-07 12:16:29 +0200
commit8b55e8151bd69e4ef11a67aff57618193f559618 (patch)
treee420000affbf3c6d7016470649d2cc711b883c7a
parentc092793e74d0778b0aff653860f274c8cc31c374 (diff)
Add support for specifying minimum required build2 version
The syntax is: using build@0.1.0-a1 The idea is that we will later also use it for modules and 'build' is a special, the "build system itself" module. Also fix a problem with peeking and lexer mode switching.
-rw-r--r--build/lexer13
-rw-r--r--build/lexer.cxx13
-rw-r--r--build/parser3
-rw-r--r--build/parser.cxx64
-rw-r--r--build/types2
-rw-r--r--build/utility10
-rw-r--r--build/utility.cxx78
-rw-r--r--tests/version/buildfile31
8 files changed, 200 insertions, 14 deletions
diff --git a/build/lexer b/build/lexer
index 6880d39..4a50e2a 100644
--- a/build/lexer
+++ b/build/lexer
@@ -15,6 +15,9 @@
#include <butl/char-scanner>
+#include <build/types>
+#include <build/utility>
+
#include <build/token>
#include <build/diagnostics>
@@ -43,7 +46,7 @@ namespace build
lexer (std::istream& is,
const std::string& name,
void (*processor) (token&, const lexer&) = nullptr)
- : char_scanner (is), fail (name), processor_ (processor)
+ : char_scanner (is), fail (name), processor_ (processor), sep_ (false)
{
mode_.push (lexer_mode::normal);
}
@@ -77,6 +80,13 @@ namespace build
token
next ();
+ // Peek at the first character of the next token. Return the character
+ // or 0 if the next token will be eos. Also return an indicator of
+ // whether the next token will be separated.
+ //
+ pair<char, bool>
+ peek_char ();
+
private:
token
next_impl ();
@@ -121,6 +131,7 @@ namespace build
std::stack<lexer_mode> mode_;
char pair_separator_;
+ bool sep_; // True if we skipped spaces in peek().
};
}
diff --git a/build/lexer.cxx b/build/lexer.cxx
index 79611c6..77e803c 100644
--- a/build/lexer.cxx
+++ b/build/lexer.cxx
@@ -17,6 +17,16 @@ namespace build
return t;
}
+ pair<char, bool> lexer::
+ peek_char ()
+ {
+ // In the quoted mode we don't skip spaces.
+ //
+ sep_ = mode_.top () != lexer_mode::quoted && skip_spaces ();
+ xchar c (peek ());
+ return make_pair (eos (c) ? '\0' : char (c), sep_);
+ }
+
token lexer::
next_impl ()
{
@@ -385,6 +395,9 @@ namespace build
get ();
}
+ r = r || sep_;
+ sep_ = false;
+
return r;
}
diff --git a/build/parser b/build/parser
index f3f88d5..08d587a 100644
--- a/build/parser
+++ b/build/parser
@@ -164,6 +164,9 @@ namespace build
token_type
next (token&, token_type&);
+ // Be careful with peeking and switching the lexer mode. See keyword()
+ // for more information.
+ //
token_type
peek ();
diff --git a/build/parser.cxx b/build/parser.cxx
index a4e2af0..8cfa665 100644
--- a/build/parser.cxx
+++ b/build/parser.cxx
@@ -14,6 +14,7 @@
#include <build/types>
#include <build/utility>
+#include <build/version>
#include <build/token>
#include <build/lexer>
@@ -492,6 +493,7 @@ namespace build
// The rest should be a list of buildfiles. Parse them as names
// to get variable expansion and directory prefixes.
//
+ lexer_->mode (lexer_mode::value);
next (t, tt);
const location l (get_location (t, &path_));
names_type ns (tt != type::newline && tt != type::eos
@@ -573,6 +575,7 @@ namespace build
// The rest should be a list of buildfiles. Parse them as names
// to get variable expansion and directory prefixes.
//
+ lexer_->mode (lexer_mode::value);
next (t, tt);
const location l (get_location (t, &path_));
names_type ns (tt != type::newline && tt != type::eos
@@ -807,20 +810,54 @@ namespace build
// The rest should be a list of module names. Parse them as names
// to get variable expansion, etc.
//
+ lexer_->mode (lexer_mode::pairs, '@');
next (t, tt);
const location l (get_location (t, &path_));
names_type ns (tt != type::newline && tt != type::eos
? names (t, tt)
: names_type ());
- for (name& n: ns)
+ for (auto i (ns.begin ()); i != ns.end (); ++i)
{
- // For now it should be a simple name.
+ string n, v;
+
+ if (!i->simple ())
+ fail (l) << "module name expected instead of " << *i;
+
+ n = move (i->value);
+
+ if (i->pair)
+ {
+ ++i;
+ if (!i->simple ())
+ fail (l) << "module version expected instead of " << *i;
+
+ v = move (i->value);
+ }
+
+ // Handle the special 'build' module.
//
- if (!n.simple ())
- fail (l) << "module name expected instead of " << n;
+ if (n == "build")
+ {
+ if (!v.empty ())
+ {
+ unsigned int iv;
+ try {iv = to_version (v);}
+ catch (const invalid_argument& e)
+ {
+ fail (l) << "invalid version '" << v << "': " << e.what ();
+ }
- load_module (optional, n.value, *root_, *scope_, l);
+ if (iv > BUILD_VERSION)
+ fail (l) << "build2 " << v << " required" <<
+ info << "running build2 " << BUILD_VERSION_STR;
+ }
+ }
+ else
+ {
+ assert (v.empty ()); // Module versioning not yet implemented.
+ load_module (optional, n, *root_, *scope_, l);
+ }
}
if (tt == type::newline)
@@ -1455,7 +1492,7 @@ namespace build
if (n.empty ())
fail (loc) << "empty variable/function name";
- // Figure out whether this is a variable expansion of a function
+ // Figure out whether this is a variable expansion or a function
// call.
//
tt = peek ();
@@ -1749,18 +1786,21 @@ namespace build
// potential keyword if:
//
// - it is not quoted [so a keyword can always be escaped] and
- // - next token is separated or is '(' [so if(...) will work] and
- // - next token is not '=' or '+=' [which means a "directive body"
- // can never start with one of them].
+ // - next token is eos or '(' [so if(...) will work] or
+ // - next token is separated and is not '=' or '+=' [which means a
+ // "directive trailer" can never start with one of them].
//
// See tests/keyword.
//
if (!t.quoted)
{
- type pt (peek ());
+ // We cannot peek at the whole token here since it might have to be
+ // lexed in a different mode. So peek at its first character.
+ //
+ pair<char, bool> p (lexer_->peek_char ());
+ char c (p.first);
- return pt == type::lparen ||
- (pt != type::equal && pt != type::plus_equal && peeked ().separated);
+ return c == '\0' || c == '(' || (p.second && c != '=' && c != '+');
}
return false;
diff --git a/build/types b/build/types
index c119839..2dc9b04 100644
--- a/build/types
+++ b/build/types
@@ -7,6 +7,7 @@
#include <vector>
#include <string>
+#include <utility> // pair
#include <memory> // unique_ptr, shared_ptr
#include <functional> // reference_wrapper
@@ -19,6 +20,7 @@ namespace build
{
// Commonly-used types.
//
+ using std::pair;
using std::string;
using std::unique_ptr;
using std::shared_ptr;
diff --git a/build/utility b/build/utility
index 404b33a..6769f95 100644
--- a/build/utility
+++ b/build/utility
@@ -7,7 +7,7 @@
#include <tuple>
#include <string>
-#include <utility> // move()
+#include <utility> // move(), make_pair()
#include <cassert> // assert()
#include <exception>
#include <unordered_set>
@@ -17,6 +17,7 @@
namespace build
{
using std::move;
+ using std::make_pair;
// Empty string and path.
//
@@ -24,6 +25,13 @@ namespace build
extern const path empty_path;
extern const dir_path empty_dir_path;
+ // Parse version string in the X.Y.Z[-{a|b}N] to a version integer in the
+ // AABBCCDD form as describe in <build/version>. Throw invalid_argument
+ // if the passed string is not a valid version.
+ //
+ unsigned int
+ to_version (const string&);
+
// Call a function if there is an exception.
//
diff --git a/build/utility.cxx b/build/utility.cxx
index d394186..7ee679b 100644
--- a/build/utility.cxx
+++ b/build/utility.cxx
@@ -4,6 +4,8 @@
#include <build/utility>
+#include <cstdlib> // strtol()
+
using namespace std;
namespace build
@@ -12,5 +14,81 @@ namespace build
const path empty_path;
const dir_path empty_dir_path;
+ unsigned int
+ to_version (const string& s)
+ {
+ // See tests/version.
+ //
+
+ auto parse = [&s] (size_t& p, const char* m, long min = 0, long max = 99)
+ -> unsigned int
+ {
+ if (s[p] == '-' || s[p] == '+') // stoi() allows these.
+ throw invalid_argument (m);
+
+ const char* b (s.c_str () + p);
+ char* e;
+ long r (strtol (b, &e, 10));
+
+ if (b == e || r < min || r > max)
+ throw invalid_argument (m);
+
+ p = e - s.c_str ();
+ return static_cast<unsigned int> (r);
+ };
+
+ auto bail = [] (const char* m) {throw invalid_argument (m);};
+
+ unsigned int ma, mi, bf, ab;
+
+ size_t p (0), n (s.size ());
+ ma = parse (p, "invalid major version");
+
+ if (p >= n || s[p] != '.')
+ bail ("'.' expected after major version");
+
+ mi = parse (++p, "invalid minor version");
+
+ if (p >= n || s[p] != '.')
+ bail ("'.' expected after minor version");
+
+ bf = parse (++p, "invalid bugfix version");
+
+ if (p < n)
+ {
+ if (s[p] != '-')
+ bail ("'-' expected after bugfix version");
+
+ char k (s[++p]);
+
+ if (k != 'a' && k != 'b')
+ bail ("'a' or 'b' expected in release component");
+
+ ab = parse (++p, "invalid release component", 1, 49);
+
+ if (p != n)
+ bail ("junk after release component");
+
+ if (k == 'b')
+ ab += 50;
+ }
+
+ // AABBCCDD
+ unsigned int r (ma * 1000000U +
+ mi * 10000U +
+ bf * 100U);
+
+ if (ab != 0)
+ {
+ if (r == 0)
+ bail ("0.0.0 version with release component");
+
+ r -= 100;
+ r += ab;
+ }
+
+ return r;
+ }
+
bool exception_unwinding_dtor = false;
}
diff --git a/tests/version/buildfile b/tests/version/buildfile
new file mode 100644
index 0000000..eaf2f77
--- /dev/null
+++ b/tests/version/buildfile
@@ -0,0 +1,31 @@
+#using build@-1.0.0
+#using build@+1.0.0
+#using build@x.0.0
+#using build@1x.0.0
+#using build@1
+#using build@1.
+#using build@1.x
+#using build@1.1
+#using build@1.1.
+#using build@1.1.x
+#using build@1.1.100
+#using build@1.1.1~
+#using build@1.1.1-d
+#using build@1.1.1-aX
+#using build@1.1.1-a0
+#using build@1.1.1-a99
+#using build@1.1.1-a1X
+#using build@0.0.0-a1
+
+using build@0.0.0
+using build@0.0.1
+using build@0.0.1-a1
+using build@0.0.1-b2
+
+#using build@0.1.0
+using build@0.1.0-a1
+
+#using build@1.1.0
+#using build@1.1.0-b1
+
+./: