From 5267c62f8c1f483c5348d4a2e498088a41c2e945 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Wed, 12 Apr 2023 07:34:39 +0200 Subject: Implement in-process package configuration --- bpkg/bpkg.cxx | 26 +-- bpkg/bpkg.hxx | 35 ++++ bpkg/buildfile | 5 +- bpkg/package-skeleton.cxx | 15 +- bpkg/pkg-build.cli | 9 +- bpkg/pkg-build.cxx | 124 +++++++++++++- bpkg/pkg-checkout.cxx | 1 - bpkg/pkg-configure.cxx | 402 ++++++++++++++++++++++++++++++++++++++++++---- bpkg/pkg-configure.hxx | 26 ++- bpkg/utility.hxx | 5 + bpkg/utility.txx | 34 ++++ 11 files changed, 617 insertions(+), 65 deletions(-) create mode 100644 bpkg/bpkg.hxx 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 + #include #include // getenv() #include // strcmp() @@ -15,9 +17,6 @@ #include #include #include -#include -#include -#include #include #include @@ -34,6 +33,9 @@ #include #include +#include +#include + #include #include @@ -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 +#include +#include + +#include +#include + +#include + +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 #include #include +#include #include #include #include +#include #include #include #include @@ -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.). 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 (), if any, should be specified before packages () 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 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_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 configure_ctx; + +#ifndef BPKG_OUTPROC_CONFIGURE + if (!simulate) + { + using build2::context; + using build2::variable_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 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 #include -#include 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 +#include +#include +#include + +#include +#include +#include +#include + +#include // build2_init(), etc + #include #include #include @@ -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 + pkg_configure_context ( + const common_options& o, + strings&& cmd_vars, + const 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 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& p, configure_prerequisites_result&& cpr, - bool disfigured, +#ifndef BPKG_OUTPROC_CONFIGURE + const unique_ptr& pctx, + const build2::variable_overrides& ovrs, +#else + const unique_ptr&, + 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 altn; + value& v (bootstrap_out (rs, altn)); + + if (!v) + v = src_root; + else + { + dir_path& p (cast (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 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 (""); + 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 (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 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 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 // version #include +#include +#include // variable_overrides + #include #include // transaction, selected_package #include @@ -100,17 +103,38 @@ namespace bpkg const function&, const 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&, configure_prerequisites_result&&, - bool disfigured, + const unique_ptr&, + const build2::variable_overrides&, bool simulate); + // Create a build context suitable for configuring packages. + // + unique_ptr + pkg_configure_context ( + const common_options&, + strings&& cmd_vars, + const 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 // // using butl::process_start_callback; + using butl::process_print_callback; // // @@ -254,6 +255,10 @@ namespace bpkg process_path search_b (const common_options&); + template + void + print_b (const common_options&, verb_b, A&&... args); + template 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 + void + print_b (const common_options& co, verb_b v, A&&... args) + { + process_path pp (search_b (co)); + + small_vector 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 (args)...); + } + template process start_b (const common_options& co, @@ -71,6 +103,8 @@ namespace bpkg { small_vector 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()). -- cgit v1.1