diff options
author | Boris Kolpackov <boris@codesynthesis.com> | 2015-12-07 12:16:29 +0200 |
---|---|---|
committer | Boris Kolpackov <boris@codesynthesis.com> | 2015-12-07 12:16:29 +0200 |
commit | 8b55e8151bd69e4ef11a67aff57618193f559618 (patch) | |
tree | e420000affbf3c6d7016470649d2cc711b883c7a | |
parent | c092793e74d0778b0aff653860f274c8cc31c374 (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/lexer | 13 | ||||
-rw-r--r-- | build/lexer.cxx | 13 | ||||
-rw-r--r-- | build/parser | 3 | ||||
-rw-r--r-- | build/parser.cxx | 64 | ||||
-rw-r--r-- | build/types | 2 | ||||
-rw-r--r-- | build/utility | 10 | ||||
-rw-r--r-- | build/utility.cxx | 78 | ||||
-rw-r--r-- | tests/version/buildfile | 31 |
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 + +./: |