aboutsummaryrefslogtreecommitdiff
path: root/bpkg/bpkg.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'bpkg/bpkg.cxx')
-rw-r--r--bpkg/bpkg.cxx376
1 files changed, 315 insertions, 61 deletions
diff --git a/bpkg/bpkg.cxx b/bpkg/bpkg.cxx
index 1abe037..b5eaf7d 100644
--- a/bpkg/bpkg.cxx
+++ b/bpkg/bpkg.cxx
@@ -1,16 +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>
@@ -27,6 +51,7 @@
#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>
@@ -56,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.
//
@@ -281,9 +464,26 @@ init (const common_options& co,
{
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 (),
@@ -301,8 +501,66 @@ init (const common_options& co,
},
"--options-file",
args_pos,
- 1024),
- o);
+ 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)
{
@@ -336,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;
-
-void
-custom_terminate ()
-{
- *diag_stream << backtrace ();
+ keep_tmp = o.keep_tmp ();
- if (default_terminate != nullptr)
- default_terminate ();
+ return o;
}
int bpkg::
@@ -361,43 +607,31 @@ 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
+ ;
+ }
- // 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.
+ exec_dir = path (argv[0]).directory ();
+ build2_argv0 = argv[0];
+
+ // 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", false, args_pos);
group_scanner scan (argv_scan);
@@ -412,6 +646,7 @@ 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;
@@ -550,13 +785,14 @@ try
// These commands need the '--' separator to be kept in args.
//
- 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 (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);
@@ -587,7 +823,25 @@ try
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;