From 546edb8e6a0b610c2db2a0bef878f28cd395bd77 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Thu, 11 Nov 2021 13:20:30 +0200 Subject: WIP: depdb dep infra --- libbuild2/build/script/builtin-options.cxx | 697 +++++++++++++++++++++++++++++ libbuild2/build/script/builtin-options.hxx | 398 ++++++++++++++++ libbuild2/build/script/builtin-options.ixx | 248 ++++++++++ libbuild2/build/script/builtin.cli | 25 ++ libbuild2/build/script/parser.cxx | 261 ++++++++--- libbuild2/build/script/parser.hxx | 15 +- libbuild2/buildfile | 76 ++-- libbuild2/script/parser.cxx | 2 +- 8 files changed, 1629 insertions(+), 93 deletions(-) create mode 100644 libbuild2/build/script/builtin-options.cxx create mode 100644 libbuild2/build/script/builtin-options.hxx create mode 100644 libbuild2/build/script/builtin-options.ixx create mode 100644 libbuild2/build/script/builtin.cli diff --git a/libbuild2/build/script/builtin-options.cxx b/libbuild2/build/script/builtin-options.cxx new file mode 100644 index 0000000..dfb8e62 --- /dev/null +++ b/libbuild2/build/script/builtin-options.cxx @@ -0,0 +1,697 @@ +// -*- C++ -*- +// +// This file was generated by CLI, a command line interface +// compiler for C++. +// + +// Begin prologue. +// +// +// End prologue. + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace build2 +{ + namespace build + { + namespace script + { + namespace cli + { + // unknown_option + // + unknown_option:: + ~unknown_option () throw () + { + } + + void unknown_option:: + print (::std::ostream& os) const + { + os << "unknown option '" << option ().c_str () << "'"; + } + + const char* unknown_option:: + what () const throw () + { + return "unknown option"; + } + + // unknown_argument + // + unknown_argument:: + ~unknown_argument () throw () + { + } + + void unknown_argument:: + print (::std::ostream& os) const + { + os << "unknown argument '" << argument ().c_str () << "'"; + } + + const char* unknown_argument:: + what () const throw () + { + return "unknown argument"; + } + + // missing_value + // + missing_value:: + ~missing_value () throw () + { + } + + void missing_value:: + print (::std::ostream& os) const + { + os << "missing value for option '" << option ().c_str () << "'"; + } + + const char* missing_value:: + what () const throw () + { + return "missing option value"; + } + + // invalid_value + // + invalid_value:: + ~invalid_value () throw () + { + } + + void invalid_value:: + print (::std::ostream& os) const + { + os << "invalid value '" << value ().c_str () << "' for option '" + << option ().c_str () << "'"; + + if (!message ().empty ()) + os << ": " << message ().c_str (); + } + + const char* invalid_value:: + what () const throw () + { + return "invalid option value"; + } + + // eos_reached + // + void eos_reached:: + print (::std::ostream& os) const + { + os << what (); + } + + const char* eos_reached:: + what () const throw () + { + return "end of argument stream reached"; + } + + // scanner + // + scanner:: + ~scanner () + { + } + + // argv_scanner + // + bool argv_scanner:: + more () + { + return i_ < argc_; + } + + const char* argv_scanner:: + peek () + { + if (i_ < argc_) + return argv_[i_]; + else + throw eos_reached (); + } + + const char* argv_scanner:: + next () + { + if (i_ < argc_) + { + const char* r (argv_[i_]); + + if (erase_) + { + for (int i (i_ + 1); i < argc_; ++i) + argv_[i - 1] = argv_[i]; + + --argc_; + argv_[argc_] = 0; + } + else + ++i_; + + ++start_position_; + return r; + } + else + throw eos_reached (); + } + + void argv_scanner:: + skip () + { + if (i_ < argc_) + { + ++i_; + ++start_position_; + } + else + throw eos_reached (); + } + + std::size_t argv_scanner:: + position () + { + return start_position_; + } + + // vector_scanner + // + bool vector_scanner:: + more () + { + return i_ < v_.size (); + } + + const char* vector_scanner:: + peek () + { + if (i_ < v_.size ()) + return v_[i_].c_str (); + else + throw eos_reached (); + } + + const char* vector_scanner:: + next () + { + if (i_ < v_.size ()) + return v_[i_++].c_str (); + else + throw eos_reached (); + } + + void vector_scanner:: + skip () + { + if (i_ < v_.size ()) + ++i_; + else + throw eos_reached (); + } + + std::size_t vector_scanner:: + position () + { + return start_position_ + i_; + } + + template + struct parser + { + static void + parse (X& x, bool& xs, scanner& s) + { + using namespace std; + + const char* o (s.next ()); + if (s.more ()) + { + string v (s.next ()); + istringstream is (v); + if (!(is >> x && is.peek () == istringstream::traits_type::eof ())) + throw invalid_value (o, v); + } + else + throw missing_value (o); + + xs = true; + } + }; + + template <> + struct parser + { + static void + parse (bool& x, scanner& s) + { + s.next (); + x = true; + } + }; + + template <> + struct parser + { + static void + parse (std::string& x, bool& xs, scanner& s) + { + const char* o (s.next ()); + + if (s.more ()) + x = s.next (); + else + throw missing_value (o); + + xs = true; + } + }; + + template + struct parser > + { + static void + parse (std::pair& x, bool& xs, scanner& s) + { + x.second = s.position (); + parser::parse (x.first, xs, s); + } + }; + + template + struct parser > + { + static void + parse (std::vector& c, bool& xs, scanner& s) + { + X x; + bool dummy; + parser::parse (x, dummy, s); + c.push_back (x); + xs = true; + } + }; + + template + struct parser > + { + static void + parse (std::set& c, bool& xs, scanner& s) + { + X x; + bool dummy; + parser::parse (x, dummy, s); + c.insert (x); + xs = true; + } + }; + + template + struct parser > + { + static void + parse (std::map& m, bool& xs, scanner& s) + { + const char* o (s.next ()); + + if (s.more ()) + { + std::size_t pos (s.position ()); + std::string ov (s.next ()); + std::string::size_type p = ov.find ('='); + + K k = K (); + V v = V (); + std::string kstr (ov, 0, p); + std::string vstr (ov, (p != std::string::npos ? p + 1 : ov.size ())); + + int ac (2); + char* av[] = + { + const_cast (o), + 0 + }; + + bool dummy; + if (!kstr.empty ()) + { + av[1] = const_cast (kstr.c_str ()); + argv_scanner s (0, ac, av, false, pos); + parser::parse (k, dummy, s); + } + + if (!vstr.empty ()) + { + av[1] = const_cast (vstr.c_str ()); + argv_scanner s (0, ac, av, false, pos); + parser::parse (v, dummy, s); + } + + m[k] = v; + } + else + throw missing_value (o); + + xs = true; + } + }; + + template + void + thunk (X& x, scanner& s) + { + parser::parse (x.*M, s); + } + + template + void + thunk (X& x, scanner& s) + { + parser::parse (x.*M, x.*S, s); + } + } + } + } +} + +#include +#include + +namespace build2 +{ + namespace build + { + namespace script + { + // depdb_dep_options + // + + depdb_dep_options:: + depdb_dep_options () + : file_ (), + file_specified_ (false), + format_ (), + format_specified_ (false) + { + } + + depdb_dep_options:: + depdb_dep_options (int& argc, + char** argv, + bool erase, + ::build2::build::script::cli::unknown_mode opt, + ::build2::build::script::cli::unknown_mode arg) + : file_ (), + file_specified_ (false), + format_ (), + format_specified_ (false) + { + ::build2::build::script::cli::argv_scanner s (argc, argv, erase); + _parse (s, opt, arg); + } + + depdb_dep_options:: + depdb_dep_options (int start, + int& argc, + char** argv, + bool erase, + ::build2::build::script::cli::unknown_mode opt, + ::build2::build::script::cli::unknown_mode arg) + : file_ (), + file_specified_ (false), + format_ (), + format_specified_ (false) + { + ::build2::build::script::cli::argv_scanner s (start, argc, argv, erase); + _parse (s, opt, arg); + } + + depdb_dep_options:: + depdb_dep_options (int& argc, + char** argv, + int& end, + bool erase, + ::build2::build::script::cli::unknown_mode opt, + ::build2::build::script::cli::unknown_mode arg) + : file_ (), + file_specified_ (false), + format_ (), + format_specified_ (false) + { + ::build2::build::script::cli::argv_scanner s (argc, argv, erase); + _parse (s, opt, arg); + end = s.end (); + } + + depdb_dep_options:: + depdb_dep_options (int start, + int& argc, + char** argv, + int& end, + bool erase, + ::build2::build::script::cli::unknown_mode opt, + ::build2::build::script::cli::unknown_mode arg) + : file_ (), + file_specified_ (false), + format_ (), + format_specified_ (false) + { + ::build2::build::script::cli::argv_scanner s (start, argc, argv, erase); + _parse (s, opt, arg); + end = s.end (); + } + + depdb_dep_options:: + depdb_dep_options (::build2::build::script::cli::scanner& s, + ::build2::build::script::cli::unknown_mode opt, + ::build2::build::script::cli::unknown_mode arg) + : file_ (), + file_specified_ (false), + format_ (), + format_specified_ (false) + { + _parse (s, opt, arg); + } + + typedef + std::map + _cli_depdb_dep_options_map; + + static _cli_depdb_dep_options_map _cli_depdb_dep_options_map_; + + struct _cli_depdb_dep_options_map_init + { + _cli_depdb_dep_options_map_init () + { + _cli_depdb_dep_options_map_["--file"] = + &::build2::build::script::cli::thunk< depdb_dep_options, string, &depdb_dep_options::file_, + &depdb_dep_options::file_specified_ >; + _cli_depdb_dep_options_map_["--format"] = + &::build2::build::script::cli::thunk< depdb_dep_options, string, &depdb_dep_options::format_, + &depdb_dep_options::format_specified_ >; + } + }; + + static _cli_depdb_dep_options_map_init _cli_depdb_dep_options_map_init_; + + bool depdb_dep_options:: + _parse (const char* o, ::build2::build::script::cli::scanner& s) + { + _cli_depdb_dep_options_map::const_iterator i (_cli_depdb_dep_options_map_.find (o)); + + if (i != _cli_depdb_dep_options_map_.end ()) + { + (*(i->second)) (*this, s); + return true; + } + + return false; + } + + bool depdb_dep_options:: + _parse (::build2::build::script::cli::scanner& s, + ::build2::build::script::cli::unknown_mode opt_mode, + ::build2::build::script::cli::unknown_mode arg_mode) + { + // Can't skip combined flags (--no-combined-flags). + // + assert (opt_mode != ::build2::build::script::cli::unknown_mode::skip); + + bool r = false; + bool opt = true; + + while (s.more ()) + { + const char* o = s.peek (); + + if (std::strcmp (o, "--") == 0) + { + opt = false; + s.skip (); + r = true; + continue; + } + + if (opt) + { + if (_parse (o, s)) + { + r = true; + continue; + } + + if (std::strncmp (o, "-", 1) == 0 && o[1] != '\0') + { + // Handle combined option values. + // + std::string co; + if (const char* v = std::strchr (o, '=')) + { + co.assign (o, 0, v - o); + ++v; + + int ac (2); + char* av[] = + { + const_cast (co.c_str ()), + const_cast (v) + }; + + ::build2::build::script::cli::argv_scanner ns (0, ac, av); + + if (_parse (co.c_str (), ns)) + { + // Parsed the option but not its value? + // + if (ns.end () != 2) + throw ::build2::build::script::cli::invalid_value (co, v); + + s.next (); + r = true; + continue; + } + else + { + // Set the unknown option and fall through. + // + o = co.c_str (); + } + } + + // Handle combined flags. + // + char cf[3]; + { + const char* p = o + 1; + for (; *p != '\0'; ++p) + { + if (!((*p >= 'a' && *p <= 'z') || + (*p >= 'A' && *p <= 'Z') || + (*p >= '0' && *p <= '9'))) + break; + } + + if (*p == '\0') + { + for (p = o + 1; *p != '\0'; ++p) + { + std::strcpy (cf, "-"); + cf[1] = *p; + cf[2] = '\0'; + + int ac (1); + char* av[] = + { + cf + }; + + ::build2::build::script::cli::argv_scanner ns (0, ac, av); + + if (!_parse (cf, ns)) + break; + } + + if (*p == '\0') + { + // All handled. + // + s.next (); + r = true; + continue; + } + else + { + // Set the unknown option and fall through. + // + o = cf; + } + } + } + + switch (opt_mode) + { + case ::build2::build::script::cli::unknown_mode::skip: + { + s.skip (); + r = true; + continue; + } + case ::build2::build::script::cli::unknown_mode::stop: + { + break; + } + case ::build2::build::script::cli::unknown_mode::fail: + { + throw ::build2::build::script::cli::unknown_option (o); + } + } + + break; + } + } + + switch (arg_mode) + { + case ::build2::build::script::cli::unknown_mode::skip: + { + s.skip (); + r = true; + continue; + } + case ::build2::build::script::cli::unknown_mode::stop: + { + break; + } + case ::build2::build::script::cli::unknown_mode::fail: + { + throw ::build2::build::script::cli::unknown_argument (o); + } + } + + break; + } + + return r; + } + } + } +} + +// Begin epilogue. +// +// +// End epilogue. + diff --git a/libbuild2/build/script/builtin-options.hxx b/libbuild2/build/script/builtin-options.hxx new file mode 100644 index 0000000..ccfd54b --- /dev/null +++ b/libbuild2/build/script/builtin-options.hxx @@ -0,0 +1,398 @@ +// -*- C++ -*- +// +// This file was generated by CLI, a command line interface +// compiler for C++. +// + +#ifndef LIBBUILD2_BUILD_SCRIPT_BUILTIN_OPTIONS_HXX +#define LIBBUILD2_BUILD_SCRIPT_BUILTIN_OPTIONS_HXX + +// Begin prologue. +// +// +// End prologue. + +#include +#include +#include +#include +#include + +#ifndef CLI_POTENTIALLY_UNUSED +# if defined(_MSC_VER) || defined(__xlC__) +# define CLI_POTENTIALLY_UNUSED(x) (void*)&x +# else +# define CLI_POTENTIALLY_UNUSED(x) (void)x +# endif +#endif + +namespace build2 +{ + namespace build + { + namespace script + { + namespace cli + { + class unknown_mode + { + public: + enum value + { + skip, + stop, + fail + }; + + unknown_mode (value); + + operator value () const + { + return v_; + } + + private: + value v_; + }; + + // Exceptions. + // + + class exception: public std::exception + { + public: + virtual void + print (::std::ostream&) const = 0; + }; + + ::std::ostream& + operator<< (::std::ostream&, const exception&); + + class unknown_option: public exception + { + public: + virtual + ~unknown_option () throw (); + + unknown_option (const std::string& option); + + const std::string& + option () const; + + virtual void + print (::std::ostream&) const; + + virtual const char* + what () const throw (); + + private: + std::string option_; + }; + + class unknown_argument: public exception + { + public: + virtual + ~unknown_argument () throw (); + + unknown_argument (const std::string& argument); + + const std::string& + argument () const; + + virtual void + print (::std::ostream&) const; + + virtual const char* + what () const throw (); + + private: + std::string argument_; + }; + + class missing_value: public exception + { + public: + virtual + ~missing_value () throw (); + + missing_value (const std::string& option); + + const std::string& + option () const; + + virtual void + print (::std::ostream&) const; + + virtual const char* + what () const throw (); + + private: + std::string option_; + }; + + class invalid_value: public exception + { + public: + virtual + ~invalid_value () throw (); + + invalid_value (const std::string& option, + const std::string& value, + const std::string& message = std::string ()); + + const std::string& + option () const; + + const std::string& + value () const; + + const std::string& + message () const; + + virtual void + print (::std::ostream&) const; + + virtual const char* + what () const throw (); + + private: + std::string option_; + std::string value_; + std::string message_; + }; + + class eos_reached: public exception + { + public: + virtual void + print (::std::ostream&) const; + + virtual const char* + what () const throw (); + }; + + // Command line argument scanner interface. + // + // The values returned by next() are guaranteed to be valid + // for the two previous arguments up until a call to a third + // peek() or next(). + // + // The position() function returns a monotonically-increasing + // number which, if stored, can later be used to determine the + // relative position of the argument returned by the following + // call to next(). Note that if multiple scanners are used to + // extract arguments from multiple sources, then the end + // position of the previous scanner should be used as the + // start position of the next. + // + class scanner + { + public: + virtual + ~scanner (); + + virtual bool + more () = 0; + + virtual const char* + peek () = 0; + + virtual const char* + next () = 0; + + virtual void + skip () = 0; + + virtual std::size_t + position () = 0; + }; + + class argv_scanner: public scanner + { + public: + argv_scanner (int& argc, + char** argv, + bool erase = false, + std::size_t start_position = 0); + + argv_scanner (int start, + int& argc, + char** argv, + bool erase = false, + std::size_t start_position = 0); + + int + end () const; + + virtual bool + more (); + + virtual const char* + peek (); + + virtual const char* + next (); + + virtual void + skip (); + + virtual std::size_t + position (); + + protected: + std::size_t start_position_; + int i_; + int& argc_; + char** argv_; + bool erase_; + }; + + class vector_scanner: public scanner + { + public: + vector_scanner (const std::vector&, + std::size_t start = 0, + std::size_t start_position = 0); + + std::size_t + end () const; + + void + reset (std::size_t start = 0, std::size_t start_position = 0); + + virtual bool + more (); + + virtual const char* + peek (); + + virtual const char* + next (); + + virtual void + skip (); + + virtual std::size_t + position (); + + private: + std::size_t start_position_; + const std::vector& v_; + std::size_t i_; + }; + + template + struct parser; + } + } + } +} + +#include + +namespace build2 +{ + namespace build + { + namespace script + { + class depdb_dep_options + { + public: + depdb_dep_options (); + + depdb_dep_options (int& argc, + char** argv, + bool erase = false, + ::build2::build::script::cli::unknown_mode option = ::build2::build::script::cli::unknown_mode::fail, + ::build2::build::script::cli::unknown_mode argument = ::build2::build::script::cli::unknown_mode::stop); + + depdb_dep_options (int start, + int& argc, + char** argv, + bool erase = false, + ::build2::build::script::cli::unknown_mode option = ::build2::build::script::cli::unknown_mode::fail, + ::build2::build::script::cli::unknown_mode argument = ::build2::build::script::cli::unknown_mode::stop); + + depdb_dep_options (int& argc, + char** argv, + int& end, + bool erase = false, + ::build2::build::script::cli::unknown_mode option = ::build2::build::script::cli::unknown_mode::fail, + ::build2::build::script::cli::unknown_mode argument = ::build2::build::script::cli::unknown_mode::stop); + + depdb_dep_options (int start, + int& argc, + char** argv, + int& end, + bool erase = false, + ::build2::build::script::cli::unknown_mode option = ::build2::build::script::cli::unknown_mode::fail, + ::build2::build::script::cli::unknown_mode argument = ::build2::build::script::cli::unknown_mode::stop); + + depdb_dep_options (::build2::build::script::cli::scanner&, + ::build2::build::script::cli::unknown_mode option = ::build2::build::script::cli::unknown_mode::fail, + ::build2::build::script::cli::unknown_mode argument = ::build2::build::script::cli::unknown_mode::stop); + + // Option accessors and modifiers. + // + const string& + file () const; + + string& + file (); + + void + file (const string&); + + bool + file_specified () const; + + void + file_specified (bool); + + const string& + format () const; + + string& + format (); + + void + format (const string&); + + bool + format_specified () const; + + void + format_specified (bool); + + // Implementation details. + // + protected: + bool + _parse (const char*, ::build2::build::script::cli::scanner&); + + private: + bool + _parse (::build2::build::script::cli::scanner&, + ::build2::build::script::cli::unknown_mode option, + ::build2::build::script::cli::unknown_mode argument); + + public: + string file_; + bool file_specified_; + string format_; + bool format_specified_; + }; + } + } +} + +#include + +// Begin epilogue. +// +// +// End epilogue. + +#endif // LIBBUILD2_BUILD_SCRIPT_BUILTIN_OPTIONS_HXX diff --git a/libbuild2/build/script/builtin-options.ixx b/libbuild2/build/script/builtin-options.ixx new file mode 100644 index 0000000..3263803 --- /dev/null +++ b/libbuild2/build/script/builtin-options.ixx @@ -0,0 +1,248 @@ +// -*- C++ -*- +// +// This file was generated by CLI, a command line interface +// compiler for C++. +// + +// Begin prologue. +// +// +// End prologue. + +#include + +namespace build2 +{ + namespace build + { + namespace script + { + namespace cli + { + // unknown_mode + // + inline unknown_mode:: + unknown_mode (value v) + : v_ (v) + { + } + + // exception + // + inline ::std::ostream& + operator<< (::std::ostream& os, const exception& e) + { + e.print (os); + return os; + } + + // unknown_option + // + inline unknown_option:: + unknown_option (const std::string& option) + : option_ (option) + { + } + + inline const std::string& unknown_option:: + option () const + { + return option_; + } + + // unknown_argument + // + inline unknown_argument:: + unknown_argument (const std::string& argument) + : argument_ (argument) + { + } + + inline const std::string& unknown_argument:: + argument () const + { + return argument_; + } + + // missing_value + // + inline missing_value:: + missing_value (const std::string& option) + : option_ (option) + { + } + + inline const std::string& missing_value:: + option () const + { + return option_; + } + + // invalid_value + // + inline invalid_value:: + invalid_value (const std::string& option, + const std::string& value, + const std::string& message) + : option_ (option), + value_ (value), + message_ (message) + { + } + + inline const std::string& invalid_value:: + option () const + { + return option_; + } + + inline const std::string& invalid_value:: + value () const + { + return value_; + } + + inline const std::string& invalid_value:: + message () const + { + return message_; + } + + // argv_scanner + // + inline argv_scanner:: + argv_scanner (int& argc, + char** argv, + bool erase, + std::size_t sp) + : start_position_ (sp + 1), + i_ (1), + argc_ (argc), + argv_ (argv), + erase_ (erase) + { + } + + inline argv_scanner:: + argv_scanner (int start, + int& argc, + char** argv, + bool erase, + std::size_t sp) + : start_position_ (sp + static_cast (start)), + i_ (start), + argc_ (argc), + argv_ (argv), + erase_ (erase) + { + } + + inline int argv_scanner:: + end () const + { + return i_; + } + + // vector_scanner + // + inline vector_scanner:: + vector_scanner (const std::vector& v, + std::size_t i, + std::size_t sp) + : start_position_ (sp), v_ (v), i_ (i) + { + } + + inline std::size_t vector_scanner:: + end () const + { + return i_; + } + + inline void vector_scanner:: + reset (std::size_t i, std::size_t sp) + { + i_ = i; + start_position_ = sp; + } + } + } + } +} + +namespace build2 +{ + namespace build + { + namespace script + { + // depdb_dep_options + // + + inline const string& depdb_dep_options:: + file () const + { + return this->file_; + } + + inline string& depdb_dep_options:: + file () + { + return this->file_; + } + + inline void depdb_dep_options:: + file (const string& x) + { + this->file_ = x; + } + + inline bool depdb_dep_options:: + file_specified () const + { + return this->file_specified_; + } + + inline void depdb_dep_options:: + file_specified (bool x) + { + this->file_specified_ = x; + } + + inline const string& depdb_dep_options:: + format () const + { + return this->format_; + } + + inline string& depdb_dep_options:: + format () + { + return this->format_; + } + + inline void depdb_dep_options:: + format (const string& x) + { + this->format_ = x; + } + + inline bool depdb_dep_options:: + format_specified () const + { + return this->format_specified_; + } + + inline void depdb_dep_options:: + format_specified (bool x) + { + this->format_specified_ = x; + } + } + } +} + +// Begin epilogue. +// +// +// End epilogue. diff --git a/libbuild2/build/script/builtin.cli b/libbuild2/build/script/builtin.cli new file mode 100644 index 0000000..6e66d67 --- /dev/null +++ b/libbuild2/build/script/builtin.cli @@ -0,0 +1,25 @@ +// file : libbuild2/build/script/builtin.cli +// license : MIT; see accompanying LICENSE file + +include ; + +// Note that options in this file are undocumented because we generate neither +// the usage printing code nor man pages. Instead, they are documented in the +// manual. +// +namespace build2 +{ + namespace build + { + namespace script + { + // Pseudo-builtin options. + // + class depdb_dep_options + { + string --file; // @@ TMP path + string --format; + }; + } + } +} diff --git a/libbuild2/build/script/parser.cxx b/libbuild2/build/script/parser.cxx index 217fa11..4314b73 100644 --- a/libbuild2/build/script/parser.cxx +++ b/libbuild2/build/script/parser.cxx @@ -10,6 +10,7 @@ #include #include +#include using namespace std; using namespace butl; @@ -487,7 +488,11 @@ namespace build2 next (t, tt); if (tt != type::word || - (v != "clear" && v != "hash" && v != "string" && v != "env")) + (v != "clear" && + v != "hash" && + v != "string" && + v != "env" && + v != "dep")) { fail (get_location (t)) << "expected 'depdb' builtin command instead of " << t; @@ -533,6 +538,28 @@ namespace build2 } else { + // Verify depdb-dep is last. + // + if (v == "dep") + { + // Note that for now we do not allow multiple depdb-dep calls. + // But we may wan to relax this later (though alternating + // targets with prerequisites may be tricky -- maybe still + // only allow additional targets in the first call). + // + if (!depdb_dep_) + depdb_dep_ = l; + else + fail (l) << "multiple 'depdb dep' calls" << + info (*depdb_dep_) << "previous call is here"; + } + else + { + if (depdb_dep_) + fail (l) << "'depdb " << v << "' after 'depdb dep'" << + info (*depdb_dep_) << "'depdb dep' call is here"; + } + // Move the script body to the end of the depdb preamble. // // Note that at this (pre-parsing) stage we cannot evaluate if @@ -917,75 +944,80 @@ namespace build2 { if (tt == type::word && t.value == "depdb") { - names ns (exec_special (t, tt)); + next (t, tt); // This should have been enforced during pre-parsing. // - assert (!ns.empty ()); // ... + assert (tt == type::word); // ... - const string& cmd (ns[0].value); + string cmd (move (t.value)); - if (cmd == "hash") + if (cmd == "dep") { - sha256 cs; - for (auto i (ns.begin () + 1); i != ns.end (); ++i) // Skip . - to_checksum (cs, *i); - - if (ctx.dd.expect (cs.string ()) != nullptr) - l4 ([&] { - ctx.trace (ll) - << "'depdb hash' argument change forcing update of " - << ctx.env.target;}); + exec_depdb_dep (t, tt, ll); } - else if (cmd == "string") + else { - string s; - try - { - s = convert ( - names (make_move_iterator (ns.begin () + 1), - make_move_iterator (ns.end ()))); - } - catch (const invalid_argument& e) + names ns (exec_special (t, tt, true /* skip */)); + + if (cmd == "hash") { - fail (ll) << "invalid 'depdb string' argument: " << e; + sha256 cs; + for (const name& n: ns) + to_checksum (cs, n); + + if (ctx.dd.expect (cs.string ()) != nullptr) + l4 ([&] { + ctx.trace (ll) + << "'depdb hash' argument change forcing update of " + << ctx.env.target;}); } - - if (ctx.dd.expect (s) != nullptr) - l4 ([&] { - ctx.trace (ll) - << "'depdb string' argument change forcing update of " - << ctx.env.target;}); - } - else if (cmd == "env") - { - sha256 cs; - const char* pf ("invalid 'depdb env' argument: "); - - try + else if (cmd == "string") { - // Skip . - // - for (auto i (ns.begin () + 1); i != ns.end (); ++i) + string s; + try { - string vn (convert (move (*i))); - build2::script::verify_environment_var_name (vn, pf, ll); - hash_environment (cs, vn); + s = convert (move (ns)); } + catch (const invalid_argument& e) + { + fail (ll) << "invalid 'depdb string' argument: " << e; + } + + if (ctx.dd.expect (s) != nullptr) + l4 ([&] { + ctx.trace (ll) + << "'depdb string' argument change forcing update of " + << ctx.env.target;}); } - catch (const invalid_argument& e) + else if (cmd == "env") { - fail (ll) << pf << e; - } + sha256 cs; + const char* pf ("invalid 'depdb env' argument: "); - if (ctx.dd.expect (cs.string ()) != nullptr) - l4 ([&] { - ctx.trace (ll) - << "'depdb env' environment change forcing update of " - << ctx.env.target;}); + try + { + for (name& n: ns) + { + string vn (convert (move (n))); + build2::script::verify_environment_var_name (vn, pf, ll); + hash_environment (cs, vn); + } + } + catch (const invalid_argument& e) + { + fail (ll) << pf << e; + } + + if (ctx.dd.expect (cs.string ()) != nullptr) + l4 ([&] { + ctx.trace (ll) + << "'depdb env' environment change forcing update of " + << ctx.env.target;}); + } + else + assert (false); } - else - assert (false); } else { @@ -1097,18 +1129,16 @@ namespace build2 } names parser:: - exec_special (token& t, build2::script::token_type& tt, - bool omit_builtin) + exec_special (token& t, build2::script::token_type& tt, bool skip_first) { - if (omit_builtin) + if (skip_first) { assert (tt != type::newline && tt != type::eos); - next (t, tt); } return tt != type::newline && tt != type::eos - ? parse_names (t, tt, pattern_mode::expand) + ? parse_names (t, tt, pattern_mode::ignore) : names (); } @@ -1134,6 +1164,121 @@ namespace build2 return r; } + void parser:: + exec_depdb_dep (token& t, build2::script::token_type& tt, + const location& ll) + { + // Similar approach to parse_env_builtin(). + // + next (t, tt); // Skip 'dep' command. + + // Note that an option name and value can belong to different name + // chunks. That's why we parse the arguments in the chunking mode into + // the list up to the `--` separator and parse this list into options + // afterwards. Note that the `--` separator should be omitted if there + // is no program (i.e., additional dependency info is being read from + // one of the prerequisites). + // + strings args; + bool prog (false); + + names ns; // Reuse to reduce allocations. + while (tt != type::newline && tt != type::eos) + { + if (tt == type::word && t.value == "--") + { + prog = true; + break; + } + + location l (get_location (t)); + + if (!start_names (tt)) + fail (l) << "depdb dep: expected option or '--' separator " + << "instead of " << t; + + parse_names (t, tt, + ns, + pattern_mode::ignore, + true /* chunk */, + "depdb dep builtin argument", + nullptr); + + for (name& n: ns) + { + try + { + args.push_back (convert (move (n))); + } + catch (const invalid_argument&) + { + diag_record dr (fail (l)); + dr << "invalid string value "; + to_stream (dr.os, n, true /* quote */); + } + } + + ns.clear (); + } + + if (prog) + { + next (t, tt); // Skip '--'. + + if (tt == type::newline || tt == type::eos) + fail (t) << "depdb dep: expected program name instead of " << t; + } + + // Parse the options. + // + depdb_dep_options ops; + try + { + cli::vector_scanner scan (args); + ops = depdb_dep_options (scan); + + if (scan.more ()) + fail (ll) << "depdb dep: unexpected argument '" << scan.next () + << "'"; + } + catch (const cli::exception& e) + { + fail (ll) << "depdb dep: " << e; + } + + optional file; + if (ops.file_specified ()) + { + file = path (ops.file ()); // @@ TMP path + + // @@ TODO: file must be absolute. + } + + if (prog) + { + // Run the remainder of the command line as a program (which can be + // a pipe). If file is absent, then redirect the command's stdout to + // a pipe. Otherwise, assume the command writes to file and add it + // to the clenups. + // + // Note that MSVC /showInclude sends its output to stderr (and so + // could do other broken tools). However, the user can always merge + // stderr to stdout (2>&1). + // + // @@ can we somehow get it as butl::process (but what if it's a + // builtin)? + + // @@ TODO + } + else + { + // Assume file is one of the prerequisites. + // + if (!file) + fail (ll) << "depdb dep: program or --file expected"; + } + } + // When add a special variable don't forget to update lexer::word(). // bool parser:: diff --git a/libbuild2/build/script/parser.hxx b/libbuild2/build/script/parser.hxx index b737a13..f3ddfba 100644 --- a/libbuild2/build/script/parser.hxx +++ b/libbuild2/build/script/parser.hxx @@ -119,8 +119,11 @@ namespace build2 exec_lines (const lines&, const function&); names - exec_special (token& t, build2::script::token_type& tt, - bool omit_builtin = true); + exec_special (token&, build2::script::token_type&, bool skip_first); + + void + exec_depdb_dep (token&, build2::script::token_type&, + const location&); // Helpers. // @@ -219,8 +222,12 @@ namespace build2 // depdb env - Track the environment variables change as a // hash. // - optional depdb_clear_; // 'depdb clear' location if any. - lines depdb_preamble_; // Note: excludes 'depdb clear'. + // depdb dep ... - Extract additional dependency information. + // Can only be the last depdb builtin call. + // + optional depdb_clear_; // depdb-clear location if any. + optional depdb_dep_; // depdb-dep location if any. + lines depdb_preamble_; // Note: excludes depdb-clear. // If present, the first impure function called in the body of the // script that performs update of a file-based target. diff --git a/libbuild2/buildfile b/libbuild2/buildfile index 17003b5..51747fd 100644 --- a/libbuild2/buildfile +++ b/libbuild2/buildfile @@ -32,7 +32,8 @@ lib{build2}: libul{build2}: \ libul{build2}: script/{hxx ixx txx cxx}{** -*-options -**.test...} \ script/{hxx ixx cxx}{builtin-options} -libul{build2}: build/{hxx ixx txx cxx}{** -**.test...} +libul{build2}: build/script/{hxx ixx txx cxx}{** -*-options -**.test...} \ + build/script/{hxx ixx cxx}{builtin-options} # Note that this won't work in libul{} since it's not installed. # @@ -216,38 +217,53 @@ else # Generated options parser. # -script/ +# @@ Consider generating common cli runtime namespace if adding more +# option files. +# +if $cli.configured { - if $cli.configured + cli.options += --std c++11 -I $src_root --include-with-brackets \ +--generate-vector-scanner --generate-modifier --generate-specifier \ +--suppress-usage + + cli.cxx{*}: { - cli.cxx{builtin-options}: cli{builtin} - - cli.options += --std c++11 -I $src_root --include-with-brackets \ ---include-prefix libbuild2/script --guard-prefix LIBBUILD2_SCRIPT \ ---cli-namespace build2::script::cli --generate-vector-scanner \ ---generate-modifier --generate-specifier --suppress-usage - - cli.cxx{*}: - { - # Include the generated cli files into the distribution and don't remove - # them when cleaning in src (so that clean results in a state identical - # to distributed). But don't install their headers since they are only - # used internally in the testscript implementation. - # - dist = true - clean = ($src_root != $out_root) - install = false - - # We keep the generated code in the repository so copy it back to src in - # case of a forwarded configuration. - # - backlink = overwrite - } - } - else - # No install for the pre-generated case. + # Include the generated cli files into the distribution and don't remove + # them when cleaning in src (so that clean results in a state identical + # to distributed). But don't install their headers since they are only + # used internally in the testscript implementation. + # + dist = true + clean = ($src_root != $out_root) + install = false + + # We keep the generated code in the repository so copy it back to src in + # case of a forwarded configuration. # - hxx{builtin-options}@./ ixx{builtin-options}@./: install = false + backlink = overwrite + } + + script/cli.cxx{builtin-options}: script/cli{builtin} + { + cli.options += --cli-namespace build2::script::cli \ +--include-prefix libbuild2/script --guard-prefix LIBBUILD2_SCRIPT + } + + build/script/cli.cxx{builtin-options}: build/script/cli{builtin} + { + cli.options += --cli-namespace build2::build::script::cli \ +--include-prefix libbuild2/build/script --guard-prefix LIBBUILD2_BUILD_SCRIPT + } +} +else +{ + # No install for the pre-generated case. + # + script/hxx{builtin-options}@./ \ + script/ixx{builtin-options}@./: install = false + + build/script/hxx{builtin-options}@./ \ + build/script/ixx{builtin-options}@./: install = false } # Install into the libbuild2/ subdirectory of, say, /usr/include/ diff --git a/libbuild2/script/parser.cxx b/libbuild2/script/parser.cxx index fffe7bb..7722002 100644 --- a/libbuild2/script/parser.cxx +++ b/libbuild2/script/parser.cxx @@ -1313,7 +1313,7 @@ namespace build2 // Note that an option name and value can belong to different name // chunks. That's why we parse the env builtin arguments in the chunking // mode into the argument/location pair list up to the '--' separator - // and parse this list into the variable sets/unsets afterwords. + // and parse this list into the variable sets/unsets afterwards. // // Align the size with environment_vars (double because of -u // which is two arguments). -- cgit v1.1