From c988a7459c85bc4139cc2e151ddb5758a998ab5a Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Thu, 17 Feb 2022 10:43:20 +0200 Subject: Factor command line parsing logic into separate function --- build2/b.cxx | 392 ++++++++++++++++++++++++++++++++--------------------------- 1 file changed, 210 insertions(+), 182 deletions(-) (limited to 'build2/b.cxx') diff --git a/build2/b.cxx b/build2/b.cxx index c094523..03ded28 100644 --- a/build2/b.cxx +++ b/build2/b.cxx @@ -71,190 +71,37 @@ using namespace std; namespace build2 { - static options ops; - - int - main (int argc, char* argv[]); - - // Structured result printer (--structured-result mode). - // - class result_printer + struct cmdline { - public: - result_printer (const action_targets& tgs): tgs_ (tgs) {} - ~result_printer (); - - private: - const action_targets& tgs_; + strings cmd_vars; + string buildspec; + uint16_t verbosity; }; - result_printer:: - ~result_printer () - { - // Let's do some sanity checking even when we are not in the structred - // output mode. - // - for (const action_target& at: tgs_) - { - switch (at.state) - { - case target_state::unknown: continue; // Not a target/no result. - case target_state::unchanged: - case target_state::changed: - case target_state::failed: break; // Valid states. - default: assert (false); - } - - if (ops.structured_result ()) - { - const target& t (at.as ()); - context& ctx (t.ctx); - - cout << at.state - << ' ' << ctx.current_mif->name - << ' ' << ctx.current_inner_oif->name; - - if (ctx.current_outer_oif != nullptr) - cout << '(' << ctx.current_outer_oif->name << ')'; - - // There are two ways one may wish to identify the target of the - // operation: as something specific but inherently non-portable (say, - // a filesystem path, for example c:\tmp\foo.exe) or as something - // regular that can be used to refer to a target in a portable way - // (for example, c:\tmp\exe{foo}; note that the directory part is - // still not portable). Which one should we use is a good question. - // Let's go with the portable one for now and see how it goes (we - // can always add a format version, e.g., --structured-result=2). - - // Set the stream extension verbosity to 0 to suppress extension - // printing by default (this can still be overriden by the target - // type's print function as is the case for file{}, for example). - // And set the path verbosity to 1 to always print absolute. - // - stream_verbosity sv (stream_verb (cout)); - stream_verb (cout, stream_verbosity (1, 0)); - - cout << ' ' << t << endl; - - stream_verb (cout, sv); - } - } - } -} - -// Print backtrace if terminating due to an unhandled exception. Note that -// custom_terminate is non-static and not a lambda to reduce the noise. -// -static terminate_handler default_terminate; - -void -custom_terminate () -{ - *diag_stream << backtrace (); - - if (default_terminate != nullptr) - default_terminate (); -} - -static void -terminate (bool trace) -{ - if (!trace) - set_terminate (default_terminate); - - std::terminate (); -} - -int build2:: -main (int argc, char* argv[]) -{ - default_terminate = set_terminate (custom_terminate); - - tracer trace ("main"); - - int r (0); - - // This is a little hack to make out baseutils for Windows work when called - // with absolute path. In a nutshell, MSYS2's exec*p() doesn't search in the - // parent's executable directory, only in PATH. And since we are running - // without a shell (that would read /etc/profile which sets PATH to some - // sensible values), we are only getting Win32 PATH values. And MSYS2 /bin - // is not one of them. So what we are going to do is add /bin at the end of - // PATH (which will be passed as is by the MSYS2 machinery). This will make - // MSYS2 search in /bin (where our baseutils live). And for everyone else - // this should be harmless since it is not a valid Win32 path. - // -#ifdef _WIN32 - { - string mp; - if (optional p = getenv ("PATH")) - { - mp = move (*p); - mp += ';'; - } - mp += "/bin"; - - setenv ("PATH", mp); - } -#endif - - // A data race happens in the libstdc++ (as of GCC 7.2) implementation of - // the ctype::narrow() function (bug #77704). The issue is easily - // triggered by the testscript runner that indirectly (via regex) uses - // ctype facet of the global locale (and can potentially be triggered - // by other locale- aware code). We work around this by pre-initializing the - // global locale facet internal cache. - // -#ifdef __GLIBCXX__ + static cmdline + parse_cmdline (tracer& trace, int argc, char* argv[], options& ops) { - const ctype& ct (use_facet> (locale ())); - - for (size_t i (0); i != 256; ++i) - ct.narrow (static_cast (i), '\0'); - } -#endif - - // On POSIX ignore SIGPIPE which is signaled to a pipe-writing process if - // the pipe reading end is closed. Note that by default this signal - // terminates a process. Also note that there is no way to disable this - // behavior on a file descriptor basis or for the write() function call. - // -#ifndef _WIN32 - if (signal (SIGPIPE, SIG_IGN) == SIG_ERR) - fail << "unable to ignore broken pipe (SIGPIPE) signal: " - << system_error (errno, generic_category ()); // Sanitize. -#endif - - scheduler sched; + // @@ cl namespace - // Parse the command line. - // - try - { // 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 = [] () + auto verbosity = [&ops] () { uint16_t v ( ops.verbose_specified () ? ops.verbose () : ops.V () ? 3 : ops.v () ? 2 : ops.quiet () || ops.silent () ? 0 : 1); - - if (ops.silent () && v != 0) - fail << "specified with -v, -V, or --verbose verbosity level " << v - << " is incompatible with --silent"; - 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). // - strings cmd_vars; - string args; try { // Command line arguments starting position. @@ -325,7 +172,7 @@ main (int argc, char* argv[]) if (p == s || (p == s + 1 && *s == '+')) fail << "missing variable name in '" << s << "'"; - cmd_vars.push_back (s); + r.cmd_vars.push_back (s); continue; } @@ -348,7 +195,7 @@ main (int argc, char* argv[]) if (scan.more ()) v += scan.next (); - cmd_vars.push_back (move (v)); + r.cmd_vars.push_back (move (v)); continue; } } @@ -361,15 +208,15 @@ main (int argc, char* argv[]) // diagnostics signify argument numbers. Clever, huh? // if (argn != 0) - args += '\n'; + r.buildspec += '\n'; - args += s; + r.buildspec += s; // See if we are using the shortcut syntax. // - if (argn == 0 && args.back () == ':') + if (argn == 0 && r.buildspec.back () == ':') { - args.back () = '('; + r.buildspec.back () = '('; shortcut = true; } @@ -382,9 +229,9 @@ main (int argc, char* argv[]) if (shortcut) { if (argn == 1) - args.pop_back (); + r.buildspec.pop_back (); else - args += ')'; + r.buildspec += ')'; } // Get/set an environment variable tracing the operation. @@ -454,7 +301,7 @@ main (int argc, char* argv[]) { path_name fn (""); - auto i (cmd_vars.begin ()); + 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 @@ -468,7 +315,7 @@ main (int argc, char* argv[]) if (!s.empty ()) { verify_glb_ovr (s, fn, false /* opt */); - i = cmd_vars.insert (i, move (s)) + 1; + i = r.cmd_vars.insert (i, move (s)) + 1; } } } @@ -535,10 +382,10 @@ main (int argc, char* argv[]) // line are naturally supported. // if (!env_ovr) - cmd_vars = + r.cmd_vars = merge_default_arguments ( def_ops, - cmd_vars, + r.cmd_vars, [&verify_glb_ovr] (const default_options_entry& e, const strings&) { @@ -568,10 +415,10 @@ main (int argc, char* argv[]) // from the command line, etc), if any, into the BUILD2_VAR_OVR // environment variable. // - if (!cmd_vars.empty ()) + if (!r.cmd_vars.empty ()) { string ovr; - for (const string& v: cmd_vars) + for (const string& v: r.cmd_vars) { if (v[0] == '!') { @@ -612,6 +459,187 @@ main (int argc, char* argv[]) 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; + } + + int + main (int argc, char* argv[]); + + // Structured result printer (--structured-result mode). + // + class result_printer + { + public: + result_printer (const options& ops, const action_targets& tgs) + : ops_ (ops), tgs_ (tgs) {} + + ~result_printer (); + + private: + const options ops_; + const action_targets& tgs_; + }; + + result_printer:: + ~result_printer () + { + // Let's do some sanity checking even when we are not in the structred + // output mode. + // + for (const action_target& at: tgs_) + { + switch (at.state) + { + case target_state::unknown: continue; // Not a target/no result. + case target_state::unchanged: + case target_state::changed: + case target_state::failed: break; // Valid states. + default: assert (false); + } + + if (ops_.structured_result ()) + { + const target& t (at.as ()); + context& ctx (t.ctx); + + cout << at.state + << ' ' << ctx.current_mif->name + << ' ' << ctx.current_inner_oif->name; + + if (ctx.current_outer_oif != nullptr) + cout << '(' << ctx.current_outer_oif->name << ')'; + + // There are two ways one may wish to identify the target of the + // operation: as something specific but inherently non-portable (say, + // a filesystem path, for example c:\tmp\foo.exe) or as something + // regular that can be used to refer to a target in a portable way + // (for example, c:\tmp\exe{foo}; note that the directory part is + // still not portable). Which one should we use is a good question. + // Let's go with the portable one for now and see how it goes (we + // can always add a format version, e.g., --structured-result=2). + + // Set the stream extension verbosity to 0 to suppress extension + // printing by default (this can still be overriden by the target + // type's print function as is the case for file{}, for example). + // And set the path verbosity to 1 to always print absolute. + // + stream_verbosity sv (stream_verb (cout)); + stream_verb (cout, stream_verbosity (1, 0)); + + cout << ' ' << t << endl; + + stream_verb (cout, sv); + } + } + } +} + +// Print backtrace if terminating due to an unhandled exception. Note that +// custom_terminate is non-static and not a lambda to reduce the noise. +// +static terminate_handler default_terminate; + +void +custom_terminate () +{ + *diag_stream << backtrace (); + + if (default_terminate != nullptr) + default_terminate (); +} + +static void +terminate (bool trace) +{ + if (!trace) + set_terminate (default_terminate); + + std::terminate (); +} + +int build2:: +main (int argc, char* argv[]) +{ + default_terminate = set_terminate (custom_terminate); + + tracer trace ("main"); + + int r (0); + + // This is a little hack to make out baseutils for Windows work when called + // with absolute path. In a nutshell, MSYS2's exec*p() doesn't search in the + // parent's executable directory, only in PATH. And since we are running + // without a shell (that would read /etc/profile which sets PATH to some + // sensible values), we are only getting Win32 PATH values. And MSYS2 /bin + // is not one of them. So what we are going to do is add /bin at the end of + // PATH (which will be passed as is by the MSYS2 machinery). This will make + // MSYS2 search in /bin (where our baseutils live). And for everyone else + // this should be harmless since it is not a valid Win32 path. + // +#ifdef _WIN32 + { + string mp; + if (optional p = getenv ("PATH")) + { + mp = move (*p); + mp += ';'; + } + mp += "/bin"; + + setenv ("PATH", mp); + } +#endif + + // A data race happens in the libstdc++ (as of GCC 7.2) implementation of + // the ctype::narrow() function (bug #77704). The issue is easily + // triggered by the testscript runner that indirectly (via regex) uses + // ctype facet of the global locale (and can potentially be triggered + // by other locale-aware code). We work around this by pre-initializing the + // global locale facet internal cache. + // +#ifdef __GLIBCXX__ + { + const ctype& ct (use_facet> (locale ())); + + for (size_t i (0); i != 256; ++i) + ct.narrow (static_cast (i), '\0'); + } +#endif + + // On POSIX ignore SIGPIPE which is signaled to a pipe-writing process if + // the pipe reading end is closed. Note that by default this signal + // terminates a process. Also note that there is no way to disable this + // behavior on a file descriptor basis or for the write() function call. + // +#ifndef _WIN32 + if (signal (SIGPIPE, SIG_IGN) == SIG_ERR) + fail << "unable to ignore broken pipe (SIGPIPE) signal: " + << system_error (errno, generic_category ()); // Sanitize. +#endif + + options ops; + scheduler sched; + + try + { + // Parse the command line. + // + strings cmd_vars; + string args; + uint16_t verbosity; + { + cmdline r (parse_cmdline (trace, argc, argv, ops)); + cmd_vars = move (r.cmd_vars); + args = move (r.buildspec); + verbosity = r.verbosity; + } + // Handle --build2-metadata (see also buildfile). // #ifndef BUILD2_BOOTSTRAP @@ -660,7 +688,7 @@ main (int argc, char* argv[]) // Initialize the diagnostics state. // - init_diag (verbosity (), + init_diag (verbosity, ops.silent (), (ops.progress () ? optional (true) : ops.no_progress () ? optional (false) : nullopt), @@ -817,7 +845,7 @@ main (int argc, char* argv[]) // below). // unique_ptr pctx; - auto new_context = [&pctx, &sched, &mutexes, &fcache, &cmd_vars] + auto new_context = [&ops, &pctx, &sched, &mutexes, &fcache, &cmd_vars] { pctx = nullptr; // Free first. pctx.reset (new context (sched, @@ -1674,7 +1702,7 @@ main (int argc, char* argv[]) action a (mid, pre_oid, oid); { - result_printer p (tgs); + result_printer p (ops, tgs); uint16_t diag (ops.structured_result () ? 0 : 1); if (mif->match != nullptr) @@ -1701,7 +1729,7 @@ main (int argc, char* argv[]) action a (mid, oid, oif->outer_id); { - result_printer p (tgs); + result_printer p (ops, tgs); uint16_t diag (ops.structured_result () ? 0 : 2); if (mif->match != nullptr) @@ -1729,7 +1757,7 @@ main (int argc, char* argv[]) action a (mid, post_oid, oid); { - result_printer p (tgs); + result_printer p (ops, tgs); uint16_t diag (ops.structured_result () ? 0 : 1); if (mif->match != nullptr) -- cgit v1.1