aboutsummaryrefslogtreecommitdiff
path: root/bpkg/bpkg.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'bpkg/bpkg.cxx')
-rw-r--r--bpkg/bpkg.cxx516
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[])