aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2023-04-12 07:34:39 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2023-04-15 09:57:58 +0200
commit5267c62f8c1f483c5348d4a2e498088a41c2e945 (patch)
tree9592af2b2a374e9d5924dd7c859e4069ec75c4fb
parent306ad185e8d8f50923316bdd1b68ef9a3b1e50e6 (diff)
Implement in-process package configuration
-rw-r--r--bpkg/bpkg.cxx26
-rw-r--r--bpkg/bpkg.hxx35
-rw-r--r--bpkg/buildfile5
-rw-r--r--bpkg/package-skeleton.cxx15
-rw-r--r--bpkg/pkg-build.cli9
-rw-r--r--bpkg/pkg-build.cxx124
-rw-r--r--bpkg/pkg-checkout.cxx1
-rw-r--r--bpkg/pkg-configure.cxx402
-rw-r--r--bpkg/pkg-configure.hxx26
-rw-r--r--bpkg/utility.hxx5
-rw-r--r--bpkg/utility.txx34
11 files changed, 617 insertions, 65 deletions
diff --git a/bpkg/bpkg.cxx b/bpkg/bpkg.cxx
index 21cbefc..4af4140 100644
--- a/bpkg/bpkg.cxx
+++ b/bpkg/bpkg.cxx
@@ -1,6 +1,8 @@
// file : bpkg/bpkg.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
+#include <bpkg/bpkg.hxx>
+
#include <limits>
#include <cstdlib> // getenv()
#include <cstring> // strcmp()
@@ -15,9 +17,6 @@
#include <libbuild2/types.hxx>
#include <libbuild2/utility.hxx>
#include <libbuild2/module.hxx>
-#include <libbuild2/context.hxx>
-#include <libbuild2/scheduler.hxx>
-#include <libbuild2/file-cache.hxx>
#include <libbuild2/b-options.hxx>
#include <libbuild2/b-cmdline.hxx>
@@ -34,6 +33,9 @@
#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>
@@ -109,8 +111,6 @@ namespace bpkg
static const char* build2_argv0;
- // Use build2_sched.started() to check if already initialized.
- //
void
build2_init (const common_options& co)
{
@@ -174,13 +174,12 @@ namespace bpkg
bo.no_column (),
bpkg::stderr_term.has_value ());
- // Note that we pretend to be in the serial-stop mode even though we may
- // build build system modules in parallel in order to get better
- // diagnostics for the common case.
+ // Also note that we now use this in pkg_configure(), but serial-stop
+ // is good for it as well.
//
init (&build2_terminate,
build2_argv0,
- true /* serial_stop */,
+ false /* serial_stop */,
bc.mtime_check,
bc.config_sub,
bc.config_guess);
@@ -197,6 +196,9 @@ namespace bpkg
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
@@ -206,6 +208,10 @@ namespace bpkg
// 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,
@@ -218,7 +224,7 @@ namespace bpkg
}
catch (const build2::failed&)
{
- throw failed (); // Assume the diagnostics has already been issued.
+ throw bpkg::failed (); // Assume the diagnostics has already been issued.
}
}
diff --git a/bpkg/bpkg.hxx b/bpkg/bpkg.hxx
new file mode 100644
index 0000000..1ebbf85
--- /dev/null
+++ b/bpkg/bpkg.hxx
@@ -0,0 +1,35 @@
+// file : bpkg/bpkg.hxx -*- C++ -*-
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BPKG_BPKG_HXX
+#define BPKG_BPKG_HXX
+
+// Embedded build system driver.
+//
+#include <libbuild2/context.hxx>
+#include <libbuild2/scheduler.hxx>
+#include <libbuild2/file-cache.hxx>
+
+#include <bpkg/types.hxx>
+#include <bpkg/utility.hxx>
+
+#include <bpkg/common-options.hxx>
+
+namespace bpkg
+{
+ // These are initialized by build2_init().
+ //
+ extern strings build2_cmd_vars;
+ extern build2::scheduler build2_sched;
+ extern build2::global_mutexes build2_mutexes;
+ extern build2::file_cache build2_fcache;
+
+ // Use build2_sched.started() to check if already initialized. Note that the
+ // scheduler is pre-tuned for serial execution.
+ //
+ //
+ void
+ build2_init (const common_options&);
+}
+
+#endif // BPKG_BPKG_HXX
diff --git a/bpkg/buildfile b/bpkg/buildfile
index 6bd7252..19fd4dc 100644
--- a/bpkg/buildfile
+++ b/bpkg/buildfile
@@ -12,7 +12,7 @@ import libs = build2%lib{build2}
# NOTE: see also module loading in bpkg.cxx if adding anything here.
#
-for m: bin c cc cxx in version
+for m: bash bin c cc cli cxx in version
import libs += build2%lib{build2-$m}
import libs += libbpkg%lib{bpkg}
@@ -98,6 +98,9 @@ for t: cxx{**.test...}
# Build options.
#
+
+#cxx.poptions += -DBPKG_OUTPROC_CONFIGURE
+
obj{utility}: cxx.poptions += \
"-DBPKG_EXE_PREFIX=\"$bin.exe.prefix\"" \
"-DBPKG_EXE_SUFFIX=\"$bin.exe.suffix\"" \
diff --git a/bpkg/package-skeleton.cxx b/bpkg/package-skeleton.cxx
index 899fb48..afce66c 100644
--- a/bpkg/package-skeleton.cxx
+++ b/bpkg/package-skeleton.cxx
@@ -16,12 +16,14 @@
#include <libbuild2/context.hxx>
#include <libbuild2/variable.hxx>
#include <libbuild2/operation.hxx>
+#include <libbuild2/diagnostics.hxx>
#include <libbuild2/lexer.hxx>
#include <libbuild2/parser.hxx>
#include <libbuild2/config/utility.hxx>
+#include <bpkg/bpkg.hxx>
#include <bpkg/package.hxx>
#include <bpkg/database.hxx>
#include <bpkg/manifest-utility.hxx>
@@ -31,19 +33,6 @@ using namespace butl;
namespace bpkg
{
- // These are defined in bpkg.cxx and initialized by build2_init().
- //
- extern strings build2_cmd_vars;
- extern build2::scheduler build2_sched;
- extern build2::global_mutexes build2_mutexes;
- extern build2::file_cache build2_fcache;
-
- void
- build2_init (const common_options&);
-}
-
-namespace bpkg
-{
// Check whether the specified configuration variable override has a project
// variable (i.e., its name starts with config.<project>). If the last
// argument is not NULL, then set it to the length of the variable portion.
diff --git a/bpkg/pkg-build.cli b/bpkg/pkg-build.cli
index 28e4fa2..6aee9b0 100644
--- a/bpkg/pkg-build.cli
+++ b/bpkg/pkg-build.cli
@@ -119,10 +119,11 @@ namespace bpkg
Additional configuration variables (<cfg-var>), if any, should be
specified before packages (<pkg-spec>) and should be separated with
\cb{--}. Such variables are effective only when configuring and only for
- packages that were explicitly specified on the command line (they can
- also be specified to only apply to specific packages using the argument
- grouping mechanism discussed below). See \l{bpkg-pkg-configure(1)} for
- more information on configuration variables.
+ packages that were explicitly specified on the command line (unless
+ global overrides). They can also be specified to only apply to specific
+ packages using the argument grouping mechanism discussed below. See
+ \l{bpkg-pkg-configure(1)} for more information on configuration
+ variables.
By default a package that is specified explicitly on the command line is
built to \i{hold}: it will not be considered for automatic removal if it
diff --git a/bpkg/pkg-build.cxx b/bpkg/pkg-build.cxx
index 2484880..4f5b423 100644
--- a/bpkg/pkg-build.cxx
+++ b/bpkg/pkg-build.cxx
@@ -1245,7 +1245,13 @@ namespace bpkg
if (a.find ('=') == string::npos)
fail << "unexpected group argument '" << a << "'";
- cvs.push_back (move (trim (a)));
+ trim (a);
+
+ if (a[0] == '!')
+ fail << "global override in package-specific configuration "
+ << "variable '" << a << "'";
+
+ cvs.push_back (move (a));
}
}
@@ -5356,12 +5362,26 @@ namespace bpkg
struct configure_package
{
reference_wrapper<build_package> pkg;
- configure_prerequisites_result res; // Unused for system package.
+
+ // These are unused for system packages.
+ //
+ configure_prerequisites_result res;
+ build2::variable_overrides ovrs;
};
vector<configure_package> configure_packages;
-
configure_packages.reserve (build_pkgs.size ());
+ // While at it also collect global configuration variable overrides from
+ // each configure_prerequisites_result::config_variables and merge them
+ // into configure_global_vars.
+ //
+ // @@ TODO: Note that the current global override semantics is quite
+ // broken in that we don't force reconfiguration of all the packages.
+ //
+#ifndef BPKG_OUTPROC_CONFIGURE
+ strings configure_global_vars;
+#endif
+
// Return the "would be" state of packages that would be configured
// by this stage.
//
@@ -5557,11 +5577,97 @@ namespace bpkg
}
t.commit ();
+
+ if (verb >= 5 && !simulate && !cpr.config_variables.empty ())
+ {
+ diag_record dr (trace);
+
+ dr << sp->name << pdb << " configuration variables:";
+
+ for (const string& cv: cpr.config_variables)
+ dr << "\n " << cv;
+ }
+
+ if (!simulate)
+ {
+#ifndef BPKG_OUTPROC_CONFIGURE
+ auto& gvs (configure_global_vars);
+
+ // Note that we keep global overrides in cpr.config_variables for
+ // diagnostics and skip them in var_override_function below.
+ //
+ for (const string& v: cpr.config_variables)
+ {
+ // Each package should have exactly the same set of global
+ // overrides by construction since we don't allow package-
+ // specific global overrides.
+ //
+ if (v[0] == '!')
+ {
+ if (find (gvs.begin (), gvs.end (), v) == gvs.end ())
+ gvs.push_back (v);
+ }
+ }
+#endif
+ // Add config.config.disfigure unless already disfigured (see the
+ // high-level pkg_configure() version for background).
+ //
+ if (ap == nullptr || !p.disfigure)
+ {
+ cpr.config_variables.push_back (
+ "config.config.disfigure='config." + sp->name.variable () + "**'");
+ }
+ }
}
- configure_packages.push_back (configure_package {p, move (cpr)});
+ configure_packages.push_back (configure_package {p, move (cpr), {}});
}
+ // Reuse the build state to avoid reloading the dependencies over and over
+ // again. This is a valid optimization since we are configuring in the
+ // dependency-dependent order.
+ //
+ unique_ptr<build2::context> configure_ctx;
+
+#ifndef BPKG_OUTPROC_CONFIGURE
+ if (!simulate)
+ {
+ using build2::context;
+ using build2::variable_override;
+
+ function<context::var_override_function> vof (
+ [&configure_packages] (context& ctx, size_t& i)
+ {
+ for (configure_package& cp: configure_packages)
+ {
+ for (const string& v: cp.res.config_variables)
+ {
+ if (v[0] == '!') // Skip global overrides (see above).
+ continue;
+
+ pair<char, variable_override> p (
+ ctx.parse_variable_override (v, i++, false /* buildspec */));
+
+ variable_override& vo (p.second);
+
+ // @@ TODO: put absolute scope overrides into global_vars.
+ //
+ assert (!(p.first == '!' || (vo.dir && vo.dir->absolute ())));
+
+ cp.ovrs.push_back (move (vo));
+ }
+ }
+ });
+
+ configure_ctx = pkg_configure_context (
+ o, move (configure_global_vars), vof);
+
+ // Only global in configure_global_vars.
+ //
+ assert (configure_ctx->var_overrides.empty ());
+ }
+#endif
+
if (progress)
{
prog_i = 0;
@@ -5606,7 +5712,8 @@ namespace bpkg
t,
sp,
move (cp.res),
- p.disfigure,
+ configure_ctx,
+ cp.ovrs,
simulate);
}
else // Dependent.
@@ -5616,7 +5723,8 @@ namespace bpkg
t,
sp,
move (cp.res),
- false /* disfigured */,
+ configure_ctx,
+ cp.ovrs,
simulate);
}
}
@@ -5643,6 +5751,10 @@ namespace bpkg
}
}
+#ifndef BPKG_OUTPROC_CONFIGURE
+ configure_ctx.reset (); // Free.
+#endif
+
// Clear the progress if shown.
//
if (progress)
diff --git a/bpkg/pkg-checkout.cxx b/bpkg/pkg-checkout.cxx
index b4d93f0..87579cc 100644
--- a/bpkg/pkg-checkout.cxx
+++ b/bpkg/pkg-checkout.cxx
@@ -15,7 +15,6 @@
#include <bpkg/pkg-purge.hxx>
#include <bpkg/pkg-verify.hxx>
-#include <bpkg/pkg-configure.hxx>
using namespace std;
using namespace butl;
diff --git a/bpkg/pkg-configure.cxx b/bpkg/pkg-configure.cxx
index 59a6af0..d6c5446 100644
--- a/bpkg/pkg-configure.cxx
+++ b/bpkg/pkg-configure.cxx
@@ -3,6 +3,17 @@
#include <bpkg/pkg-configure.hxx>
+#include <libbuild2/types.hxx>
+#include <libbuild2/utility.hxx>
+#include <libbuild2/diagnostics.hxx>
+
+#include <libbuild2/file.hxx>
+#include <libbuild2/scope.hxx>
+#include <libbuild2/operation.hxx>
+#include <libbuild2/config/operation.hxx>
+
+#include <bpkg/bpkg.hxx> // build2_init(), etc
+
#include <bpkg/package.hxx>
#include <bpkg/package-odb.hxx>
#include <bpkg/database.hxx>
@@ -275,6 +286,25 @@ namespace bpkg
else
od = sp->effective_out_root (pdb.config);
+ // We tried to use global overrides to recreate the original
+ // behavior of not warning about unused config.import.*
+ // variables (achived via the config.config.persist value in
+ // amalgamation). Even though it's probably misguided (we
+ // don't actually save the unused values anywhere, just
+ // don't warn about them).
+ //
+ // Can we somehow cause a clash, say if the same package
+ // comes from different configurations? Yeah, we probably
+ // can. So could add it as undermined (?), detect a clash,
+ // and "fallforward" to the correct behavior.
+ //
+ // But we can clash with an absent value -- that is, we
+ // force importing from a wrong configuration where without
+ // any import things would have been found in the same
+ // amalgamation. Maybe we could detect that (no import
+ // for the same package -- but it could be for a package
+ // we are not configuring).
+ //
vars.push_back ("config.import." + sp->name.variable () +
"='" + od.representation () + '\'');
}
@@ -345,13 +375,87 @@ namespace bpkg
move (srcs)};
}
+
+ unique_ptr<build2::context>
+ pkg_configure_context (
+ const common_options& o,
+ strings&& cmd_vars,
+ const function<build2::context::var_override_function>& var_ovr_func)
+ {
+ using namespace build2;
+
+ // Initialize the build system.
+ //
+ // Note that this takes into account --build-option and default options
+ // files (which may have global overrides and which end up in
+ // build2_cmd_vars).
+ //
+ if (!build2_sched.started ())
+ build2_init (o);
+
+ // Re-tune the scheduler for parallel execution (see build2_init()
+ // for details).
+ //
+ if (build2_sched.tuned ())
+ build2_sched.tune (0);
+
+ auto merge_cmd_vars = [&cmd_vars] () -> const strings&
+ {
+ if (cmd_vars.empty ())
+ return build2_cmd_vars;
+
+ if (!build2_cmd_vars.empty ())
+ cmd_vars.insert (cmd_vars.begin (),
+ build2_cmd_vars.begin (), build2_cmd_vars.end ());
+
+ return cmd_vars;
+ };
+
+ // Shouldn't we shared the module context with package skeleton
+ // contexts? Maybe we don't have to since we don't build modules in
+ // them concurrently (in a sence, we didn't share it when we were
+ // invoking the build system driver).
+ //
+ unique_ptr<context> ctx (
+ new context (build2_sched,
+ build2_mutexes,
+ build2_fcache,
+ false /* match_only */,
+ false /* no_external_modules */,
+ false /* dry_run */,
+ false /* no_diag_buffer */,
+ false /* keep_going */,
+ merge_cmd_vars (),
+ context::reserves {
+ 30000 /* targets */,
+ 1100 /* variables */},
+ nullptr /* module_context */,
+ nullptr /* inherited_mudules_lock */,
+ var_ovr_func));
+
+ // Set the current meta-operation once per context so that we don't reset
+ // ctx->current_on. Note that this function also sets ctx->current_mname
+ // and var_build_meta_operation on global scope.
+ //
+ ctx->current_meta_operation (config::mo_configure);
+ ctx->current_oname = string (); // default
+
+ return ctx;
+ }
+
void
pkg_configure (const common_options& o,
database& db,
transaction& t,
const shared_ptr<selected_package>& p,
configure_prerequisites_result&& cpr,
- bool disfigured,
+#ifndef BPKG_OUTPROC_CONFIGURE
+ const unique_ptr<build2::context>& pctx,
+ const build2::variable_overrides& ovrs,
+#else
+ const unique_ptr<build2::context>&,
+ const build2::variable_overrides&, // Still in cpr.config_variables.
+#endif
bool simulate)
{
tracer trace ("pkg_configure");
@@ -361,15 +465,22 @@ namespace bpkg
tracer_guard tg (db, trace);
- const dir_path& c (db.config_orig);
+#ifndef BPKG_OUTPROC_CONFIGURE
+ const dir_path& c (db.config); // Absolute.
+#else
+ const dir_path& c (db.config_orig); // Relative.
+#endif
+
dir_path src_root (p->effective_src_root (c));
// Calculate package's out_root.
//
// Note: see a version of this in pkg_configure_prerequisites().
//
+ bool external (p->external ());
+
dir_path out_root (
- p->external ()
+ external
? c / dir_path (p->name.string ())
: c / dir_path (p->name.string () + '-' + p->version.string ()));
@@ -383,28 +494,18 @@ namespace bpkg
//
if (!simulate)
{
- // Unless this package has been completely disfigured, disfigure all the
- // package configuration variables to reset all the old values to
- // defaults (all the new user/dependent/reflec values, including old
- // user, are returned by collect_config() and specified as overrides).
- // Note that this semantics must be consistent with how we load things
- // in the package skeleton during configuration negotiation.
+ // Original implementation that runs the standard build system driver.
//
- // Note also that this means we don't really use the dependent and
- // reflect sources that we save in the database. But let's keep them
- // for the completeness of information (maybe could be useful during
- // configuration reset or some such).
+ // Note that the semantics doesn't match 100%. In particular, in the
+ // in-process implementation we enter overrides with global visibility
+ // in each project instead of the amalgamation (which is probably more
+ // accurate, since we don't re-configure the amalgamation nor some
+ // dependencies which could be affected by such overrides). In a sense,
+ // we enter them as if they were specified with the special .../ scope
+ // (but not with the % project visibility -- they must still be visible
+ // in subprojects).
//
- string dvar;
- if (!disfigured)
- {
- // Note: must be quoted to preserve the pattern.
- //
- dvar = "config.config.disfigure='config.";
- dvar += p->name.variable ();
- dvar += "**'";
- }
-
+#ifdef BPKG_OUTPROC_CONFIGURE
// Form the buildspec.
//
string bspec;
@@ -422,11 +523,7 @@ namespace bpkg
try
{
- run_b (o,
- verb_b::quiet,
- cpr.config_variables,
- (!dvar.empty () ? dvar.c_str () : nullptr),
- bspec);
+ run_b (o, verb_b::quiet, cpr.config_variables, bspec);
}
catch (const failed&)
{
@@ -437,6 +534,214 @@ namespace bpkg
pkg_disfigure (o, db, t, p, true, true, false);
throw;
}
+#else
+ // Print the out-process command line in the verbose mode.
+ //
+ if (verb >= 2)
+ {
+ string bspec;
+
+ // Use path representation to get canonical trailing slash.
+ //
+ if (src_root == out_root)
+ bspec = "configure('" + out_root.representation () + "')";
+ else
+ bspec = "configure('" +
+ src_root.representation () + "'@'" +
+ out_root.representation () + "')";
+
+ print_b (o, verb_b::quiet, cpr.config_variables, bspec);
+ }
+
+ try
+ {
+ // Note: no bpkg::failed should be thrown from this block.
+ //
+ using namespace build2;
+ using build2::fail;
+ using build2::info;
+ using build2::endf;
+ using build2::location;
+
+ // The build2_init() function initializes the build system verbosity
+ // as if running with verb_b::normal while we need verb_b::quiet. So
+ // we temporarily adjust the build2 verbosity (see map_verb_b() for
+ // details).
+ //
+ auto verbg (make_guard ([ov = build2::verb] () {build2::verb = ov;}));
+ if (bpkg::verb == 1)
+ build2::verb = 0;
+
+ context& ctx (*pctx);
+
+ // Bootstrap and load the project.
+ //
+ // Note: in many ways similar to package_skeleton code.
+ //
+ scope& rs (*create_root (ctx, out_root, src_root)->second.front ());
+
+ // If we are configuring in the dependency order (as we should), then
+ // it feels like the only situation where we can end up with an
+ // already bootstrapped project is an unspecified dependency. Note
+ // that this is a hard fail since it would have been loaded without
+ // the proper configuration.
+ //
+ if (bootstrapped (rs))
+ {
+ fail << p->name << db << " loaded ahead of its dependents" <<
+ info << "likely unspecified dependency on package " << p->name;
+ }
+
+ optional<bool> altn;
+ value& v (bootstrap_out (rs, altn));
+
+ if (!v)
+ v = src_root;
+ else
+ {
+ dir_path& p (cast<dir_path> (v));
+
+ if (src_root != p)
+ {
+ // @@ Fuzzy if need this or can do as package skeleton (seeing
+ // that we know we are re-configuring).
+ //
+ ctx.new_src_root = src_root;
+ ctx.old_src_root = move (p);
+ p = src_root;
+ }
+ }
+
+ setup_root (rs, false /* forwarded */);
+
+ // Note: we already know our amalgamation.
+ //
+ bootstrap_pre (rs, altn);
+ bootstrap_src (rs, altn,
+ db.config.relative (out_root) /* amalgamation */,
+ true /* subprojects */);
+
+ create_bootstrap_outer (rs, true /* subprojects */);
+ bootstrap_post (rs);
+
+ values mparams;
+ const meta_operation_info& mif (config::mo_configure);
+ const operation_info& oif (op_default);
+
+ // Skip configure_pre() and configure_operation_pre() calls since we
+ // don't pass any parameteres and pass default operation. We also know
+ // that op_default has no pre/post operations, naturally.
+
+ // Find the root buildfile. Note that the implied buildfile logic does
+ // not apply (our target is the project root directory).
+ //
+ optional<path> bf (find_buildfile (src_root, src_root, altn));
+
+ if (!bf)
+ fail << "no buildfile in " << src_root;
+
+ // Enter project-wide overrides.
+ //
+ // Note that the use of the root scope as amalgamation makes sure
+ // scenarious like below work correctly (see above for background).
+ //
+ // bpkg create -d cfg cc config.cc.coptions=-Wall
+ // bpkg build { config.cc.coptions+=-g }+ libfoo
+ // { config.cc.coptions+=-O }+ libbar
+ //
+ ctx.enter_project_overrides (rs, out_root, ovrs, &rs);
+
+ // The goal here is to be more or less semantically equivalent to
+ // configuring several projects at once. Except that here we have
+ // interleaving load/match instead of first all load then all
+ // match. But presumably this shouldn't be a problem (we can already
+ // have match interrupted by load and the "island append" requirement
+ // should hold here as well).
+ //
+ // Note that either way we will be potentially re-matching the same
+ // dependency targets multiple times (see build2::configure_execute()
+ // for details).
+ //
+ const path_name bsn ("<buildspec>");
+ const location loc (bsn, 0, 0);
+
+ // out_root/dir{./}
+ //
+ target_key tk {
+ &dir::static_type,
+ &out_root,
+ &empty_dir_path,
+ &empty_string,
+ nullopt};
+
+ action_targets tgs;
+ mif.load (mparams, rs, *bf, out_root, src_root, loc);
+ mif.search (mparams, rs, rs, *bf, tk, loc, tgs);
+
+ ctx.current_operation (oif, nullptr);
+ action a (ctx.current_action ());
+
+ mif.match (mparams, a, tgs, 2 /* diag */, true /* progress */);
+ mif.execute (mparams, a, tgs, 2 /* diag */, true /* progress */);
+
+ // Note: no operation_post/meta_operation_post for configure.
+
+ // Here is a tricky part: if this is a normal package, then it will be
+ // discovered as a subproject of the bpkg configuration when we load
+ // it for the first time (because they are all unpacked). However, if
+ // this is an external package, there could be no out_root directory
+ // for it in the bpkg configuration yet. As a result, we need to
+ // manually add it as a newly discovered subproject.
+ //
+ if (external)
+ {
+ scope* as (rs.parent_scope ()->root_scope ());
+ assert (as != nullptr); // No bpkg configuration?
+
+ // Kept NULL if there are no subprojects, so we may need to
+ // initialize it (see build2::bootstrap_src() for details).
+ //
+ subprojects* sp (*as->root_extra->subprojects);
+ if (sp == nullptr)
+ {
+ value& v (as->vars.assign (*ctx.var_subprojects));
+ v = subprojects {};
+ sp = *(as->root_extra->subprojects = &cast<subprojects> (v));
+ }
+
+ const project_name& n (**rs.root_extra->project);
+
+ if (sp->find (n) == sp->end ())
+ sp->emplace (n, out_root.leaf ());
+ }
+ }
+ catch (const build2::failed&)
+ {
+ // Assume the diagnostics has already been issued.
+
+ // If we failed to configure the package, make sure we revert
+ // it back to the unpacked state by running disfigure (it is
+ // valid to run disfigure on an un-configured build). And if
+ // disfigure fails as well, then the package will be set into
+ // the broken state.
+
+ // Indicate to pkg_disfigure() we are partially configured.
+ //
+ p->out_root = out_root.leaf ();
+ p->state = package_state::broken;
+
+ // Commits the transaction.
+ //
+ pkg_disfigure (o, db, t,
+ p,
+ true /* clean */,
+ true /* disfigure */,
+ false /* simulate */);
+
+
+ throw bpkg::failed ();
+ }
+#endif
p->config_variables = move (cpr.config_sources);
}
@@ -473,7 +778,46 @@ namespace bpkg
fdb,
nullptr));
- pkg_configure (o, db, t, p, move (cpr), disfigured, simulate);
+ if (!simulate)
+ {
+ // Unless this package has been completely disfigured, disfigure all the
+ // package configuration variables to reset all the old values to
+ // defaults (all the new user/dependent/reflec values, including old
+ // user, are returned by collect_config() and specified as overrides).
+ // Note that this semantics must be consistent with how we load things
+ // in the package skeleton during configuration negotiation.
+ //
+ // Note also that this means we don't really use the dependent and
+ // reflect sources that we save in the database. But let's keep them
+ // for the completeness of information (maybe could be useful during
+ // configuration reset or some such).
+ //
+ if (!disfigured)
+ {
+ // Note: must be quoted to preserve the pattern.
+ //
+ cpr.config_variables.push_back (
+ "config.config.disfigure='config." + p->name.variable () + "**'");
+ }
+ }
+
+ unique_ptr<build2::context> ctx;
+
+#ifndef BPKG_OUTPROC_CONFIGURE
+ if (!simulate)
+ ctx = pkg_configure_context (o, move (cpr.config_variables));
+#endif
+
+ pkg_configure (o,
+ db,
+ t,
+ p,
+ move (cpr),
+ ctx,
+ (ctx != nullptr
+ ? ctx->var_overrides
+ : build2::variable_overrides {}),
+ simulate);
}
shared_ptr<selected_package>
diff --git a/bpkg/pkg-configure.hxx b/bpkg/pkg-configure.hxx
index a7409b9..c4c2758 100644
--- a/bpkg/pkg-configure.hxx
+++ b/bpkg/pkg-configure.hxx
@@ -7,6 +7,9 @@
#include <libbpkg/manifest.hxx> // version
#include <libbpkg/package-name.hxx>
+#include <libbuild2/context.hxx>
+#include <libbuild2/variable.hxx> // variable_overrides
+
#include <bpkg/types.hxx>
#include <bpkg/forward.hxx> // transaction, selected_package
#include <bpkg/utility.hxx>
@@ -100,17 +103,38 @@ namespace bpkg
const function<find_database_function>&,
const function<find_package_state_function>&);
+
// Configure the package, update its state, and commit the transaction.
//
+ // This is a lower-level version meant for sharing the same build context
+ // to configure multiple packages (in the dependency order).
+ //
+ // Note: variable_overrides must include config.config.disfigure, if
+ // required.
+ //
+ // Note: expects all the non-external packages to be configured to be
+ // already unpackged (for subproject discovery).
+ //
void
pkg_configure (const common_options&,
database&,
transaction&,
const shared_ptr<selected_package>&,
configure_prerequisites_result&&,
- bool disfigured,
+ const unique_ptr<build2::context>&,
+ const build2::variable_overrides&,
bool simulate);
+ // Create a build context suitable for configuring packages.
+ //
+ unique_ptr<build2::context>
+ pkg_configure_context (
+ const common_options&,
+ strings&& cmd_vars,
+ const function<build2::context::var_override_function>& = nullptr);
+
+ // This is a higher-level version meant for configuring a single package.
+ //
// Note: loads selected packages.
//
void
diff --git a/bpkg/utility.hxx b/bpkg/utility.hxx
index bb264ba..f36185a 100644
--- a/bpkg/utility.hxx
+++ b/bpkg/utility.hxx
@@ -66,6 +66,7 @@ namespace bpkg
// <libbutl/process.hxx>
//
using butl::process_start_callback;
+ using butl::process_print_callback;
// <libbutl/filesystem.hxx>
//
@@ -254,6 +255,10 @@ namespace bpkg
process_path
search_b (const common_options&);
+ template <typename... A>
+ void
+ print_b (const common_options&, verb_b, A&&... args);
+
template <typename O, typename E, typename... A>
process
start_b (const common_options&, O&& out, E&& err, verb_b, A&&... args);
diff --git a/bpkg/utility.txx b/bpkg/utility.txx
index a21325c..33bb711 100644
--- a/bpkg/utility.txx
+++ b/bpkg/utility.txx
@@ -25,6 +25,8 @@ namespace bpkg
}
else if (verb == 1)
{
+ // NOTE: search for verb_b usage if changing anything here.
+ //
if (v != verb_b::normal)
{
ops.push_back ("-q");
@@ -57,6 +59,36 @@ namespace bpkg
ops.push_back ("--no-progress");
}
+ template <typename... A>
+ void
+ print_b (const common_options& co, verb_b v, A&&... args)
+ {
+ process_path pp (search_b (co));
+
+ small_vector<const char*, 1> ops;
+
+ // As in start_b() below.
+ //
+ string verb_arg;
+ map_verb_b (co, v, ops, verb_arg);
+
+ if (co.diag_color ())
+ ops.push_back ("--diag-color");
+
+ if (co.no_diag_color ())
+ ops.push_back ("--no-diag-color");
+
+ process_print_callback (
+ [] (const char* const args[], size_t n)
+ {
+ print_process (args, n);
+ },
+ pp,
+ ops,
+ co.build_option (),
+ forward<A> (args)...);
+ }
+
template <typename O, typename E, typename... A>
process
start_b (const common_options& co,
@@ -71,6 +103,8 @@ namespace bpkg
{
small_vector<const char*, 1> ops;
+ // NOTE: see print_b() above if changing anything here.
+ //
// NOTE: see custom versions in system_package_manager* if adding
// anything new here (search for search_b()).