diff options
Diffstat (limited to 'bpkg/bpkg.cxx')
-rw-r--r-- | bpkg/bpkg.cxx | 516 |
1 files changed, 417 insertions, 99 deletions
diff --git a/bpkg/bpkg.cxx b/bpkg/bpkg.cxx index ffc91ab..b5eaf7d 100644 --- a/bpkg/bpkg.cxx +++ b/bpkg/bpkg.cxx @@ -1,15 +1,40 @@ // file : bpkg/bpkg.cxx -*- C++ -*- // license : MIT; see accompanying LICENSE file -#ifndef _WIN32 -# include <signal.h> // signal() -#endif +#include <bpkg/bpkg.hxx> +#include <limits> +#include <cstdlib> // getenv() +#include <cstring> // strcmp() #include <iostream> #include <exception> // set_terminate(), terminate_handler #include <type_traits> // enable_if, is_base_of -#include <libbutl/backtrace.mxx> // backtrace() +#include <libbutl/backtrace.hxx> // backtrace() + +// Embedded build system driver. +// +#include <libbuild2/types.hxx> +#include <libbuild2/utility.hxx> +#include <libbuild2/module.hxx> + +#include <libbuild2/b-options.hxx> +#include <libbuild2/b-cmdline.hxx> + +#include <libbuild2/dist/init.hxx> +#include <libbuild2/test/init.hxx> +#include <libbuild2/config/init.hxx> +#include <libbuild2/install/init.hxx> + +#include <libbuild2/in/init.hxx> +#include <libbuild2/bin/init.hxx> +#include <libbuild2/c/init.hxx> +#include <libbuild2/cc/init.hxx> +#include <libbuild2/cxx/init.hxx> +#include <libbuild2/version/init.hxx> + +#include <libbuild2/bash/init.hxx> +#include <libbuild2/cli/init.hxx> #include <bpkg/types.hxx> #include <bpkg/utility.hxx> @@ -22,7 +47,11 @@ #include <bpkg/help.hxx> #include <bpkg/cfg-create.hxx> +#include <bpkg/cfg-info.hxx> +#include <bpkg/cfg-link.hxx> +#include <bpkg/cfg-unlink.hxx> +#include <bpkg/pkg-bindist.hxx> #include <bpkg/pkg-build.hxx> #include <bpkg/pkg-checkout.hxx> #include <bpkg/pkg-clean.hxx> @@ -52,6 +81,164 @@ using namespace bpkg; namespace bpkg { + // 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 + build2_terminate (bool trace) + { + if (!trace) + set_terminate (default_terminate); + + std::terminate (); + } + + strings build2_cmd_vars; + build2::scheduler build2_sched; + build2::global_mutexes build2_mutexes; + build2::file_cache build2_fcache; + + static const char* build2_argv0; + + void + build2_init (const common_options& co) + { + try + { + using namespace build2; + using build2::fail; + using build2::endf; + + build2::tracer trace ("build2_init"); + + // Parse --build-option values as the build2 driver command line. + // + // With things like verbosity, progress, etc., we use values from + // --build-option if specified, falling back to equivalent bpkg values + // otherwise. + // + b_options bo; + b_cmdline bc; + { + small_vector<char*, 1> argv {const_cast<char*> (build2_argv0)}; + + if (size_t n = co.build_option ().size ()) + { + argv.reserve (n + 1); + + for (const string& a: co.build_option ()) + argv.push_back (const_cast<char*> (a.c_str ())); + } + + // Note that this function also parses the default options files and + // gets/sets the relevant environment variables. + // + // For now we use the same default verbosity as us (equivalent to + // start_b() with verb_b::normal). + // + bc = parse_b_cmdline (trace, + static_cast<int> (argv.size ()), argv.data (), + bo, + bpkg::verb, + co.jobs_specified () ? co.jobs () : 0); + + if (!bc.buildspec.empty ()) + fail << "argument specified with --build-option"; + + if (bo.help () || bo.version ()) + fail << "--help or --version specified with --build-option"; + + // Make sure someone didn't specify a non-global override with + // --build-option, which messes our global/package-specific config + // variable split. + // + for (const string& v: bc.cmd_vars) + { + if (v[0] != '!') + fail << "non-global configuration variable '" << v + << "' specified with --build-option"; + } + } + + build2_cmd_vars = move (bc.cmd_vars); + + init_diag (bc.verbosity, + bo.silent (), + (bc.progress ? bc.progress : + co.progress () ? optional<bool> (true) : + co.no_progress () ? optional<bool> (false) : nullopt), + (bc.diag_color ? bc.diag_color : + co.diag_color () ? optional<bool> (true) : + co.no_diag_color () ? optional<bool> (false) : nullopt), + bo.no_line (), + bo.no_column (), + bpkg::stderr_term.has_value ()); + + // Also note that we now use this in pkg_configure(), but serial-stop + // is good for it as well. + // + init (&build2_terminate, + build2_argv0, + false /* serial_stop */, + bc.mtime_check, + bc.config_sub, + bc.config_guess); + + load_builtin_module (&build2::config::build2_config_load); + load_builtin_module (&build2::dist::build2_dist_load); + load_builtin_module (&build2::test::build2_test_load); + load_builtin_module (&build2::install::build2_install_load); + + load_builtin_module (&build2::bin::build2_bin_load); + load_builtin_module (&build2::cc::build2_cc_load); + load_builtin_module (&build2::c::build2_c_load); + load_builtin_module (&build2::cxx::build2_cxx_load); + load_builtin_module (&build2::version::build2_version_load); + load_builtin_module (&build2::in::build2_in_load); + + load_builtin_module (&build2::bash::build2_bash_load); + load_builtin_module (&build2::cli::build2_cli_load); + + // Note that while all we need is serial execution (all we do is load), + // in the process we may need to update some build system modules (while + // we only support built-in and standard pre-installed modules here, we + // may need to build the latter during development). At the same time, + // this is an unlikely case and starting a parallel scheduler is not + // cheap. So what we will do is start a parallel scheduler pre-tuned to + // serial execution, which is relatively cheap. The module building + // logic will then re-tune it to parallel if and when necessary. + // + // Note that we now also use this in pkg_configure() where we re-tune + // the scheduler (it may already have been initialized as part of the + // package skeleton work). + // + build2_sched.startup (1 /* max_active */, + 1 /* init_active */, + bc.max_jobs, + bc.jobs * bo.queue_depth (), + bc.max_stack, + bc.jobs); + + build2_mutexes.init (build2_sched.shard_size ()); + build2_fcache.init (bc.fcache_compress); + } + catch (const build2::failed&) + { + throw bpkg::failed (); // Assume the diagnostics has already been issued. + } + } + // Deduce the default options files and the directory to start searching // from based on the command line options and arguments. // @@ -141,6 +328,17 @@ namespace bpkg main (int argc, char* argv[]); } +// Note that pkg-build command supports multiple configurations and +// initializes multiple temporary directories itself. This function is, +// however, required since pkg_build_options::directory() returns a vector and +// the below template function cannot be used. +// +static inline const dir_path& +cfg_dir (const pkg_build_options*) +{ + return empty_dir_path; +} + // Get -d|--directory value if the option class O has it and empty path // otherwise. Note that for some commands (like rep-info) that allow // specifying empty path, the returned value is a string, not a dir_path. @@ -152,6 +350,20 @@ cfg_dir (const O* o) -> decltype(o->directory ()) {return o->directory ();} static inline auto cfg_dir (...) -> const dir_path& {return empty_dir_path;} +// Command line arguments starting position. +// +// We want the positions of the command line arguments to be after the default +// options files (parsed in init()). 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. +// +static const size_t args_pos (numeric_limits<size_t>::max () / 2); + // Initialize the command option class O with the common options and then // parse the rest of the command line placing non-option arguments to args. // Once this is done, use the "final" values of the common options to do @@ -161,11 +373,14 @@ template <typename O> static O init (const common_options& co, cli::group_scanner& scan, - strings& args, + strings& args, cli::vector_scanner& args_scan, const char* cmd, bool keep_sep, bool tmp) { + using bpkg::optional; + using bpkg::getenv; + tracer trace ("init"); O o; @@ -178,6 +393,11 @@ init (const common_options& co, { if (opt) { + // Parse the next chunk of options until we reach an argument (or eos). + // + if (o.parse (scan) && !scan.more ()) + break; + // If we see first "--", then we are done parsing options. // if (strcmp (scan.peek (), "--") == 0) @@ -189,11 +409,6 @@ init (const common_options& co, continue; } - // Parse the next chunk of options until we reach an argument (or eos). - // - if (o.parse (scan)) - continue; - // Fall through. } @@ -214,6 +429,11 @@ init (const common_options& co, } } + // Carry over the positions of the arguments. In particular, this can be + // used to get the max position for the options. + // + args_scan.reset (0, scan.position ()); + // Note that the diagnostics verbosity level can only be calculated after // default options are loaded and merged (see below). Thus, to trace the // default options files search, we refer to the verbosity level specified @@ -226,18 +446,44 @@ init (const common_options& co, : o.V () ? 3 : o.v () ? 2 : o.quiet () ? 0 : 1; }; - // Handle default options files. + // Load the default options files, unless --no-default-options is specified + // on the command line or the BPKG_DEF_OPT environment variable is set to a + // value other than 'true' or '1'. + // + optional<string> env_def (getenv ("BPKG_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 (!o.no_default_options ()); + // Note: don't need to use group_scaner (no arguments in options files). // - if (!o.no_default_options ()) // Command line option. + if (cmd_def && (!env_def || *env_def == "true" || *env_def == "1")) try { - bpkg::optional<dir_path> extra; + optional<dir_path> extra; if (o.default_options_specified ()) + { extra = o.default_options (); - o = merge_options ( + // Note that load_default_options() expects absolute and normalized + // directory. + // + try + { + if (extra->relative ()) + extra->complete (); + + extra->normalize (); + } + catch (const invalid_path& e) + { + fail << "invalid --default-options value " << e.path; + } + } + + default_options<O> dos ( load_default_options<O, cli::argv_file_scanner, cli::unknown_mode> ( nullopt /* sys_dir */, path::home_directory (), @@ -252,8 +498,73 @@ init (const common_options& co, else trace << "loading " << (r ? "remote " : "local ") << f; } - }), - o); + }, + "--options-file", + args_pos, + 1024)); + + // Verify common options. + // + // Also merge the --*/--no-* options, overriding a less specific flag with + // a more specific. + // + // + optional<bool> progress, diag_color; + auto merge_no = [&progress, &diag_color] ( + const O& o, + const default_options_entry<O>* e = nullptr) + { + { + if (o.progress () && o.no_progress ()) + { + diag_record dr; + (e != nullptr ? dr << fail (e->file) : dr << fail) + << "both --progress and --no-progress specified"; + } + + if (o.progress ()) + progress = true; + else if (o.no_progress ()) + progress = false; + } + + { + if (o.diag_color () && o.no_diag_color ()) + { + diag_record dr; + (e != nullptr ? dr << fail (e->file) : dr << fail) + << "both --diag-color and --no-diag-color specified"; + } + + if (o.diag_color ()) + diag_color = true; + else if (o.no_diag_color ()) + diag_color = false; + } + }; + + for (const default_options_entry<O>& e: dos) + merge_no (e.options, &e); + + merge_no (o); + + o = merge_options (dos, o); + + if (progress) + { + o.progress (*progress); + o.no_progress (!*progress); + } + + if (diag_color) + { + o.diag_color (*diag_color); + o.no_diag_color (!*diag_color); + } + } + catch (const invalid_argument& e) + { + fail << "unable to load default options files: " << e; } catch (const pair<path, system_error>& e) { @@ -265,6 +576,12 @@ init (const common_options& co, fail << "unable to obtain home directory: " << e; } + // Propagate disabling of the default options files to the potential nested + // invocations. + // + if (!cmd_def && (!env_def || *env_def != "0")) + setenv ("BPKG_DEF_OPT", "0"); + // Global initializations. // @@ -277,21 +594,9 @@ init (const common_options& co, if (tmp) init_tmp (dir_path (cfg_dir (&o))); - return o; -} - -// 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; + keep_tmp = o.keep_tmp (); -void -custom_terminate () -{ - *diag_stream << backtrace (); - - if (default_terminate != nullptr) - default_terminate (); + return o; } int bpkg:: @@ -302,45 +607,33 @@ try default_terminate = set_terminate (custom_terminate); - stderr_term = fdterm (stderr_fd ()); - exec_dir = path (argv[0]).directory (); - - // This is a little hack to make our 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 + if (fdterm (stderr_fd ())) { - string mp; - if (optional<string> p = getenv ("PATH")) - { - mp = move (*p); - mp += ';'; - } - mp += "/bin"; + stderr_term = std::getenv ("TERM"); - setenv ("PATH", mp); - } + stderr_term_color = +#ifdef _WIN32 + // For now we disable color on Windows since it's unclear if/where/how + // it is supported. Maybe one day someone will figure this out. + // + false +#else + // This test was lifted from GCC (Emacs shell sets TERM=dumb). + // + *stderr_term != nullptr && strcmp (*stderr_term, "dumb") != 0 #endif + ; + } + + exec_dir = path (argv[0]).directory (); + build2_argv0 = argv[0]; - // 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. + // Note that this call sets PATH to include our baseutils /bin on Windows + // and ignores SIGPIPE. // -#ifndef _WIN32 - if (signal (SIGPIPE, SIG_IGN) == SIG_ERR) - fail << "unable to ignore broken pipe (SIGPIPE) signal: " - << system_error (errno, generic_category ()); // Sanitize. -#endif + build2::init_process (); - argv_file_scanner argv_scan (argc, argv, "--options-file"); + argv_file_scanner argv_scan (argc, argv, "--options-file", false, args_pos); group_scanner scan (argv_scan); // First parse common options and --version/--help. @@ -353,21 +646,22 @@ try cout << "bpkg " << BPKG_VERSION_ID << endl << "libbpkg " << LIBBPKG_VERSION_ID << endl << "libbutl " << LIBBUTL_VERSION_ID << endl + << "host " << host_triplet << endl << "Copyright (c) " << BPKG_COPYRIGHT << "." << endl << "This is free software released under the MIT license." << endl; return 0; } - strings argsv; // To be filled by parse() above. - vector_scanner vect_args (argsv); - group_scanner args (vect_args); + strings argsv; // To be filled by init() above. + vector_scanner scanv (argsv); + group_scanner args (scanv); const common_options& co (o); if (o.help ()) return help (init<help_options> (co, scan, - argsv, + argsv, scanv, "help", false /* keep_sep */, false /* tmp */), @@ -400,7 +694,7 @@ try { ho = init<help_options> (co, scan, - argsv, + argsv, scanv, "help", false /* keep_sep */, false /* tmp */); @@ -449,6 +743,7 @@ try // r = pkg_verify (init<pkg_verify_options> (co, // scan, // argsv, + // scanv, // "pkg-verify", // false /* keep_sep */, // true /* tmp */), @@ -466,6 +761,7 @@ try r = NP##CMD (init<NP##CMD##_options> (co, \ scan, \ argsv, \ + scanv, \ SP#CMD, \ SEP, \ TMP), \ @@ -479,51 +775,73 @@ try #define CFG_COMMAND(CMD, TMP) COMMAND_IMPL(cfg_, "cfg-", CMD, false, TMP) CFG_COMMAND (create, false); // Temp dir initialized manually. + CFG_COMMAND (info, true); + CFG_COMMAND (link, true); + CFG_COMMAND (unlink, true); // pkg-* commands // -#define PKG_COMMAND(CMD, SEP) COMMAND_IMPL(pkg_, "pkg-", CMD, SEP, true) +#define PKG_COMMAND(CMD, SEP, TMP) COMMAND_IMPL(pkg_, "pkg-", CMD, SEP, TMP) // These commands need the '--' separator to be kept in args. // - PKG_COMMAND (build, true); - PKG_COMMAND (clean, true); - PKG_COMMAND (configure, true); - PKG_COMMAND (install, true); - PKG_COMMAND (test, true); - PKG_COMMAND (uninstall, true); - PKG_COMMAND (update, true); - - PKG_COMMAND (checkout, false); - PKG_COMMAND (disfigure, false); - PKG_COMMAND (drop, false); - PKG_COMMAND (fetch, false); - PKG_COMMAND (purge, false); - PKG_COMMAND (status, false); - PKG_COMMAND (unpack, false); - PKG_COMMAND (verify, false); + PKG_COMMAND (bindist, true, true); + PKG_COMMAND (build, true, false); + PKG_COMMAND (clean, true, true); + PKG_COMMAND (configure, true, true); + PKG_COMMAND (install, true, true); + PKG_COMMAND (test, true, true); + PKG_COMMAND (uninstall, true, true); + PKG_COMMAND (update, true, true); + + PKG_COMMAND (checkout, false, true); + PKG_COMMAND (disfigure, false, true); + PKG_COMMAND (drop, false, true); + PKG_COMMAND (fetch, false, true); + PKG_COMMAND (purge, false, true); + PKG_COMMAND (status, false, true); + PKG_COMMAND (unpack, false, true); + PKG_COMMAND (verify, false, true); // rep-* commands // -#define REP_COMMAND(CMD) COMMAND_IMPL(rep_, "rep-", CMD, false, true) +#define REP_COMMAND(CMD, TMP) COMMAND_IMPL(rep_, "rep-", CMD, false, TMP) - REP_COMMAND (add); - REP_COMMAND (create); - REP_COMMAND (fetch); - REP_COMMAND (info); - REP_COMMAND (list); - REP_COMMAND (remove); + REP_COMMAND (add, true); + REP_COMMAND (create, true); + REP_COMMAND (fetch, true); + REP_COMMAND (info, false); + REP_COMMAND (list, true); + REP_COMMAND (remove, true); assert (false); fail << "unhandled command"; } - catch (const failed&) + catch (const failed& e) { - r = 1; + r = e.code; break; } - clean_tmp (true /* ignore_error */); + // Shutdown the build2 scheduler if it was initialized. + // + if (build2_sched.started ()) + build2_sched.shutdown (); + + if (!keep_tmp) + { + clean_tmp (true /* ignore_error */); + } + else if (verb > 1) + { + for (const auto& d: tmp_dirs) + { + const dir_path& td (d.second); + + if (exists (td)) + info << "keeping temporary directory " << td; + } + } if (r != 0) return r; @@ -541,22 +859,22 @@ try return 0; } -catch (const failed&) +catch (const failed& e) { - return 1; // Diagnostics has already been issued. + return e.code; // Diagnostics has already been issued. } catch (const cli::exception& e) { error << e; return 1; } -/* +#if 0 catch (const std::exception& e) { error << e; return 1; } -*/ +#endif int main (int argc, char* argv[]) |