diff options
author | Karen Arutyunov <karen@codesynthesis.com> | 2022-02-17 16:33:27 +0300 |
---|---|---|
committer | Karen Arutyunov <karen@codesynthesis.com> | 2022-02-18 17:15:18 +0300 |
commit | 2835794b28d482b1e391dc85f79dfa91f9e63d3e (patch) | |
tree | 9d6378809644329c62df5caef536337566b9a86f /libbuild2 | |
parent | 68da2afcaa84479142e80e23712793f6ed3e2beb (diff) |
Move parse_cmdline() to libbuild2
Diffstat (limited to 'libbuild2')
-rw-r--r-- | libbuild2/b-options.cxx | 1710 | ||||
-rw-r--r-- | libbuild2/b-options.hxx | 726 | ||||
-rw-r--r-- | libbuild2/b-options.ixx | 585 | ||||
-rw-r--r-- | libbuild2/b.cli | 751 | ||||
-rw-r--r-- | libbuild2/buildfile | 46 | ||||
-rw-r--r-- | libbuild2/cmdline.cxx | 408 | ||||
-rw-r--r-- | libbuild2/cmdline.hxx | 29 | ||||
-rw-r--r-- | libbuild2/types-parsers.cxx | 53 | ||||
-rw-r--r-- | libbuild2/types-parsers.hxx | 48 |
9 files changed, 4347 insertions, 9 deletions
diff --git a/libbuild2/b-options.cxx b/libbuild2/b-options.cxx new file mode 100644 index 0000000..86f5bfe --- /dev/null +++ b/libbuild2/b-options.cxx @@ -0,0 +1,1710 @@ +// -*- C++ -*- +// +// This file was generated by CLI, a command line interface +// compiler for C++. +// + +// Begin prologue. +// +#include <libbuild2/types-parsers.hxx> +// +// End prologue. + +#include <libbuild2/b-options.hxx> + +#include <map> +#include <set> +#include <string> +#include <vector> +#include <utility> +#include <ostream> +#include <sstream> +#include <cstring> +#include <fstream> + +namespace build2 +{ + namespace build + { + 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"; + } + + // file_io_failure + // + file_io_failure:: + ~file_io_failure () throw () + { + } + + void file_io_failure:: + print (::std::ostream& os) const + { + os << "unable to open file '" << file ().c_str () << "' or read failure"; + } + + const char* file_io_failure:: + what () const throw () + { + return "unable to open file or read failure"; + } + + // unmatched_quote + // + unmatched_quote:: + ~unmatched_quote () throw () + { + } + + void unmatched_quote:: + print (::std::ostream& os) const + { + os << "unmatched quote in argument '" << argument ().c_str () << "'"; + } + + const char* unmatched_quote:: + what () const throw () + { + return "unmatched quote"; + } + + // 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_; + } + + // argv_file_scanner + // + int argv_file_scanner::zero_argc_ = 0; + std::string argv_file_scanner::empty_string_; + + bool argv_file_scanner:: + more () + { + if (!args_.empty ()) + return true; + + while (base::more ()) + { + // See if the next argument is the file option. + // + const char* a (base::peek ()); + const option_info* oi = 0; + const char* ov = 0; + + if (!skip_) + { + if ((oi = find (a)) != 0) + { + base::next (); + + if (!base::more ()) + throw missing_value (a); + + ov = base::next (); + } + else if (std::strncmp (a, "-", 1) == 0) + { + if ((ov = std::strchr (a, '=')) != 0) + { + std::string o (a, 0, ov - a); + if ((oi = find (o.c_str ())) != 0) + { + base::next (); + ++ov; + } + } + } + } + + if (oi != 0) + { + if (oi->search_func != 0) + { + std::string f (oi->search_func (ov, oi->arg)); + + if (!f.empty ()) + load (f); + } + else + load (ov); + + if (!args_.empty ()) + return true; + } + else + { + if (!skip_) + skip_ = (std::strcmp (a, "--") == 0); + + return true; + } + } + + return false; + } + + const char* argv_file_scanner:: + peek () + { + if (!more ()) + throw eos_reached (); + + return args_.empty () ? base::peek () : args_.front ().value.c_str (); + } + + const std::string& argv_file_scanner:: + peek_file () + { + if (!more ()) + throw eos_reached (); + + return args_.empty () ? empty_string_ : *args_.front ().file; + } + + std::size_t argv_file_scanner:: + peek_line () + { + if (!more ()) + throw eos_reached (); + + return args_.empty () ? 0 : args_.front ().line; + } + + const char* argv_file_scanner:: + next () + { + if (!more ()) + throw eos_reached (); + + if (args_.empty ()) + return base::next (); + else + { + hold_[i_ == 0 ? ++i_ : --i_].swap (args_.front ().value); + args_.pop_front (); + ++start_position_; + return hold_[i_].c_str (); + } + } + + void argv_file_scanner:: + skip () + { + if (!more ()) + throw eos_reached (); + + if (args_.empty ()) + return base::skip (); + else + { + args_.pop_front (); + ++start_position_; + } + } + + const argv_file_scanner::option_info* argv_file_scanner:: + find (const char* a) const + { + for (std::size_t i (0); i < options_count_; ++i) + if (std::strcmp (a, options_[i].option) == 0) + return &options_[i]; + + return 0; + } + + std::size_t argv_file_scanner:: + position () + { + return start_position_; + } + + void argv_file_scanner:: + load (const std::string& file) + { + using namespace std; + + ifstream is (file.c_str ()); + + if (!is.is_open ()) + throw file_io_failure (file); + + files_.push_back (file); + + arg a; + a.file = &*files_.rbegin (); + + for (a.line = 1; !is.eof (); ++a.line) + { + string line; + getline (is, line); + + if (is.fail () && !is.eof ()) + throw file_io_failure (file); + + string::size_type n (line.size ()); + + // Trim the line from leading and trailing whitespaces. + // + if (n != 0) + { + const char* f (line.c_str ()); + const char* l (f + n); + + const char* of (f); + while (f < l && (*f == ' ' || *f == '\t' || *f == '\r')) + ++f; + + --l; + + const char* ol (l); + while (l > f && (*l == ' ' || *l == '\t' || *l == '\r')) + --l; + + if (f != of || l != ol) + line = f <= l ? string (f, l - f + 1) : string (); + } + + // Ignore empty lines, those that start with #. + // + if (line.empty () || line[0] == '#') + continue; + + string::size_type p (string::npos); + if (line.compare (0, 1, "-") == 0) + { + p = line.find (' '); + + string::size_type q (line.find ('=')); + if (q != string::npos && q < p) + p = q; + } + + string s1; + if (p != string::npos) + { + s1.assign (line, 0, p); + + // Skip leading whitespaces in the argument. + // + if (line[p] == '=') + ++p; + else + { + n = line.size (); + for (++p; p < n; ++p) + { + char c (line[p]); + if (c != ' ' && c != '\t' && c != '\r') + break; + } + } + } + else if (!skip_) + skip_ = (line == "--"); + + string s2 (line, p != string::npos ? p : 0); + + // If the string (which is an option value or argument) is + // wrapped in quotes, remove them. + // + n = s2.size (); + char cf (s2[0]), cl (s2[n - 1]); + + if (cf == '"' || cf == '\'' || cl == '"' || cl == '\'') + { + if (n == 1 || cf != cl) + throw unmatched_quote (s2); + + s2 = string (s2, 1, n - 2); + } + + if (!s1.empty ()) + { + // See if this is another file option. + // + const option_info* oi; + if (!skip_ && (oi = find (s1.c_str ()))) + { + if (s2.empty ()) + throw missing_value (oi->option); + + if (oi->search_func != 0) + { + string f (oi->search_func (s2.c_str (), oi->arg)); + if (!f.empty ()) + load (f); + } + else + { + // If the path of the file being parsed is not simple and the + // path of the file that needs to be loaded is relative, then + // complete the latter using the former as a base. + // +#ifndef _WIN32 + string::size_type p (file.find_last_of ('/')); + bool c (p != string::npos && s2[0] != '/'); +#else + string::size_type p (file.find_last_of ("/\\")); + bool c (p != string::npos && s2[1] != ':'); +#endif + if (c) + s2.insert (0, file, 0, p + 1); + + load (s2); + } + + continue; + } + + a.value = s1; + args_.push_back (a); + } + + a.value = s2; + args_.push_back (a); + } + } + + template <typename X> + 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; + } + + static void + merge (X& b, const X& a) + { + b = a; + } + }; + + template <> + struct parser<bool> + { + static void + parse (bool& x, scanner& s) + { + s.next (); + x = true; + } + + static void + merge (bool& b, const bool&) + { + b = true; + } + }; + + template <> + struct parser<std::string> + { + 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; + } + + static void + merge (std::string& b, const std::string& a) + { + b = a; + } + }; + + template <typename X> + struct parser<std::pair<X, std::size_t> > + { + static void + parse (std::pair<X, std::size_t>& x, bool& xs, scanner& s) + { + x.second = s.position (); + parser<X>::parse (x.first, xs, s); + } + + static void + merge (std::pair<X, std::size_t>& b, const std::pair<X, std::size_t>& a) + { + b = a; + } + }; + + template <typename X> + struct parser<std::vector<X> > + { + static void + parse (std::vector<X>& c, bool& xs, scanner& s) + { + X x; + bool dummy; + parser<X>::parse (x, dummy, s); + c.push_back (x); + xs = true; + } + + static void + merge (std::vector<X>& b, const std::vector<X>& a) + { + b.insert (b.end (), a.begin (), a.end ()); + } + }; + + template <typename X, typename C> + struct parser<std::set<X, C> > + { + static void + parse (std::set<X, C>& c, bool& xs, scanner& s) + { + X x; + bool dummy; + parser<X>::parse (x, dummy, s); + c.insert (x); + xs = true; + } + + static void + merge (std::set<X, C>& b, const std::set<X, C>& a) + { + b.insert (a.begin (), a.end ()); + } + }; + + template <typename K, typename V, typename C> + struct parser<std::map<K, V, C> > + { + static void + parse (std::map<K, V, C>& 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<char*> (o), + 0 + }; + + bool dummy; + if (!kstr.empty ()) + { + av[1] = const_cast<char*> (kstr.c_str ()); + argv_scanner s (0, ac, av, false, pos); + parser<K>::parse (k, dummy, s); + } + + if (!vstr.empty ()) + { + av[1] = const_cast<char*> (vstr.c_str ()); + argv_scanner s (0, ac, av, false, pos); + parser<V>::parse (v, dummy, s); + } + + m[k] = v; + } + else + throw missing_value (o); + + xs = true; + } + + static void + merge (std::map<K, V, C>& b, const std::map<K, V, C>& a) + { + for (typename std::map<K, V, C>::const_iterator i (a.begin ()); + i != a.end (); + ++i) + b[i->first] = i->second; + } + }; + + template <typename X, typename T, T X::*M> + void + thunk (X& x, scanner& s) + { + parser<T>::parse (x.*M, s); + } + + template <typename X, typename T, T X::*M, bool X::*S> + void + thunk (X& x, scanner& s) + { + parser<T>::parse (x.*M, x.*S, s); + } + } + } +} + +#include <map> +#include <cstring> + +namespace build2 +{ + // options + // + + options:: + options () + : build2_metadata_ (), + build2_metadata_specified_ (false), + v_ (), + V_ (), + quiet_ (), + silent_ (), + verbose_ (1), + verbose_specified_ (false), + stat_ (), + dump_ (), + dump_specified_ (false), + progress_ (), + no_progress_ (), + jobs_ (), + jobs_specified_ (false), + max_jobs_ (), + max_jobs_specified_ (false), + queue_depth_ (4), + queue_depth_specified_ (false), + file_cache_ (), + file_cache_specified_ (false), + max_stack_ (), + max_stack_specified_ (false), + serial_stop_ (), + dry_run_ (), + match_only_ (), + no_external_modules_ (), + structured_result_ (), + mtime_check_ (), + no_mtime_check_ (), + no_column_ (), + no_line_ (), + buildfile_ (), + buildfile_specified_ (false), + config_guess_ (), + config_guess_specified_ (false), + config_sub_ (), + config_sub_specified_ (false), + pager_ (), + pager_specified_ (false), + pager_option_ (), + pager_option_specified_ (false), + options_file_ (), + options_file_specified_ (false), + default_options_ (), + default_options_specified_ (false), + no_default_options_ (), + help_ (), + version_ () + { + } + + bool options:: + parse (int& argc, + char** argv, + bool erase, + ::build2::build::cli::unknown_mode opt, + ::build2::build::cli::unknown_mode arg) + { + ::build2::build::cli::argv_scanner s (argc, argv, erase); + bool r = _parse (s, opt, arg); + return r; + } + + bool options:: + parse (int start, + int& argc, + char** argv, + bool erase, + ::build2::build::cli::unknown_mode opt, + ::build2::build::cli::unknown_mode arg) + { + ::build2::build::cli::argv_scanner s (start, argc, argv, erase); + bool r = _parse (s, opt, arg); + return r; + } + + bool options:: + parse (int& argc, + char** argv, + int& end, + bool erase, + ::build2::build::cli::unknown_mode opt, + ::build2::build::cli::unknown_mode arg) + { + ::build2::build::cli::argv_scanner s (argc, argv, erase); + bool r = _parse (s, opt, arg); + end = s.end (); + return r; + } + + bool options:: + parse (int start, + int& argc, + char** argv, + int& end, + bool erase, + ::build2::build::cli::unknown_mode opt, + ::build2::build::cli::unknown_mode arg) + { + ::build2::build::cli::argv_scanner s (start, argc, argv, erase); + bool r = _parse (s, opt, arg); + end = s.end (); + return r; + } + + bool options:: + parse (::build2::build::cli::scanner& s, + ::build2::build::cli::unknown_mode opt, + ::build2::build::cli::unknown_mode arg) + { + bool r = _parse (s, opt, arg); + return r; + } + + void options:: + merge (const options& a) + { + CLI_POTENTIALLY_UNUSED (a); + + if (a.build2_metadata_specified_) + { + ::build2::build::cli::parser< uint64_t>::merge ( + this->build2_metadata_, a.build2_metadata_); + this->build2_metadata_specified_ = true; + } + + if (a.v_) + { + ::build2::build::cli::parser< bool>::merge ( + this->v_, a.v_); + } + + if (a.V_) + { + ::build2::build::cli::parser< bool>::merge ( + this->V_, a.V_); + } + + if (a.quiet_) + { + ::build2::build::cli::parser< bool>::merge ( + this->quiet_, a.quiet_); + } + + if (a.silent_) + { + ::build2::build::cli::parser< bool>::merge ( + this->silent_, a.silent_); + } + + if (a.verbose_specified_) + { + ::build2::build::cli::parser< uint16_t>::merge ( + this->verbose_, a.verbose_); + this->verbose_specified_ = true; + } + + if (a.stat_) + { + ::build2::build::cli::parser< bool>::merge ( + this->stat_, a.stat_); + } + + if (a.dump_specified_) + { + ::build2::build::cli::parser< std::set<string>>::merge ( + this->dump_, a.dump_); + this->dump_specified_ = true; + } + + if (a.progress_) + { + ::build2::build::cli::parser< bool>::merge ( + this->progress_, a.progress_); + } + + if (a.no_progress_) + { + ::build2::build::cli::parser< bool>::merge ( + this->no_progress_, a.no_progress_); + } + + if (a.jobs_specified_) + { + ::build2::build::cli::parser< size_t>::merge ( + this->jobs_, a.jobs_); + this->jobs_specified_ = true; + } + + if (a.max_jobs_specified_) + { + ::build2::build::cli::parser< size_t>::merge ( + this->max_jobs_, a.max_jobs_); + this->max_jobs_specified_ = true; + } + + if (a.queue_depth_specified_) + { + ::build2::build::cli::parser< size_t>::merge ( + this->queue_depth_, a.queue_depth_); + this->queue_depth_specified_ = true; + } + + if (a.file_cache_specified_) + { + ::build2::build::cli::parser< string>::merge ( + this->file_cache_, a.file_cache_); + this->file_cache_specified_ = true; + } + + if (a.max_stack_specified_) + { + ::build2::build::cli::parser< size_t>::merge ( + this->max_stack_, a.max_stack_); + this->max_stack_specified_ = true; + } + + if (a.serial_stop_) + { + ::build2::build::cli::parser< bool>::merge ( + this->serial_stop_, a.serial_stop_); + } + + if (a.dry_run_) + { + ::build2::build::cli::parser< bool>::merge ( + this->dry_run_, a.dry_run_); + } + + if (a.match_only_) + { + ::build2::build::cli::parser< bool>::merge ( + this->match_only_, a.match_only_); + } + + if (a.no_external_modules_) + { + ::build2::build::cli::parser< bool>::merge ( + this->no_external_modules_, a.no_external_modules_); + } + + if (a.structured_result_) + { + ::build2::build::cli::parser< bool>::merge ( + this->structured_result_, a.structured_result_); + } + + if (a.mtime_check_) + { + ::build2::build::cli::parser< bool>::merge ( + this->mtime_check_, a.mtime_check_); + } + + if (a.no_mtime_check_) + { + ::build2::build::cli::parser< bool>::merge ( + this->no_mtime_check_, a.no_mtime_check_); + } + + if (a.no_column_) + { + ::build2::build::cli::parser< bool>::merge ( + this->no_column_, a.no_column_); + } + + if (a.no_line_) + { + ::build2::build::cli::parser< bool>::merge ( + this->no_line_, a.no_line_); + } + + if (a.buildfile_specified_) + { + ::build2::build::cli::parser< path>::merge ( + this->buildfile_, a.buildfile_); + this->buildfile_specified_ = true; + } + + if (a.config_guess_specified_) + { + ::build2::build::cli::parser< path>::merge ( + this->config_guess_, a.config_guess_); + this->config_guess_specified_ = true; + } + + if (a.config_sub_specified_) + { + ::build2::build::cli::parser< path>::merge ( + this->config_sub_, a.config_sub_); + this->config_sub_specified_ = true; + } + + if (a.pager_specified_) + { + ::build2::build::cli::parser< string>::merge ( + this->pager_, a.pager_); + this->pager_specified_ = true; + } + + if (a.pager_option_specified_) + { + ::build2::build::cli::parser< strings>::merge ( + this->pager_option_, a.pager_option_); + this->pager_option_specified_ = true; + } + + if (a.options_file_specified_) + { + ::build2::build::cli::parser< string>::merge ( + this->options_file_, a.options_file_); + this->options_file_specified_ = true; + } + + if (a.default_options_specified_) + { + ::build2::build::cli::parser< dir_path>::merge ( + this->default_options_, a.default_options_); + this->default_options_specified_ = true; + } + + if (a.no_default_options_) + { + ::build2::build::cli::parser< bool>::merge ( + this->no_default_options_, a.no_default_options_); + } + + if (a.help_) + { + ::build2::build::cli::parser< bool>::merge ( + this->help_, a.help_); + } + + if (a.version_) + { + ::build2::build::cli::parser< bool>::merge ( + this->version_, a.version_); + } + } + + ::build2::build::cli::usage_para options:: + print_usage (::std::ostream& os, ::build2::build::cli::usage_para p) + { + CLI_POTENTIALLY_UNUSED (os); + + if (p != ::build2::build::cli::usage_para::none) + os << ::std::endl; + + os << "\033[1mOPTIONS\033[0m" << ::std::endl; + + os << std::endl + << "\033[1m-v\033[0m Print actual commands being executed. This options is" << ::std::endl + << " equivalent to \033[1m--verbose 2\033[0m." << ::std::endl; + + os << std::endl + << "\033[1m-V\033[0m Print all underlying commands being executed. This" << ::std::endl + << " options is equivalent to \033[1m--verbose 3\033[0m." << ::std::endl; + + os << std::endl + << "\033[1m--quiet\033[0m|\033[1m-q\033[0m Run quietly, only printing error messages in most" << ::std::endl + << " contexts. In certain contexts (for example, while" << ::std::endl + << " updating build system modules) this verbosity level may" << ::std::endl + << " be ignored. Use \033[1m--silent\033[0m to run quietly in all contexts." << ::std::endl + << " This option is equivalent to \033[1m--verbose 0\033[0m." << ::std::endl; + + os << std::endl + << "\033[1m--silent\033[0m Run quietly, only printing error messages in all" << ::std::endl + << " contexts." << ::std::endl; + + os << std::endl + << "\033[1m--verbose\033[0m \033[4mlevel\033[0m Set the diagnostics verbosity to \033[4mlevel\033[0m between 0 and 6." << ::std::endl + << " Level 0 disables any non-error messages (but see the" << ::std::endl + << " difference between \033[1m--quiet\033[0m and \033[1m--silent\033[0m) while level 6" << ::std::endl + << " produces lots of information, with level 1 being the" << ::std::endl + << " default. The following additional types of diagnostics" << ::std::endl + << " are produced at each level:" << ::std::endl + << ::std::endl + << " 1. High-level information messages." << ::std::endl + << " 2. Essential underlying commands being executed." << ::std::endl + << " 3. All underlying commands being executed." << ::std::endl + << " 4. Information that could be helpful to the user." << ::std::endl + << " 5. Information that could be helpful to the developer." << ::std::endl + << " 6. Even more detailed information." << ::std::endl; + + os << std::endl + << "\033[1m--stat\033[0m Display build statistics." << ::std::endl; + + os << std::endl + << "\033[1m--dump\033[0m \033[4mphase\033[0m Dump the build system state after the specified phase." << ::std::endl + << " Valid \033[4mphase\033[0m values are \033[1mload\033[0m (after loading \033[1mbuildfiles\033[0m)" << ::std::endl + << " and \033[1mmatch\033[0m (after matching rules to targets). Repeat this" << ::std::endl + << " option to dump the state after multiple phases." << ::std::endl; + + os << std::endl + << "\033[1m--progress\033[0m Display build progress. If printing to a terminal the" << ::std::endl + << " progress is displayed by default for low verbosity" << ::std::endl + << " levels. Use \033[1m--no-progress\033[0m to suppress." << ::std::endl; + + os << std::endl + << "\033[1m--no-progress\033[0m Don't display build progress." << ::std::endl; + + os << std::endl + << "\033[1m--jobs\033[0m|\033[1m-j\033[0m \033[4mnum\033[0m Number of active jobs to perform in parallel. This" << ::std::endl + << " includes both the number of active threads inside the" << ::std::endl + << " build system as well as the number of external commands" << ::std::endl + << " (compilers, linkers, etc) started but not yet finished." << ::std::endl + << " If this option is not specified or specified with the \033[1m0\033[0m" << ::std::endl + << " value, then the number of available hardware threads is" << ::std::endl + << " used." << ::std::endl; + + os << std::endl + << "\033[1m--max-jobs\033[0m|\033[1m-J\033[0m \033[4mnum\033[0m Maximum number of jobs (threads) to create. The default" << ::std::endl + << " is 8x the number of active jobs (\033[1m--jobs|j\033[0m) on 32-bit" << ::std::endl + << " architectures and 32x on 64-bit. See the build system" << ::std::endl + << " scheduler implementation for details." << ::std::endl; + + os << std::endl + << "\033[1m--queue-depth\033[0m|\033[1m-Q\033[0m \033[4mnum\033[0m The queue depth as a multiplier over the number of active" << ::std::endl + << " jobs. Normally we want a deeper queue if the jobs take" << ::std::endl + << " long (for example, compilation) and shorter if they are" << ::std::endl + << " quick (for example, simple tests). The default is 4. See" << ::std::endl + << " the build system scheduler implementation for details." << ::std::endl; + + os << std::endl + << "\033[1m--file-cache\033[0m \033[4mimpl\033[0m File cache implementation to use for intermediate build" << ::std::endl + << " results. Valid values are \033[1mnoop\033[0m (no caching or" << ::std::endl + << " compression) and \033[1msync-lz4\033[0m (no caching with synchronous" << ::std::endl + << " LZ4 on-disk compression). If this option is not" << ::std::endl + << " specified, then a suitable default implementation is used" << ::std::endl + << " (currently \033[1msync-lz4\033[0m)." << ::std::endl; + + os << std::endl + << "\033[1m--max-stack\033[0m \033[4mnum\033[0m The maximum stack size in KBytes to allow for newly" << ::std::endl + << " created threads. For \033[4mpthreads\033[0m-based systems the driver" << ::std::endl + << " queries the stack size of the main thread and uses the" << ::std::endl + << " same size for creating additional threads. This allows" << ::std::endl + << " adjusting the stack size using familiar mechanisms, such" << ::std::endl + << " as \033[1mulimit\033[0m. Sometimes, however, the stack size of the main" << ::std::endl + << " thread is excessively large. As a result, the driver" << ::std::endl + << " checks if it is greater than a predefined limit (64MB on" << ::std::endl + << " 64-bit systems and 32MB on 32-bit ones) and caps it to a" << ::std::endl + << " more sensible value (8MB) if that's the case. This option" << ::std::endl + << " allows you to override this check with the special zero" << ::std::endl + << " value indicating that the main thread stack size should" << ::std::endl + << " be used as is." << ::std::endl; + + os << std::endl + << "\033[1m--serial-stop\033[0m|\033[1m-s\033[0m Run serially and stop at the first error. This mode is" << ::std::endl + << " useful to investigate build failures that are caused by" << ::std::endl + << " build system errors rather than compilation errors. Note" << ::std::endl + << " that if you don't want to keep going but still want" << ::std::endl + << " parallel execution, add \033[1m--jobs|-j\033[0m (for example \033[1m-j 0\033[0m for" << ::std::endl + << " default concurrency)." << ::std::endl; + + os << std::endl + << "\033[1m--dry-run\033[0m|\033[1m-n\033[0m Print commands without actually executing them. Note that" << ::std::endl + << " commands that are required to create an accurate build" << ::std::endl + << " state will still be executed and the extracted auxiliary" << ::std::endl + << " dependency information saved. In other words, this is not" << ::std::endl + << " the \033[4m\"don't touch the filesystem\"\033[0m mode but rather \033[4m\"do" << ::std::endl + << " minimum amount of work to show what needs to be done\"\033[0m." << ::std::endl + << " Note also that only the \033[1mperform\033[0m meta-operation supports" << ::std::endl + << " this mode." << ::std::endl; + + os << std::endl + << "\033[1m--match-only\033[0m Match the rules but do not execute the operation. This" << ::std::endl + << " mode is primarily useful for profiling." << ::std::endl; + + os << std::endl + << "\033[1m--no-external-modules\033[0m Don't load external modules during project bootstrap." << ::std::endl + << " Note that this option can only be used with" << ::std::endl + << " meta-operations that do not load the project's" << ::std::endl + << " \033[1mbuildfiles\033[0m, such as \033[1minfo\033[0m." << ::std::endl; + + os << std::endl + << "\033[1m--structured-result\033[0m Write the result of execution in a structured form. In" << ::std::endl + << " this mode, instead of printing to \033[1mSTDERR\033[0m diagnostics" << ::std::endl + << " messages about the outcome of executing actions on" << ::std::endl + << " targets, the driver writes to \033[1mSTDOUT\033[0m a structured result" << ::std::endl + << " description one line per the buildspec action/target" << ::std::endl + << " pair. Each line has the following format:" << ::std::endl + << ::std::endl + << " \033[4mstate\033[0m \033[4mmeta-operation\033[0m \033[4moperation\033[0m \033[4mtarget\033[0m\033[0m" << ::std::endl + << ::std::endl + << " Where \033[4mstate\033[0m can be one of \033[1munchanged\033[0m, \033[1mchanged\033[0m, or \033[1mfailed\033[0m." << ::std::endl + << " If the action is a pre or post operation, then the outer" << ::std::endl + << " operation is specified in parenthesis. For example:" << ::std::endl + << ::std::endl + << " unchanged perform update(test) /tmp/dir{hello/}" << ::std::endl + << " changed perform test /tmp/dir{hello/}" << ::std::endl + << ::std::endl + << " Note that only the \033[1mperform\033[0m meta-operation supports the" << ::std::endl + << " structured result output." << ::std::endl; + + os << std::endl + << "\033[1m--mtime-check\033[0m Perform file modification time sanity checks. These" << ::std::endl + << " checks can be helpful in diagnosing spurious rebuilds and" << ::std::endl + << " are enabled by default on Windows (which is known not to" << ::std::endl + << " guarantee monotonically increasing mtimes) and for the" << ::std::endl + << " staged version of the build system on other platforms." << ::std::endl + << " Use \033[1m--no-mtime-check\033[0m to disable." << ::std::endl; + + os << std::endl + << "\033[1m--no-mtime-check\033[0m Don't perform file modification time sanity checks. See" << ::std::endl + << " \033[1m--mtime-check\033[0m for details." << ::std::endl; + + os << std::endl + << "\033[1m--no-column\033[0m Don't print column numbers in diagnostics." << ::std::endl; + + os << std::endl + << "\033[1m--no-line\033[0m Don't print line and column numbers in diagnostics." << ::std::endl; + + os << std::endl + << "\033[1m--buildfile\033[0m \033[4mpath\033[0m The alternative file to read build information from. The" << ::std::endl + << " default is \033[1mbuildfile\033[0m or \033[1mbuild2file\033[0m, depending on the" << ::std::endl + << " project's build file/directory naming scheme. If \033[4mpath\033[0m is" << ::std::endl + << " '\033[1m-\033[0m', then read from \033[1mSTDIN\033[0m. Note that this option only" << ::std::endl + << " affects the files read as part of the buildspec" << ::std::endl + << " processing. Specifically, it has no effect on the \033[1msource\033[0m" << ::std::endl + << " and \033[1minclude\033[0m directives. As a result, this option is" << ::std::endl + << " primarily intended for testing rather than changing the" << ::std::endl + << " build file names in real projects." << ::std::endl; + + os << std::endl + << "\033[1m--config-guess\033[0m \033[4mpath\033[0m The path to the \033[1mconfig.guess(1)\033[0m script that should be" << ::std::endl + << " used to guess the host machine triplet. If this option is" << ::std::endl + << " not specified, then \033[1mb\033[0m will fall back on to using the" << ::std::endl + << " target it was built for as host." << ::std::endl; + + os << std::endl + << "\033[1m--config-sub\033[0m \033[4mpath\033[0m The path to the \033[1mconfig.sub(1)\033[0m script that should be used" << ::std::endl + << " to canonicalize machine triplets. If this option is not" << ::std::endl + << " specified, then \033[1mb\033[0m will use its built-in canonicalization" << ::std::endl + << " support which should be sufficient for commonly-used" << ::std::endl + << " platforms." << ::std::endl; + + os << std::endl + << "\033[1m--pager\033[0m \033[4mpath\033[0m The pager program to be used to show long text. Commonly" << ::std::endl + << " used pager programs are \033[1mless\033[0m and \033[1mmore\033[0m. You can also" << ::std::endl + << " specify additional options that should be passed to the" << ::std::endl + << " pager program with \033[1m--pager-option\033[0m. If an empty string is" << ::std::endl + << " specified as the pager program, then no pager will be" << ::std::endl + << " used. If the pager program is not explicitly specified," << ::std::endl + << " then \033[1mb\033[0m will try to use \033[1mless\033[0m. If it is not available, then" << ::std::endl + << " no pager will be used." << ::std::endl; + + os << std::endl + << "\033[1m--pager-option\033[0m \033[4mopt\033[0m Additional option to be passed to the pager program. See" << ::std::endl + << " \033[1m--pager\033[0m for more information on the pager program. Repeat" << ::std::endl + << " this option to specify multiple pager options." << ::std::endl; + + os << std::endl + << "\033[1m--options-file\033[0m \033[4mfile\033[0m Read additional options from \033[4mfile\033[0m. Each option should" << ::std::endl + << " appear on a separate line optionally followed by space or" << ::std::endl + << " equal sign (\033[1m=\033[0m) and an option value. Empty lines and lines" << ::std::endl + << " starting with \033[1m#\033[0m are ignored. Option values can be" << ::std::endl + << " enclosed in double (\033[1m\"\033[0m) or single (\033[1m'\033[0m) quotes to preserve" << ::std::endl + << " leading and trailing whitespaces as well as to specify" << ::std::endl + << " empty values. If the value itself contains trailing or" << ::std::endl + << " leading quotes, enclose it with an extra pair of quotes," << ::std::endl + << " for example \033[1m'\"x\"'\033[0m. Non-leading and non-trailing quotes" << ::std::endl + << " are interpreted as being part of the option value." << ::std::endl + << ::std::endl + << " The semantics of providing options in a file is" << ::std::endl + << " equivalent to providing the same set of options in the" << ::std::endl + << " same order on the command line at the point where the" << ::std::endl + << " \033[1m--options-file\033[0m option is specified except that the shell" << ::std::endl + << " escaping and quoting is not required. Repeat this option" << ::std::endl + << " to specify more than one options file." << ::std::endl; + + os << std::endl + << "\033[1m--default-options\033[0m \033[4mdir\033[0m The directory to load additional default options files" << ::std::endl + << " from." << ::std::endl; + + os << std::endl + << "\033[1m--no-default-options\033[0m Don't load default options files." << ::std::endl; + + os << std::endl + << "\033[1m--help\033[0m Print usage information and exit." << ::std::endl; + + os << std::endl + << "\033[1m--version\033[0m Print version and exit." << ::std::endl; + + p = ::build2::build::cli::usage_para::option; + + return p; + } + + typedef + std::map<std::string, void (*) (options&, ::build2::build::cli::scanner&)> + _cli_options_map; + + static _cli_options_map _cli_options_map_; + + struct _cli_options_map_init + { + _cli_options_map_init () + { + _cli_options_map_["--build2-metadata"] = + &::build2::build::cli::thunk< options, uint64_t, &options::build2_metadata_, + &options::build2_metadata_specified_ >; + _cli_options_map_["-v"] = + &::build2::build::cli::thunk< options, bool, &options::v_ >; + _cli_options_map_["-V"] = + &::build2::build::cli::thunk< options, bool, &options::V_ >; + _cli_options_map_["--quiet"] = + &::build2::build::cli::thunk< options, bool, &options::quiet_ >; + _cli_options_map_["-q"] = + &::build2::build::cli::thunk< options, bool, &options::quiet_ >; + _cli_options_map_["--silent"] = + &::build2::build::cli::thunk< options, bool, &options::silent_ >; + _cli_options_map_["--verbose"] = + &::build2::build::cli::thunk< options, uint16_t, &options::verbose_, + &options::verbose_specified_ >; + _cli_options_map_["--stat"] = + &::build2::build::cli::thunk< options, bool, &options::stat_ >; + _cli_options_map_["--dump"] = + &::build2::build::cli::thunk< options, std::set<string>, &options::dump_, + &options::dump_specified_ >; + _cli_options_map_["--progress"] = + &::build2::build::cli::thunk< options, bool, &options::progress_ >; + _cli_options_map_["--no-progress"] = + &::build2::build::cli::thunk< options, bool, &options::no_progress_ >; + _cli_options_map_["--jobs"] = + &::build2::build::cli::thunk< options, size_t, &options::jobs_, + &options::jobs_specified_ >; + _cli_options_map_["-j"] = + &::build2::build::cli::thunk< options, size_t, &options::jobs_, + &options::jobs_specified_ >; + _cli_options_map_["--max-jobs"] = + &::build2::build::cli::thunk< options, size_t, &options::max_jobs_, + &options::max_jobs_specified_ >; + _cli_options_map_["-J"] = + &::build2::build::cli::thunk< options, size_t, &options::max_jobs_, + &options::max_jobs_specified_ >; + _cli_options_map_["--queue-depth"] = + &::build2::build::cli::thunk< options, size_t, &options::queue_depth_, + &options::queue_depth_specified_ >; + _cli_options_map_["-Q"] = + &::build2::build::cli::thunk< options, size_t, &options::queue_depth_, + &options::queue_depth_specified_ >; + _cli_options_map_["--file-cache"] = + &::build2::build::cli::thunk< options, string, &options::file_cache_, + &options::file_cache_specified_ >; + _cli_options_map_["--max-stack"] = + &::build2::build::cli::thunk< options, size_t, &options::max_stack_, + &options::max_stack_specified_ >; + _cli_options_map_["--serial-stop"] = + &::build2::build::cli::thunk< options, bool, &options::serial_stop_ >; + _cli_options_map_["-s"] = + &::build2::build::cli::thunk< options, bool, &options::serial_stop_ >; + _cli_options_map_["--dry-run"] = + &::build2::build::cli::thunk< options, bool, &options::dry_run_ >; + _cli_options_map_["-n"] = + &::build2::build::cli::thunk< options, bool, &options::dry_run_ >; + _cli_options_map_["--match-only"] = + &::build2::build::cli::thunk< options, bool, &options::match_only_ >; + _cli_options_map_["--no-external-modules"] = + &::build2::build::cli::thunk< options, bool, &options::no_external_modules_ >; + _cli_options_map_["--structured-result"] = + &::build2::build::cli::thunk< options, bool, &options::structured_result_ >; + _cli_options_map_["--mtime-check"] = + &::build2::build::cli::thunk< options, bool, &options::mtime_check_ >; + _cli_options_map_["--no-mtime-check"] = + &::build2::build::cli::thunk< options, bool, &options::no_mtime_check_ >; + _cli_options_map_["--no-column"] = + &::build2::build::cli::thunk< options, bool, &options::no_column_ >; + _cli_options_map_["--no-line"] = + &::build2::build::cli::thunk< options, bool, &options::no_line_ >; + _cli_options_map_["--buildfile"] = + &::build2::build::cli::thunk< options, path, &options::buildfile_, + &options::buildfile_specified_ >; + _cli_options_map_["--config-guess"] = + &::build2::build::cli::thunk< options, path, &options::config_guess_, + &options::config_guess_specified_ >; + _cli_options_map_["--config-sub"] = + &::build2::build::cli::thunk< options, path, &options::config_sub_, + &options::config_sub_specified_ >; + _cli_options_map_["--pager"] = + &::build2::build::cli::thunk< options, string, &options::pager_, + &options::pager_specified_ >; + _cli_options_map_["--pager-option"] = + &::build2::build::cli::thunk< options, strings, &options::pager_option_, + &options::pager_option_specified_ >; + _cli_options_map_["--options-file"] = + &::build2::build::cli::thunk< options, string, &options::options_file_, + &options::options_file_specified_ >; + _cli_options_map_["--default-options"] = + &::build2::build::cli::thunk< options, dir_path, &options::default_options_, + &options::default_options_specified_ >; + _cli_options_map_["--no-default-options"] = + &::build2::build::cli::thunk< options, bool, &options::no_default_options_ >; + _cli_options_map_["--help"] = + &::build2::build::cli::thunk< options, bool, &options::help_ >; + _cli_options_map_["--version"] = + &::build2::build::cli::thunk< options, bool, &options::version_ >; + } + }; + + static _cli_options_map_init _cli_options_map_init_; + + bool options:: + _parse (const char* o, ::build2::build::cli::scanner& s) + { + _cli_options_map::const_iterator i (_cli_options_map_.find (o)); + + if (i != _cli_options_map_.end ()) + { + (*(i->second)) (*this, s); + return true; + } + + return false; + } + + bool options:: + _parse (::build2::build::cli::scanner& s, + ::build2::build::cli::unknown_mode opt_mode, + ::build2::build::cli::unknown_mode arg_mode) + { + // Can't skip combined flags (--no-combined-flags). + // + assert (opt_mode != ::build2::build::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; + } + + 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<char*> (co.c_str ()), + const_cast<char*> (v) + }; + + ::build2::build::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::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::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::cli::unknown_mode::skip: + { + s.skip (); + r = true; + continue; + } + case ::build2::build::cli::unknown_mode::stop: + { + break; + } + case ::build2::build::cli::unknown_mode::fail: + { + throw ::build2::build::cli::unknown_option (o); + } + } + + break; + } + } + + switch (arg_mode) + { + case ::build2::build::cli::unknown_mode::skip: + { + s.skip (); + r = true; + continue; + } + case ::build2::build::cli::unknown_mode::stop: + { + break; + } + case ::build2::build::cli::unknown_mode::fail: + { + throw ::build2::build::cli::unknown_argument (o); + } + } + + break; + } + + return r; + } +} + +namespace build2 +{ + ::build2::build::cli::usage_para + print_b_usage (::std::ostream& os, ::build2::build::cli::usage_para p) + { + CLI_POTENTIALLY_UNUSED (os); + + if (p != ::build2::build::cli::usage_para::none) + os << ::std::endl; + + os << "\033[1mSYNOPSIS\033[0m" << ::std::endl + << ::std::endl + << "\033[1mb --help\033[0m" << ::std::endl + << "\033[1mb --version\033[0m" << ::std::endl + << "\033[1mb\033[0m [\033[4moptions\033[0m] [\033[4mvariables\033[0m] [\033[4mbuildspec\033[0m]\033[0m" << ::std::endl + << ::std::endl + << "\033[4mbuildspec\033[0m = \033[4mmeta-operation\033[0m\033[1m(\033[0m\033[4moperation\033[0m\033[1m(\033[0m\033[4mtarget\033[0m...[\033[1m,\033[0m\033[4mparameters\033[0m]\033[1m)\033[0m...\033[1m)\033[0m...\033[0m" << ::std::endl + << ::std::endl + << "\033[1mDESCRIPTION\033[0m" << ::std::endl + << ::std::endl + << "The \033[1mbuild2\033[0m build system driver executes a set of meta-operations on operations" << ::std::endl + << "on targets according to the build specification, or \033[4mbuildspec\033[0m for short. This" << ::std::endl + << "process can be controlled by specifying driver \033[4moptions\033[0m and build system" << ::std::endl + << "\033[4mvariables\033[0m." << ::std::endl + << ::std::endl + << "Note that \033[4moptions\033[0m, \033[4mvariables\033[0m, and \033[4mbuildspec\033[0m fragments can be specified in any" << ::std::endl + << "order. To avoid treating an argument that starts with \033[1m'-'\033[0m as an option, add the" << ::std::endl + << "\033[1m'--'\033[0m separator. To avoid treating an argument that contains \033[1m'='\033[0m as a variable," << ::std::endl + << "add the second \033[1m'--'\033[0m separator." << ::std::endl; + + p = ::build2::options::print_usage (os, ::build2::build::cli::usage_para::text); + + if (p != ::build2::build::cli::usage_para::none) + os << ::std::endl; + + os << "\033[1mDEFAULT OPTIONS FILES\033[0m" << ::std::endl + << ::std::endl + << "Instead of having a separate config file format for tool configuration, the" << ::std::endl + << "\033[1mbuild2\033[0m toolchain uses \033[4mdefault options files\033[0m which contain the same options as" << ::std::endl + << "what can be specified on the command line. The default options files are like" << ::std::endl + << "options files that one can specify with \033[1m--options-file\033[0m except that they are" << ::std::endl + << "loaded by default." << ::std::endl + << ::std::endl + << "The default options files for the build system driver are called \033[1mb.options\033[0m and" << ::std::endl + << "are searched for in the \033[1m.build2/\033[0m subdirectory of the home directory and in the" << ::std::endl + << "system directory (for example, \033[1m/etc/build2/\033[0m) if configured. Note that besides" << ::std::endl + << "options these files can also contain global variable overrides." << ::std::endl + << ::std::endl + << "Once the search is complete, the files are loaded in the reverse order, that" << ::std::endl + << "is, beginning from the system directory (if any), followed by the home" << ::std::endl + << "directory, and finishing off with the options specified on the command line. In" << ::std::endl + << "other words, the files are loaded from the more generic to the more specific" << ::std::endl + << "with the command line options having the ability to override any values" << ::std::endl + << "specified in the default options files." << ::std::endl + << ::std::endl + << "If a default options file contains \033[1m--no-default-options\033[0m, then the search is" << ::std::endl + << "stopped at the directory containing this file and no outer files are loaded. If" << ::std::endl + << "this option is specified on the command line, then none of the default options" << ::std::endl + << "files are searched for or loaded." << ::std::endl + << ::std::endl + << "An additional directory containing default options files can be specified with" << ::std::endl + << "\033[1m--default-options\033[0m. Its configuration files are loaded after the home directory." << ::std::endl + << ::std::endl + << "The order in which default options files are loaded is traced at the verbosity" << ::std::endl + << "level 3 (\033[1m-V\033[0m option) or higher." << ::std::endl + << ::std::endl + << "\033[1mEXIT STATUS\033[0m" << ::std::endl + << ::std::endl + << "Non-zero exit status is returned in case of an error." << ::std::endl; + + os << std::endl + << "\033[1mENVIRONMENT\033[0m" << ::std::endl + << ::std::endl + << "The \033[1mHOME\033[0m environment variable is used to determine the user's home directory." << ::std::endl + << "If it is not set, then \033[1mgetpwuid(3)\033[0m is used instead. This value is used to" << ::std::endl + << "shorten paths printed in diagnostics by replacing the home directory with \033[1m~/\033[0m." << ::std::endl + << "It is also made available to \033[1mbuildfile\033[0m's as the \033[1mbuild.home\033[0m variable." << ::std::endl + << ::std::endl + << "The \033[1mBUILD2_VAR_OVR\033[0m environment variable is used to propagate global variable" << ::std::endl + << "overrides to nested build system driver invocations. Its value is a list of" << ::std::endl + << "global variable assignments separated with newlines." << ::std::endl + << ::std::endl + << "The \033[1mBUILD2_DEF_OPT\033[0m environment variable is used to suppress loading of default" << ::std::endl + << "options files in nested build system driver invocations. Its values are \033[1mfalse\033[0m" << ::std::endl + << "or \033[1m0\033[0m to suppress and \033[1mtrue\033[0m or \033[1m1\033[0m to load." << ::std::endl; + + p = ::build2::build::cli::usage_para::text; + + return p; + } +} + +// Begin epilogue. +// +// +// End epilogue. + diff --git a/libbuild2/b-options.hxx b/libbuild2/b-options.hxx new file mode 100644 index 0000000..dda9f08 --- /dev/null +++ b/libbuild2/b-options.hxx @@ -0,0 +1,726 @@ +// -*- C++ -*- +// +// This file was generated by CLI, a command line interface +// compiler for C++. +// + +#ifndef LIBBUILD2_B_OPTIONS_HXX +#define LIBBUILD2_B_OPTIONS_HXX + +// Begin prologue. +// +#include <libbuild2/export.hxx> +// +// End prologue. + +#include <list> +#include <deque> +#include <iosfwd> +#include <string> +#include <cstddef> +#include <exception> + +#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 cli + { + class usage_para + { + public: + enum value + { + none, + text, + option + }; + + usage_para (value); + + operator value () const + { + return v_; + } + + private: + value v_; + }; + + class unknown_mode + { + public: + enum value + { + skip, + stop, + fail + }; + + unknown_mode (value); + + operator value () const + { + return v_; + } + + private: + value v_; + }; + + // Exceptions. + // + + class LIBBUILD2_SYMEXPORT exception: public std::exception + { + public: + virtual void + print (::std::ostream&) const = 0; + }; + + ::std::ostream& + operator<< (::std::ostream&, const exception&); + + class LIBBUILD2_SYMEXPORT 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 LIBBUILD2_SYMEXPORT 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 LIBBUILD2_SYMEXPORT 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 LIBBUILD2_SYMEXPORT 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 LIBBUILD2_SYMEXPORT eos_reached: public exception + { + public: + virtual void + print (::std::ostream&) const; + + virtual const char* + what () const throw (); + }; + + class LIBBUILD2_SYMEXPORT file_io_failure: public exception + { + public: + virtual + ~file_io_failure () throw (); + + file_io_failure (const std::string& file); + + const std::string& + file () const; + + virtual void + print (::std::ostream&) const; + + virtual const char* + what () const throw (); + + private: + std::string file_; + }; + + class LIBBUILD2_SYMEXPORT unmatched_quote: public exception + { + public: + virtual + ~unmatched_quote () throw (); + + unmatched_quote (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_; + }; + + // 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 LIBBUILD2_SYMEXPORT 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 LIBBUILD2_SYMEXPORT 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 LIBBUILD2_SYMEXPORT argv_file_scanner: public argv_scanner + { + public: + argv_file_scanner (int& argc, + char** argv, + const std::string& option, + bool erase = false, + std::size_t start_position = 0); + + argv_file_scanner (int start, + int& argc, + char** argv, + const std::string& option, + bool erase = false, + std::size_t start_position = 0); + + argv_file_scanner (const std::string& file, + const std::string& option, + std::size_t start_position = 0); + + struct option_info + { + // If search_func is not NULL, it is called, with the arg + // value as the second argument, to locate the options file. + // If it returns an empty string, then the file is ignored. + // + const char* option; + std::string (*search_func) (const char*, void* arg); + void* arg; + }; + + argv_file_scanner (int& argc, + char** argv, + const option_info* options, + std::size_t options_count, + bool erase = false, + std::size_t start_position = 0); + + argv_file_scanner (int start, + int& argc, + char** argv, + const option_info* options, + std::size_t options_count, + bool erase = false, + std::size_t start_position = 0); + + argv_file_scanner (const std::string& file, + const option_info* options = 0, + std::size_t options_count = 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 (); + + // Return the file path if the peeked at argument came from a file and + // the empty string otherwise. The reference is guaranteed to be valid + // till the end of the scanner lifetime. + // + const std::string& + peek_file (); + + // Return the 1-based line number if the peeked at argument came from + // a file and zero otherwise. + // + std::size_t + peek_line (); + + private: + const option_info* + find (const char*) const; + + void + load (const std::string& file); + + typedef argv_scanner base; + + const std::string option_; + option_info option_info_; + const option_info* options_; + std::size_t options_count_; + + struct arg + { + std::string value; + const std::string* file; + std::size_t line; + }; + + std::deque<arg> args_; + std::list<std::string> files_; + + // Circular buffer of two arguments. + // + std::string hold_[2]; + std::size_t i_; + + bool skip_; + + static int zero_argc_; + static std::string empty_string_; + }; + + template <typename X> + struct parser; + } + } +} + +#include <set> + +#include <libbuild2/types.hxx> + +namespace build2 +{ + class LIBBUILD2_SYMEXPORT options + { + public: + options (); + + // Return true if anything has been parsed. + // + bool + parse (int& argc, + char** argv, + bool erase = false, + ::build2::build::cli::unknown_mode option = ::build2::build::cli::unknown_mode::fail, + ::build2::build::cli::unknown_mode argument = ::build2::build::cli::unknown_mode::stop); + + bool + parse (int start, + int& argc, + char** argv, + bool erase = false, + ::build2::build::cli::unknown_mode option = ::build2::build::cli::unknown_mode::fail, + ::build2::build::cli::unknown_mode argument = ::build2::build::cli::unknown_mode::stop); + + bool + parse (int& argc, + char** argv, + int& end, + bool erase = false, + ::build2::build::cli::unknown_mode option = ::build2::build::cli::unknown_mode::fail, + ::build2::build::cli::unknown_mode argument = ::build2::build::cli::unknown_mode::stop); + + bool + parse (int start, + int& argc, + char** argv, + int& end, + bool erase = false, + ::build2::build::cli::unknown_mode option = ::build2::build::cli::unknown_mode::fail, + ::build2::build::cli::unknown_mode argument = ::build2::build::cli::unknown_mode::stop); + + bool + parse (::build2::build::cli::scanner&, + ::build2::build::cli::unknown_mode option = ::build2::build::cli::unknown_mode::fail, + ::build2::build::cli::unknown_mode argument = ::build2::build::cli::unknown_mode::stop); + + // Merge options from the specified instance appending/overriding + // them as if they appeared after options in this instance. + // + void + merge (const options&); + + // Option accessors. + // + const uint64_t& + build2_metadata () const; + + bool + build2_metadata_specified () const; + + const bool& + v () const; + + const bool& + V () const; + + const bool& + quiet () const; + + const bool& + silent () const; + + const uint16_t& + verbose () const; + + bool + verbose_specified () const; + + const bool& + stat () const; + + const std::set<string>& + dump () const; + + bool + dump_specified () const; + + const bool& + progress () const; + + const bool& + no_progress () const; + + const size_t& + jobs () const; + + bool + jobs_specified () const; + + const size_t& + max_jobs () const; + + bool + max_jobs_specified () const; + + const size_t& + queue_depth () const; + + bool + queue_depth_specified () const; + + const string& + file_cache () const; + + bool + file_cache_specified () const; + + const size_t& + max_stack () const; + + bool + max_stack_specified () const; + + const bool& + serial_stop () const; + + const bool& + dry_run () const; + + const bool& + match_only () const; + + const bool& + no_external_modules () const; + + const bool& + structured_result () const; + + const bool& + mtime_check () const; + + const bool& + no_mtime_check () const; + + const bool& + no_column () const; + + const bool& + no_line () const; + + const path& + buildfile () const; + + bool + buildfile_specified () const; + + const path& + config_guess () const; + + bool + config_guess_specified () const; + + const path& + config_sub () const; + + bool + config_sub_specified () const; + + const string& + pager () const; + + bool + pager_specified () const; + + const strings& + pager_option () const; + + bool + pager_option_specified () const; + + const string& + options_file () const; + + bool + options_file_specified () const; + + const dir_path& + default_options () const; + + bool + default_options_specified () const; + + const bool& + no_default_options () const; + + const bool& + help () const; + + const bool& + version () const; + + // Print usage information. + // + static ::build2::build::cli::usage_para + print_usage (::std::ostream&, + ::build2::build::cli::usage_para = ::build2::build::cli::usage_para::none); + + // Implementation details. + // + protected: + bool + _parse (const char*, ::build2::build::cli::scanner&); + + private: + bool + _parse (::build2::build::cli::scanner&, + ::build2::build::cli::unknown_mode option, + ::build2::build::cli::unknown_mode argument); + + public: + uint64_t build2_metadata_; + bool build2_metadata_specified_; + bool v_; + bool V_; + bool quiet_; + bool silent_; + uint16_t verbose_; + bool verbose_specified_; + bool stat_; + std::set<string> dump_; + bool dump_specified_; + bool progress_; + bool no_progress_; + size_t jobs_; + bool jobs_specified_; + size_t max_jobs_; + bool max_jobs_specified_; + size_t queue_depth_; + bool queue_depth_specified_; + string file_cache_; + bool file_cache_specified_; + size_t max_stack_; + bool max_stack_specified_; + bool serial_stop_; + bool dry_run_; + bool match_only_; + bool no_external_modules_; + bool structured_result_; + bool mtime_check_; + bool no_mtime_check_; + bool no_column_; + bool no_line_; + path buildfile_; + bool buildfile_specified_; + path config_guess_; + bool config_guess_specified_; + path config_sub_; + bool config_sub_specified_; + string pager_; + bool pager_specified_; + strings pager_option_; + bool pager_option_specified_; + string options_file_; + bool options_file_specified_; + dir_path default_options_; + bool default_options_specified_; + bool no_default_options_; + bool help_; + bool version_; + }; +} + +// Print page usage information. +// +namespace build2 +{ + LIBBUILD2_SYMEXPORT ::build2::build::cli::usage_para + print_b_usage (::std::ostream&, + ::build2::build::cli::usage_para = ::build2::build::cli::usage_para::none); +} + +#include <libbuild2/b-options.ixx> + +// Begin epilogue. +// +// +// End epilogue. + +#endif // LIBBUILD2_B_OPTIONS_HXX diff --git a/libbuild2/b-options.ixx b/libbuild2/b-options.ixx new file mode 100644 index 0000000..62c8299 --- /dev/null +++ b/libbuild2/b-options.ixx @@ -0,0 +1,585 @@ +// -*- C++ -*- +// +// This file was generated by CLI, a command line interface +// compiler for C++. +// + +// Begin prologue. +// +// +// End prologue. + +#include <cassert> + +namespace build2 +{ + namespace build + { + namespace cli + { + // usage_para + // + inline usage_para:: + usage_para (value v) + : v_ (v) + { + } + + // 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_; + } + + // file_io_failure + // + inline file_io_failure:: + file_io_failure (const std::string& file) + : file_ (file) + { + } + + inline const std::string& file_io_failure:: + file () const + { + return file_; + } + + // unmatched_quote + // + inline unmatched_quote:: + unmatched_quote (const std::string& argument) + : argument_ (argument) + { + } + + inline const std::string& unmatched_quote:: + argument () const + { + return argument_; + } + + // 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<std::size_t> (start)), + i_ (start), + argc_ (argc), + argv_ (argv), + erase_ (erase) + { + } + + inline int argv_scanner:: + end () const + { + return i_; + } + + // argv_file_scanner + // + inline argv_file_scanner:: + argv_file_scanner (int& argc, + char** argv, + const std::string& option, + bool erase, + std::size_t sp) + : argv_scanner (argc, argv, erase, sp), + option_ (option), + options_ (&option_info_), + options_count_ (1), + i_ (1), + skip_ (false) + { + option_info_.option = option_.c_str (); + option_info_.search_func = 0; + } + + inline argv_file_scanner:: + argv_file_scanner (int start, + int& argc, + char** argv, + const std::string& option, + bool erase, + std::size_t sp) + : argv_scanner (start, argc, argv, erase, sp), + option_ (option), + options_ (&option_info_), + options_count_ (1), + i_ (1), + skip_ (false) + { + option_info_.option = option_.c_str (); + option_info_.search_func = 0; + } + + inline argv_file_scanner:: + argv_file_scanner (const std::string& file, + const std::string& option, + std::size_t sp) + : argv_scanner (0, zero_argc_, 0, sp), + option_ (option), + options_ (&option_info_), + options_count_ (1), + i_ (1), + skip_ (false) + { + option_info_.option = option_.c_str (); + option_info_.search_func = 0; + + load (file); + } + + inline argv_file_scanner:: + argv_file_scanner (int& argc, + char** argv, + const option_info* options, + std::size_t options_count, + bool erase, + std::size_t sp) + : argv_scanner (argc, argv, erase, sp), + options_ (options), + options_count_ (options_count), + i_ (1), + skip_ (false) + { + } + + inline argv_file_scanner:: + argv_file_scanner (int start, + int& argc, + char** argv, + const option_info* options, + std::size_t options_count, + bool erase, + std::size_t sp) + : argv_scanner (start, argc, argv, erase, sp), + options_ (options), + options_count_ (options_count), + i_ (1), + skip_ (false) + { + } + + inline argv_file_scanner:: + argv_file_scanner (const std::string& file, + const option_info* options, + std::size_t options_count, + std::size_t sp) + : argv_scanner (0, zero_argc_, 0, sp), + options_ (options), + options_count_ (options_count), + i_ (1), + skip_ (false) + { + load (file); + } + } + } +} + +namespace build2 +{ + // options + // + + inline const uint64_t& options:: + build2_metadata () const + { + return this->build2_metadata_; + } + + inline bool options:: + build2_metadata_specified () const + { + return this->build2_metadata_specified_; + } + + inline const bool& options:: + v () const + { + return this->v_; + } + + inline const bool& options:: + V () const + { + return this->V_; + } + + inline const bool& options:: + quiet () const + { + return this->quiet_; + } + + inline const bool& options:: + silent () const + { + return this->silent_; + } + + inline const uint16_t& options:: + verbose () const + { + return this->verbose_; + } + + inline bool options:: + verbose_specified () const + { + return this->verbose_specified_; + } + + inline const bool& options:: + stat () const + { + return this->stat_; + } + + inline const std::set<string>& options:: + dump () const + { + return this->dump_; + } + + inline bool options:: + dump_specified () const + { + return this->dump_specified_; + } + + inline const bool& options:: + progress () const + { + return this->progress_; + } + + inline const bool& options:: + no_progress () const + { + return this->no_progress_; + } + + inline const size_t& options:: + jobs () const + { + return this->jobs_; + } + + inline bool options:: + jobs_specified () const + { + return this->jobs_specified_; + } + + inline const size_t& options:: + max_jobs () const + { + return this->max_jobs_; + } + + inline bool options:: + max_jobs_specified () const + { + return this->max_jobs_specified_; + } + + inline const size_t& options:: + queue_depth () const + { + return this->queue_depth_; + } + + inline bool options:: + queue_depth_specified () const + { + return this->queue_depth_specified_; + } + + inline const string& options:: + file_cache () const + { + return this->file_cache_; + } + + inline bool options:: + file_cache_specified () const + { + return this->file_cache_specified_; + } + + inline const size_t& options:: + max_stack () const + { + return this->max_stack_; + } + + inline bool options:: + max_stack_specified () const + { + return this->max_stack_specified_; + } + + inline const bool& options:: + serial_stop () const + { + return this->serial_stop_; + } + + inline const bool& options:: + dry_run () const + { + return this->dry_run_; + } + + inline const bool& options:: + match_only () const + { + return this->match_only_; + } + + inline const bool& options:: + no_external_modules () const + { + return this->no_external_modules_; + } + + inline const bool& options:: + structured_result () const + { + return this->structured_result_; + } + + inline const bool& options:: + mtime_check () const + { + return this->mtime_check_; + } + + inline const bool& options:: + no_mtime_check () const + { + return this->no_mtime_check_; + } + + inline const bool& options:: + no_column () const + { + return this->no_column_; + } + + inline const bool& options:: + no_line () const + { + return this->no_line_; + } + + inline const path& options:: + buildfile () const + { + return this->buildfile_; + } + + inline bool options:: + buildfile_specified () const + { + return this->buildfile_specified_; + } + + inline const path& options:: + config_guess () const + { + return this->config_guess_; + } + + inline bool options:: + config_guess_specified () const + { + return this->config_guess_specified_; + } + + inline const path& options:: + config_sub () const + { + return this->config_sub_; + } + + inline bool options:: + config_sub_specified () const + { + return this->config_sub_specified_; + } + + inline const string& options:: + pager () const + { + return this->pager_; + } + + inline bool options:: + pager_specified () const + { + return this->pager_specified_; + } + + inline const strings& options:: + pager_option () const + { + return this->pager_option_; + } + + inline bool options:: + pager_option_specified () const + { + return this->pager_option_specified_; + } + + inline const string& options:: + options_file () const + { + return this->options_file_; + } + + inline bool options:: + options_file_specified () const + { + return this->options_file_specified_; + } + + inline const dir_path& options:: + default_options () const + { + return this->default_options_; + } + + inline bool options:: + default_options_specified () const + { + return this->default_options_specified_; + } + + inline const bool& options:: + no_default_options () const + { + return this->no_default_options_; + } + + inline const bool& options:: + help () const + { + return this->help_; + } + + inline const bool& options:: + version () const + { + return this->version_; + } +} + +// Begin epilogue. +// +// +// End epilogue. diff --git a/libbuild2/b.cli b/libbuild2/b.cli new file mode 100644 index 0000000..112db2b --- /dev/null +++ b/libbuild2/b.cli @@ -0,0 +1,751 @@ +// file : build2/b.cli +// license : MIT; see accompanying LICENSE file + +include <set>; +include <libbuild2/types.hxx>; + +"\section=1" +"\name=b" +"\summary=build system driver" + +namespace build2 +{ + { + "<options> + <variables> + <buildspec> <meta-operation> <operation> <target> <parameters>", + + "\h|SYNOPSIS| + + \c{\b{b --help}\n + \b{b --version}\n + \b{b} [<options>] [<variables>] [<buildspec>]} + + \c{<buildspec> = <meta-operation>\b{(}<operation>\b{(}<target>...[\b{,}<parameters>]\b{)}...\b{)}...} + + \h|DESCRIPTION| + + The \cb{build2} build system driver executes a set of meta-operations on + operations on targets according to the build specification, or + \i{buildspec} for short. This process can be controlled by specifying + driver <options> and build system <variables>. + + Note that <options>, <variables>, and <buildspec> fragments can be + specified in any order. To avoid treating an argument that starts with + \cb{'-'} as an option, add the \cb{'--'} separator. To avoid treating an + argument that contains \cb{'='} as a variable, add the second \cb{'--'} + separator." + } + + // For usage it's nice to see the list of options on the first page. So + // let's not put this "extended" description into usage. + // + { + "<meta-operation> <operation> <target> <parameters> <src-base>", + "", + + "All components in the buildspec can be omitted. If <meta-operation> is + omitted, then it defaults to \cb{perform}. If <operation> is omitted, + then it defaults to the default operation for this meta-operation. For + \cb{perform} it is \cb{update}. Finally, if <target> is omitted, then it + defaults to the current working directory. A meta-operation on operation + is called an \i{action}. Some operations and meta-operations may take + additional <parameters>. For example: + + \ + $ b # perform(update(./)) + $ b foo/ # perform(update(foo/)) + $ b foo/ bar/ # perform(update(foo/ bar/)) + $ b update # perform(update(./)) + $ b 'clean(../)' # perform(clean(../)) + $ b perform # perform(update(./)) + $ b configure # configure(?(./)) + $ b 'configure(../)' # configure(?(../)) + $ b clean update # perform(clean(./) update(./)) + $ b configure update # configure(?(./)) perform(update(./)) + $ b 'create(conf/, cxx)' # create(?(conf/), cxx) + \ + + Notice the question mark used to show the (imaginary) default operation + for the \cb{configure} meta-operation. For \cb{configure} the default + operation is \"all operations\". That is, it will configure all the + operations for the specified target. + + You can also \"generate\" multiple operations for the same set of targets. + Compare: + + \ + $ b 'clean(foo/ bar/)' 'update(foo/ bar/)' + $ b '{clean update}(foo/ bar/)' + \ + + Some more useful buildspec examples: + + \ + $ b '{clean update}(...)' # rebuild + $ b '{clean update clean}(...)' # make sure builds + $ b '{clean test clean}(...)' # make sure passes tests + $ b '{clean disfigure}(...)' # similar to distclean + \ + + In POSIX shells parenthesis are special characters and must be quoted + when used in a buildspec. Besides being an inconvenience in itself, + quoting also inhibits path auto-completion. To help with this situation a + shortcut syntax is available for executing a single operation or + meta-operation, for example: + + \ + $ b clean: foo/ bar/ # clean(foo/ bar/) + $ b configure: src/@out/ # configure(src/@out/) + $ b create: conf/, cxx # create(conf/, cxx) + $ b configure: config.cxx=g++ src/ # configure(src/) config.cxx=g++ + \ + + To activate the shortcut syntax the first buildspec argument must start + with an operation or meta-operation name and end with a colon (\cb{:}). + To transform the shortcut syntax to the normal buildspec syntax the colon + is replaced with the opening parenthesis ('\cb{(}'), the rest of the + buildspec arguments are treated as is, and the final closing parenthesis + ('\cb{)}') is added. + + For each <target> the driver expects to find \cb{buildfile} either in the + target's directory or, if the directory is part of the \cb{out} tree + (\cb{out_base}), in the corresponding \cb{src} directory (\cb{src_base}). + + For example, assuming \cb{foo/} is the source directory of a project: + + \ + $ b foo/ # out_base=src_base=foo/ + $ b foo-out/ # out_base=foo-out/ src_base=foo/ + $ b foo-out/exe{foo} # out_base=foo-out/ src_base=foo/ + \ + + An exception to this requirement is a directory target in which case, + provided the directory has subdirectories, an \i{implied} \cb{buildfile} + with the following content is assumed: + + \ + # Implied directory buildfile: build all subdirectories. + # + ./: */ + \ + + In the above example, we assumed that the build system driver was able to + determine the association between \cb{out_base} and \cb{src_base}. In + case \cb{src_base} and \cb{out_base} are not the same directory, this is + achieved in one of two ways: the \cb{config} module (which implements the + \cb{configure}, \cb{disfigure}, and \cb{create} meta-operations) saves + this association as part of the configuration process. If, however, the + association hasn't been saved, then we have to specify \cb{src_base} + explicitly using the following extended <target> syntax: + + \c{<src-base>/@<target>} + + Continuing with the previous example: + + \ + $ b foo/@foo-out/exe{foo} # out_base=foo-out/ src_base=foo/ + \ + + Normally, you would need to specify \cb{src_base} explicitly only once, + during configuration. For example, a typical usage would be: + + \ + $ b configure: foo/@foo-out/ # src_base is saved + $ b foo-out/ # no need to specify src_base + $ b clean: foo-out/exe{foo} # no need to specify src_base + \ + + Besides in and out of source builds, \cb{build2} also supports + configuring a project's source directory as \i{forwarded} to an out of + source build. With such a forwarded configuration in place, if we run the + build system driver from the source directory, it will automatically + build in the output directory and \i{backlink} (using symlinks or another + suitable mechanism) certain \"interesting\" targets (executables, + documentation, etc) to the source directory for easy access. Continuing + with the previous example: + + \ + $ b configure: foo/@foo-out/,forward # foo/ forwarded to foo-out/ + $ cd foo/ + $ b # build in foo-out/ + $ ./foo # symlink to foo-out/foo + \ + + The ability to specify \cb{build2} variables as part of the command line + is normally used to pass configuration values, for example: + + \ + $ b config.cxx=clang++ config.cxx.coptions=-O3 + \ + + Similar to buildspec, POSIX shells often inhibit path auto-completion on + the right hand side of a variable assignment. To help with this situation + the assignment can be broken down into three separate command line + arguments, for example: + + \ + $ b config.import.libhello = ../libhello/ + \ + + The build system has the following built-in and pre-defined + meta-operations: + + \dl| + + \li|\cb{perform} + + Perform an operation.| + + \li|\cb{configure} + + Configure all operations supported by a project and save the result + in the project's \cb{build/config.build} file. Implemented by the + \cb{config} module. For example: + + \ + $ b configure \ + config.cxx=clang++ \ + config.cxx.coptions=-O3 \ + config.install.root=/usr/local \ + config.install.root.sudo=sudo + \ + + Use the \cb{forward} parameter to instead configure a source + directory as forwarded to an out of source build. For example: + + \ + $ b configure: src/@out/,forward + \ + + | + + \li|\cb{disfigure} + + Disfigure all operations supported by a project and remove the + project's \cb{build/config.build} file. Implemented by the + \cb{config} module. + + Use the \cb{forward} parameter to instead disfigure forwarding of a + source directory to an out of source build. For example: + + \ + $ b disfigure: src/,forward + \ + + | + + \li|\cb{create} + + Create and configure a \i{configuration} project. Implemented by the + \cb{config} module. + + Normally a \cb{build2} project is created manually by writing the + \cb{bootstrap.build} and \cb{config.build} files, adding source + files, and so on. However, a special kind of project, which we call + \i{configuration}, is often useful. Such a project doesn't have any + source files of its own. Instead, it serves as an amalgamation for + building other projects as part of it. Doing it this way has two + major benefits: sub-projects automatically resolve their imports + to other projects in the amalgamation and sub-projects inherits their + configuration from the amalgamation (which means if we want to change + something, we only need to do it in one place). + + As an example, let's assume we have two C++ projects: the + \cb{libhello} library in \cb{libhello/} and the \cb{hello} executable + that imports it in \cb{hello/}. And we want to build \cb{hello} with + \cb{clang++}. + + One way to do it would be to configure and build each project in its + own directory, for example: + + \ + $ b configure: libhello/@libhello-clang/ config.cxx=clang++ + $ b configure: hello/@hello-clang/ config.cxx=clang++ \ + config.import.libhello=libhello-clang/ + \ + + The two drawbacks, as mentioned above, are the need to explicitly + resolve the import and having to make changes in multiple places + should, for example, we want to switch from \cb{clang++} to \cb{g++}. + + We can, however, achieve the same end result but without any of the + drawbacks using the configuration project: + + \ + $ b create: clang/,cxx config.cxx=clang++ # Creates clang/. + $ b configure: libhello/@clang/libhello/ + $ b configure: hello/@clang/hello/ + \ + + The targets passed to the \cb{create} meta-operation must be + directories which should either not exist or be empty. For each + such directory \cb{create} first initializes a project as described + below and then configures it by executing the \cb{configure} + meta-operation. + + The first optional parameter to \cb{create} is the list of modules to + load in \cb{root.build}. By default, \cb{create} appends \cb{.config} + to the names of these modules so that only their configurations are + loaded. You can override this behavior by specifying the period + (\cb{.}) after the module name. You can also instruct \cb{create} to + use the optional module load by prefixing the module name with the + question mark (\cb{?}). + + The second optional parameter is the list of modules to load in + \cb{bootstrap.build}. If not specified, then the \cb{test}, + \cb{dist}, and \cb{install} modules are loaded by default. The + \cb{config} module is always loaded first. + + Besides creating project's \cb{bootstrap.build} and \cb{root.build}, + \cb{create} also writes the root \cb{buildfile} with the following + contents: + + \ + ./: {*/ -build/} + \ + + If used, this \cb{buildfile} will build all the sub-projects + currently present in the configuration.| + + \li|\cb{dist} + + Prepare a distribution containing all files necessary to perform all + operations in a project. Implemented by the \cb{dist} module.| + + \li|\cb{info} + + Print basic information (name, version, source and output + directories, etc) about one or more projects to \cb{STDOUT}, + separating multiple projects with a blank line. Each project is + identified by its root directory target. For example: + + \ + $ b info: libfoo/ libbar/ + \ + + || + + The build system has the following built-in and pre-defined operations: + + \dl| + + \li|\cb{update} + + Update a target.| + + \li|\cb{clean} + + Clean a target.| + + \li|\cb{test} + + Test a target. Performs \cb{update} as a pre-operation. Implemented by + the \cb{test} module.| + + \li|\cb{update-for-test} + + Update a target for testing. This operation is equivalent to the + \cb{update} pre-operation as executed by the \cb{test} operation and + can be used to only update what is necessary for testing. Implemented + by the \cb{test} module.| + + \li|\cb{install} + + Install a target. Performs \cb{update} as a pre-operation. Implemented + by the \cb{install} module.| + + + \li|\cb{uninstall} + + Uninstall a target. Performs \cb{update} as a pre-operation. + Implemented by the \cb{install} module.| + + \li|\cb{update-for-install} + + Update a target for installation. This operation is equivalent to the + \cb{update} pre-operation as executed by the \cb{install} operation + and can be used to only update what is necessary for + installation. Implemented by the \cb{install} module.|| + + Note that buildspec and command line variable values are treated as + \cb{buildfile} fragments and so can use quoting and escaping as well as + contain variable expansions and evaluation contexts. However, to be more + usable on various platforms, escaping in these two situations is limited + to the \i{effective sequences} of \cb{\\'}, \cb{\\\"}, \cb{\\\\}, + \cb{\\$}, and \cb{\\(} with all other sequences interpreted as is. + Together with double-quoting this is sufficient to represent any value. + For example: + + \ + $ b config.install.root=c:\projects\install + $ b \"config.install.root='c:\Program Files (x86)\test\'\" + $ b 'config.cxx.poptions=-DFOO_STR=\"foo\"' + \ + " + } + + class options + { + "\h#options|OPTIONS|" + + uint64_t --build2-metadata; // Leave undocumented/hidden. + + bool -v + { + "Print actual commands being executed. This options is equivalent to + \cb{--verbose 2}." + } + + bool -V + { + "Print all underlying commands being executed. This options is + equivalent to \cb{--verbose 3}." + } + + bool --quiet|-q + { + "Run quietly, only printing error messages in most contexts. In certain + contexts (for example, while updating build system modules) this + verbosity level may be ignored. Use \cb{--silent} to run quietly in all + contexts. This option is equivalent to \cb{--verbose 0}." + } + + bool --silent + { + "Run quietly, only printing error messages in all contexts." + } + + uint16_t --verbose = 1 + { + "<level>", + "Set the diagnostics verbosity to <level> between 0 and 6. Level 0 + disables any non-error messages (but see the difference between + \cb{--quiet} and \cb{--silent}) while level 6 produces lots of + information, with level 1 being the default. The following additional + types of diagnostics are produced at each level: + + \ol| + + \li|High-level information messages.| + + \li|Essential underlying commands being executed.| + + \li|All underlying commands being executed.| + + \li|Information that could be helpful to the user.| + + \li|Information that could be helpful to the developer.| + + \li|Even more detailed information.||" + } + + bool --stat + { + "Display build statistics." + } + + std::set<string> --dump + { + "<phase>", + "Dump the build system state after the specified phase. Valid <phase> + values are \cb{load} (after loading \cb{buildfiles}) and \cb{match} + (after matching rules to targets). Repeat this option to dump the + state after multiple phases." + } + + bool --progress + { + "Display build progress. If printing to a terminal the progress is + displayed by default for low verbosity levels. Use \cb{--no-progress} + to suppress." + } + + bool --no-progress + { + "Don't display build progress." + } + + size_t --jobs|-j + { + "<num>", + "Number of active jobs to perform in parallel. This includes both the + number of active threads inside the build system as well as the number + of external commands (compilers, linkers, etc) started but not yet + finished. If this option is not specified or specified with the \cb{0} + value, then the number of available hardware threads is used." + } + + size_t --max-jobs|-J + { + "<num>", + "Maximum number of jobs (threads) to create. The default is 8x the + number of active jobs (\cb{--jobs|j}) on 32-bit architectures and 32x + on 64-bit. See the build system scheduler implementation for details." + } + + size_t --queue-depth|-Q = 4 + { + "<num>", + "The queue depth as a multiplier over the number of active jobs. + Normally we want a deeper queue if the jobs take long (for example, + compilation) and shorter if they are quick (for example, simple tests). + The default is 4. See the build system scheduler implementation for + details." + } + + string --file-cache + { + "<impl>", + "File cache implementation to use for intermediate build results. Valid + values are \cb{noop} (no caching or compression) and \cb{sync-lz4} (no + caching with synchronous LZ4 on-disk compression). If this option is + not specified, then a suitable default implementation is used + (currently \cb{sync-lz4})." + } + + size_t --max-stack + { + "<num>", + "The maximum stack size in KBytes to allow for newly created threads. + For \i{pthreads}-based systems the driver queries the stack size of + the main thread and uses the same size for creating additional threads. + This allows adjusting the stack size using familiar mechanisms, such + as \cb{ulimit}. Sometimes, however, the stack size of the main thread + is excessively large. As a result, the driver checks if it is greater + than a predefined limit (64MB on 64-bit systems and 32MB on 32-bit + ones) and caps it to a more sensible value (8MB) if that's the case. + This option allows you to override this check with the special zero + value indicating that the main thread stack size should be used as is." + } + + bool --serial-stop|-s + { + "Run serially and stop at the first error. This mode is useful to + investigate build failures that are caused by build system errors + rather than compilation errors. Note that if you don't want to keep + going but still want parallel execution, add \cb{--jobs|-j} (for + example \cb{-j\ 0} for default concurrency)." + } + + bool --dry-run|-n + { + "Print commands without actually executing them. Note that commands + that are required to create an accurate build state will still be + executed and the extracted auxiliary dependency information saved. In + other words, this is not the \i{\"don't touch the filesystem\"} mode + but rather \i{\"do minimum amount of work to show what needs to be + done\"}. Note also that only the \cb{perform} meta-operation supports + this mode." + } + + bool --match-only + { + "Match the rules but do not execute the operation. This mode is primarily + useful for profiling." + } + + bool --no-external-modules + { + "Don't load external modules during project bootstrap. Note that this + option can only be used with meta-operations that do not load the + project's \cb{buildfiles}, such as \cb{info}." + } + + bool --structured-result + { + "Write the result of execution in a structured form. In this mode, + instead of printing to \cb{STDERR} diagnostics messages about the + outcome of executing actions on targets, the driver writes to + \cb{STDOUT} a structured result description one line per the + buildspec action/target pair. Each line has the following format: + + \c{\i{state} \i{meta-operation} \i{operation} \i{target}} + + Where \ci{state} can be one of \cb{unchanged}, \cb{changed}, or + \cb{failed}. If the action is a pre or post operation, then the + outer operation is specified in parenthesis. For example: + + \ + unchanged perform update(test) /tmp/dir{hello/} + changed perform test /tmp/dir{hello/} + \ + + Note that only the \cb{perform} meta-operation supports the structured + result output. + " + } + + bool --mtime-check + { + "Perform file modification time sanity checks. These checks can be + helpful in diagnosing spurious rebuilds and are enabled by default + on Windows (which is known not to guarantee monotonically increasing + mtimes) and for the staged version of the build system on other + platforms. Use \cb{--no-mtime-check} to disable." + } + + bool --no-mtime-check + { + "Don't perform file modification time sanity checks. See + \cb{--mtime-check} for details." + } + + bool --no-column + { + "Don't print column numbers in diagnostics." + } + + bool --no-line + { + "Don't print line and column numbers in diagnostics." + } + + path --buildfile + { + "<path>", + "The alternative file to read build information from. The default is + \cb{buildfile} or \cb{build2file}, depending on the project's build + file/directory naming scheme. If <path> is '\cb{-}', then read from + \cb{STDIN}. Note that this option only affects the files read as part + of the buildspec processing. Specifically, it has no effect on the + \cb{source} and \cb{include} directives. As a result, this option is + primarily intended for testing rather than changing the build file + names in real projects." + } + + path --config-guess + { + "<path>", + "The path to the \cb{config.guess(1)} script that should be used to + guess the host machine triplet. If this option is not specified, then + \cb{b} will fall back on to using the target it was built for as host." + } + + path --config-sub + { + "<path>", + "The path to the \cb{config.sub(1)} script that should be used to + canonicalize machine triplets. If this option is not specified, then + \cb{b} will use its built-in canonicalization support which should + be sufficient for commonly-used platforms." + } + + string --pager // String to allow empty value. + { + "<path>", + "The pager program to be used to show long text. Commonly used pager + programs are \cb{less} and \cb{more}. You can also specify additional + options that should be passed to the pager program with + \cb{--pager-option}. If an empty string is specified as the pager + program, then no pager will be used. If the pager program is not + explicitly specified, then \cb{b} will try to use \cb{less}. If it + is not available, then no pager will be used." + } + + strings --pager-option + { + "<opt>", + "Additional option to be passed to the pager program. See \cb{--pager} + for more information on the pager program. Repeat this option to + specify multiple pager options." + } + + // The following option is "fake" in that it is actually handled by + // argv_file_scanner. We have it here for documentation. + // + string --options-file + { + "<file>", + "Read additional options from <file>. Each option should appear on a + separate line optionally followed by space or equal sign (\cb{=}) and + an option value. Empty lines and lines starting with \cb{#} are + ignored. Option values can be enclosed in double (\cb{\"}) or single + (\cb{'}) quotes to preserve leading and trailing whitespaces as well as + to specify empty values. If the value itself contains trailing or + leading quotes, enclose it with an extra pair of quotes, for example + \cb{'\"x\"'}. Non-leading and non-trailing quotes are interpreted as + being part of the option value. + + The semantics of providing options in a file is equivalent to providing + the same set of options in the same order on the command line at the + point where the \cb{--options-file} option is specified except that + the shell escaping and quoting is not required. Repeat this option + to specify more than one options file." + } + + dir_path --default-options + { + "<dir>", + "The directory to load additional default options files from." + } + + bool --no-default-options + { + "Don't load default options files." + } + + bool --help {"Print usage information and exit."} + bool --version {"Print version and exit."} + }; + + " + \h|DEFAULT OPTIONS FILES| + + Instead of having a separate config file format for tool configuration, the + \cb{build2} toolchain uses \i{default options files} which contain the same + options as what can be specified on the command line. The default options + files are like options files that one can specify with \cb{--options-file} + except that they are loaded by default. + + The default options files for the build system driver are called + \cb{b.options} and are searched for in the \cb{.build2/} subdirectory of the + home directory and in the system directory (for example, \cb{/etc/build2/}) + if configured. Note that besides options these files can also contain global + variable overrides. + + Once the search is complete, the files are loaded in the reverse order, that + is, beginning from the system directory (if any), followed by the home + directory, and finishing off with the options specified on the command line. + In other words, the files are loaded from the more generic to the more + specific with the command line options having the ability to override any + values specified in the default options files. + + If a default options file contains \cb{--no-default-options}, then the + search is stopped at the directory containing this file and no outer files + are loaded. If this option is specified on the command line, then none of + the default options files are searched for or loaded. + + An additional directory containing default options files can be specified + with \cb{--default-options}. Its configuration files are loaded after the + home directory. + + The order in which default options files are loaded is traced at the + verbosity level 3 (\cb{-V} option) or higher. + + \h|EXIT STATUS| + + Non-zero exit status is returned in case of an error. + " + + // NOTE: remember to update --build2-metadata output if adding any relevant + // new environment variables. + // + " + \h|ENVIRONMENT| + + The \cb{HOME} environment variable is used to determine the user's home + directory. If it is not set, then \cb{getpwuid(3)} is used instead. This + value is used to shorten paths printed in diagnostics by replacing the home + directory with \cb{~/}. It is also made available to \cb{buildfile}'s as the + \cb{build.home} variable. + + The \cb{BUILD2_VAR_OVR} environment variable is used to propagate global + variable overrides to nested build system driver invocations. Its value is a + list of global variable assignments separated with newlines. + + The \cb{BUILD2_DEF_OPT} environment variable is used to suppress loading of + default options files in nested build system driver invocations. Its values + are \cb{false} or \cb{0} to suppress and \cb{true} or \cb{1} to load. + " +} diff --git a/libbuild2/buildfile b/libbuild2/buildfile index ee320e4..74d7485 100644 --- a/libbuild2/buildfile +++ b/libbuild2/buildfile @@ -25,8 +25,13 @@ include $bundled_modules # intf_libs = $libbutl -lib{build2}: libul{build2}: \ - {hxx ixx txx cxx}{* -utility-*installed -config -version -*.test...} \ +lib{build2}: libul{build2}: \ + {hxx ixx txx cxx}{* -utility-*installed \ + -b-options \ + -config \ + -version \ + -*.test...} \ + {hxx ixx cxx}{b-options} \ {hxx}{config version} libul{build2}: script/{hxx ixx txx cxx}{** -*-options -**.test...} \ @@ -227,19 +232,16 @@ else if $cli.configured { cli.options += --std c++11 -I $src_root --include-with-brackets \ ---generate-vector-scanner --generate-modifier --generate-specifier \ ---suppress-usage +--generate-specifier 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. + # to distributed). # 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. @@ -247,10 +249,31 @@ if $cli.configured backlink = overwrite } + cli.cxx{b-options}: cli{b} + { + cli.options += --cli-namespace build2::build::cli \ +--include-prefix libbuild2 --guard-prefix LIBBUILD2 \ +--export-symbol LIBBUILD2_SYMEXPORT \ +--hxx-prologue '#include <libbuild2/export.hxx>' \ +--cxx-prologue "#include <libbuild2/types-parsers.hxx>" \ +--generate-file-scanner --keep-separator --generate-parse --generate-merge + + # Usage options. + # + cli.options += --suppress-undocumented --long-usage --ansi-color \ +--ascii-tree --page-usage 'build2::print_$name$_' --option-length 21 + } + script/cli.cxx{builtin-options}: script/cli{builtin} { cli.options += --cli-namespace build2::script::cli \ ---include-prefix libbuild2/script --guard-prefix LIBBUILD2_SCRIPT +--include-prefix libbuild2/script --guard-prefix LIBBUILD2_SCRIPT \ +--generate-vector-scanner --generate-modifier --suppress-usage + + # Don't install the generated cli headers since they are only used + # internally in the script implementation. + # + install = false } build/script/cli.cxx{builtin-options}: build/script/cli{builtin} @@ -258,7 +281,12 @@ if $cli.configured cli.options += --cli-namespace build2::build::script::cli \ --include-prefix libbuild2/build/script --guard-prefix LIBBUILD2_BUILD_SCRIPT \ --cxx-prologue "#include <libbuild2/build/script/types-parsers.hxx>" \ ---generate-parse +--generate-parse --generate-vector-scanner --generate-modifier --suppress-usage + + # Don't install the generated cli headers since they are only used + # internally in the buildscript implementation. + # + install = false } } else diff --git a/libbuild2/cmdline.cxx b/libbuild2/cmdline.cxx new file mode 100644 index 0000000..a5f9616 --- /dev/null +++ b/libbuild2/cmdline.cxx @@ -0,0 +1,408 @@ +// file : libbuild2/cmdline.cxx -*- C++ -*- +// license : MIT; see accompanying LICENSE file + +#include <libbuild2/cmdline.hxx> + +#include <limits> +#include <cstring> // strcmp(), strchr() + +#include <libbutl/default-options.hxx> + +#include <libbuild2/b-options.hxx> +#include <libbuild2/diagnostics.hxx> + +using namespace std; +using namespace butl; + +namespace cli = build2::build::cli; + +namespace build2 +{ + cmdline + parse_cmdline (tracer& trace, int argc, char* argv[], options& ops) + { + // Note that the diagnostics verbosity level can only be calculated after + // default options are loaded and merged (see below). Thus, until then we + // refer to the verbosity level specified on the command line. + // + auto verbosity = [&ops] () + { + uint16_t v ( + ops.verbose_specified () + ? ops.verbose () + : ops.V () ? 3 : ops.v () ? 2 : ops.quiet () || ops.silent () ? 0 : 1); + return v; + }; + + cmdline r; + + // We want to be able to specify options, vars, and buildspecs in any + // order (it is really handy to just add -v at the end of the command + // line). + // + try + { + // Command line arguments starting position. + // + // We want the positions of the command line arguments to be after the + // default options files. Normally that would be achieved by passing the + // last position of the previous scanner to the next. The problem is + // that we parse the command line arguments first (for good reasons). + // Also the default options files parsing machinery needs the maximum + // number of arguments to be specified and assigns the positions below + // this value (see load_default_options() for details). So we are going + // to "reserve" the first half of the size_t value range for the default + // options positions and the second half for the command line arguments + // positions. + // + size_t args_pos (numeric_limits<size_t>::max () / 2); + cli::argv_file_scanner scan (argc, argv, "--options-file", args_pos); + + size_t argn (0); // Argument count. + bool shortcut (false); // True if the shortcut syntax is used. + + for (bool opt (true), var (true); scan.more (); ) + { + if (opt) + { + // Parse the next chunk of options until we reach an argument (or + // eos). + // + if (ops.parse (scan) && !scan.more ()) + break; + + // If we see first "--", then we are done parsing options. + // + if (strcmp (scan.peek (), "--") == 0) + { + scan.next (); + opt = false; + continue; + } + + // Fall through. + } + + const char* s (scan.next ()); + + // See if this is a command line variable. What if someone needs to + // pass a buildspec that contains '='? One way to support this would + // be to quote such a buildspec (e.g., "'/tmp/foo=bar/'"). Or invent + // another separator. Or use a second "--". Actually, let's just do + // the second "--". + // + if (var) + { + // If we see second "--", then we are also done parsing variables. + // + if (strcmp (s, "--") == 0) + { + var = false; + continue; + } + + if (const char* p = strchr (s, '=')) // Covers =, +=, and =+. + { + // Diagnose the empty variable name situation. Note that we don't + // allow "partially broken down" assignments (as in foo =bar) + // since foo= bar would be ambigous. + // + if (p == s || (p == s + 1 && *s == '+')) + fail << "missing variable name in '" << s << "'"; + + r.cmd_vars.push_back (s); + continue; + } + + // Handle the "broken down" variable assignments (i.e., foo = bar + // instead of foo=bar). + // + if (scan.more ()) + { + const char* a (scan.peek ()); + + if (strcmp (a, "=" ) == 0 || + strcmp (a, "+=") == 0 || + strcmp (a, "=+") == 0) + { + string v (s); + v += a; + + scan.next (); + + if (scan.more ()) + v += scan.next (); + + r.cmd_vars.push_back (move (v)); + continue; + } + } + + // Fall through. + } + + // Merge all the individual buildspec arguments into a single string. + // We use newlines to separate arguments so that line numbers in + // diagnostics signify argument numbers. Clever, huh? + // + if (argn != 0) + r.buildspec += '\n'; + + r.buildspec += s; + + // See if we are using the shortcut syntax. + // + if (argn == 0 && r.buildspec.back () == ':') + { + r.buildspec.back () = '('; + shortcut = true; + } + + argn++; + } + + // Add the closing parenthesis unless there wasn't anything in between + // in which case pop the opening one. + // + if (shortcut) + { + if (argn == 1) + r.buildspec.pop_back (); + else + r.buildspec += ')'; + } + + // Get/set an environment variable tracing the operation. + // + auto get_env = [&verbosity, &trace] (const char* nm) + { + optional<string> r (getenv (nm)); + + if (verbosity () >= 5) + { + if (r) + trace << nm << ": '" << *r << "'"; + else + trace << nm << ": <NULL>"; + } + + return r; + }; + + auto set_env = [&verbosity, &trace] (const char* nm, const string& vl) + { + try + { + if (verbosity () >= 5) + trace << "setting " << nm << "='" << vl << "'"; + + setenv (nm, vl); + } + catch (const system_error& e) + { + // The variable value can potentially be long/multi-line, so let's + // print it last. + // + fail << "unable to set environment variable " << nm << ": " << e << + info << "value: '" << vl << "'"; + } + }; + + // If the BUILD2_VAR_OVR environment variable is present, then parse its + // value as a newline-separated global variable overrides and prepend + // them to the overrides specified on the command line. + // + // Note that this means global overrides may not contain a newline. + + // Verify that the string is a valid global override. Uses the file name + // and the options flag for diagnostics only. + // + auto verify_glb_ovr = [] (const string& v, const path_name& fn, bool opt) + { + size_t p (v.find ('=', 1)); + if (p == string::npos || v[0] != '!') + { + diag_record dr (fail (fn)); + dr << "expected " << (opt ? "option or " : "") << "global " + << "variable override instead of '" << v << "'"; + + if (p != string::npos) + dr << info << "prefix variable assignment with '!'"; + } + + if (p == 1 || (p == 2 && v[1] == '+')) // '!=' or '!+=' ? + fail (fn) << "missing variable name in '" << v << "'"; + }; + + optional<string> env_ovr (get_env ("BUILD2_VAR_OVR")); + if (env_ovr) + { + path_name fn ("<BUILD2_VAR_OVR>"); + + auto i (r.cmd_vars.begin ()); + for (size_t b (0), e (0); next_word (*env_ovr, b, e, '\n', '\r'); ) + { + // Extract the override from the current line, stripping the leading + // and trailing spaces. + // + string s (*env_ovr, b, e - b); + trim (s); + + // Verify and save the override, unless the line is empty. + // + if (!s.empty ()) + { + verify_glb_ovr (s, fn, false /* opt */); + i = r.cmd_vars.insert (i, move (s)) + 1; + } + } + } + + // Load the default options files, unless --no-default-options is + // specified on the command line or the BUILD2_DEF_OPT environment + // variable is set to a value other than 'true' or '1'. + // + // If loaded, prepend the default global overrides to the variables + // specified on the command line, unless BUILD2_VAR_OVR is set in which + // case just ignore them. + // + optional<string> env_def (get_env ("BUILD2_DEF_OPT")); + + // False if --no-default-options is specified on the command line. Note + // that we cache the flag since it can be overridden by a default + // options file. + // + bool cmd_def (!ops.no_default_options ()); + + if (cmd_def && (!env_def || *env_def == "true" || *env_def == "1")) + try + { + optional<dir_path> extra; + if (ops.default_options_specified ()) + extra = ops.default_options (); + + // Load default options files. + // + default_options<options> def_ops ( + load_default_options<options, + cli::argv_file_scanner, + cli::unknown_mode> ( + nullopt /* sys_dir */, + path::home_directory (), // The home variable is not assigned yet. + extra, + default_options_files {{path ("b.options")}, + nullopt /* start */}, + [&trace, &verbosity] (const path& f, bool r, bool o) + { + if (verbosity () >= 3) + { + if (o) + trace << "treating " << f << " as " + << (r ? "remote" : "local"); + else + trace << "loading " << (r ? "remote " : "local ") << f; + } + }, + "--options-file", + args_pos, + 1024, + true /* args */)); + + // Merge the default and command line options. + // + ops = merge_default_options (def_ops, ops); + + // Merge the default and command line global overrides, unless + // BUILD2_VAR_OVR is already set (in which case we assume this has + // already been done). + // + // Note that the "broken down" variable assignments occupying a single + // line are naturally supported. + // + if (!env_ovr) + r.cmd_vars = + merge_default_arguments ( + def_ops, + r.cmd_vars, + [&verify_glb_ovr] (const default_options_entry<options>& e, + const strings&) + { + path_name fn (e.file); + + // Verify that all arguments are global overrides. + // + for (const string& a: e.arguments) + verify_glb_ovr (a, fn, true /* opt */); + }); + } + catch (const invalid_argument& e) + { + fail << "unable to load default options files: " << e; + } + catch (const pair<path, system_error>& e) + { + fail << "unable to load default options files: " << e.first << ": " + << e.second; + } + catch (const system_error& e) + { + fail << "unable to obtain home directory: " << e; + } + + // Verify and save the global overrides present in cmd_vars (default, + // from the command line, etc), if any, into the BUILD2_VAR_OVR + // environment variable. + // + if (!r.cmd_vars.empty ()) + { + string ovr; + for (const string& v: r.cmd_vars) + { + if (v[0] == '!') + { + if (v.find_first_of ("\n\r") != string::npos) + fail << "newline in global variable override '" << v << "'"; + + if (!ovr.empty ()) + ovr += '\n'; + + ovr += v; + } + } + + // Optimize for the common case. + // + // Note: cmd_vars may contain non-global overrides. + // + if (!ovr.empty () && (!env_ovr || *env_ovr != ovr)) + set_env ("BUILD2_VAR_OVR", ovr); + } + + // Propagate disabling of the default options files to the potential + // nested invocations. + // + if (!cmd_def && (!env_def || *env_def != "0")) + set_env ("BUILD2_DEF_OPT", "0"); + + // Validate options. + // + if (ops.progress () && ops.no_progress ()) + fail << "both --progress and --no-progress specified"; + + if (ops.mtime_check () && ops.no_mtime_check ()) + fail << "both --mtime-check and --no-mtime-check specified"; + } + catch (const cli::exception& e) + { + fail << e; + } + + r.verbosity = verbosity (); + + if (ops.silent () && r.verbosity != 0) + fail << "specified with -v, -V, or --verbose verbosity level " + << r.verbosity << " is incompatible with --silent"; + + return r; + } +} diff --git a/libbuild2/cmdline.hxx b/libbuild2/cmdline.hxx new file mode 100644 index 0000000..7bf41c2 --- /dev/null +++ b/libbuild2/cmdline.hxx @@ -0,0 +1,29 @@ +// file : libbuild2/cmdline.hxx -*- C++ -*- +// license : MIT; see accompanying LICENSE file + +#ifndef LIBBUILD2_CMDLINE_HXX +#define LIBBUILD2_CMDLINE_HXX + +#include <libbuild2/types.hxx> +#include <libbuild2/forward.hxx> +#include <libbuild2/utility.hxx> + +#include <libbuild2/b-options.hxx> +#include <libbuild2/diagnostics.hxx> + +#include <libbuild2/export.hxx> + +namespace build2 +{ + struct cmdline + { + strings cmd_vars; + string buildspec; + uint16_t verbosity; + }; + + LIBBUILD2_SYMEXPORT cmdline + parse_cmdline (tracer&, int argc, char* argv[], options&); +} + +#endif // LIBBUILD2_CMDLINE_HXX diff --git a/libbuild2/types-parsers.cxx b/libbuild2/types-parsers.cxx new file mode 100644 index 0000000..86ce219 --- /dev/null +++ b/libbuild2/types-parsers.cxx @@ -0,0 +1,53 @@ +// file : libbuild2/types-parsers.cxx -*- C++ -*- +// license : MIT; see accompanying LICENSE file + +#include <libbuild2/types-parsers.hxx> + +#include <libbuild2/b-options.hxx> // build2::build::cli namespace + +namespace build2 +{ + namespace build + { + namespace cli + { + template <typename T> + static void + parse_path (T& x, scanner& s) + { + const char* o (s.next ()); + + if (!s.more ()) + throw missing_value (o); + + const char* v (s.next ()); + + try + { + x = T (v); + + if (x.empty ()) + throw invalid_value (o, v); + } + catch (const invalid_path&) + { + throw invalid_value (o, v); + } + } + + void parser<path>:: + parse (path& x, bool& xs, scanner& s) + { + xs = true; + parse_path (x, s); + } + + void parser<dir_path>:: + parse (dir_path& x, bool& xs, scanner& s) + { + xs = true; + parse_path (x, s); + } + } + } +} diff --git a/libbuild2/types-parsers.hxx b/libbuild2/types-parsers.hxx new file mode 100644 index 0000000..c64e0f6 --- /dev/null +++ b/libbuild2/types-parsers.hxx @@ -0,0 +1,48 @@ +// file : libbuild2/types-parsers.hxx -*- C++ -*- +// license : MIT; see accompanying LICENSE file + +// CLI parsers, included into the generated source files. +// + +#ifndef LIBBUILD2_TYPES_PARSERS_HXX +#define LIBBUILD2_TYPES_PARSERS_HXX + +#include <libbuild2/types.hxx> + +#include <libbuild2/types-parsers.hxx> + +namespace build2 +{ + namespace build + { + namespace cli + { + class scanner; + + template <typename T> + struct parser; + + template <> + struct parser<path> + { + static void + parse (path&, bool&, scanner&); + + static void + merge (path& b, const path& a) {b = a;} + }; + + template <> + struct parser<dir_path> + { + static void + parse (dir_path&, bool&, scanner&); + + static void + merge (dir_path& b, const dir_path& a) {b = a;} + }; + } + } +} + +#endif // LIBBUILD2_TYPES_PARSERS_HXX |