diff options
Diffstat (limited to 'bpkg/pkg-build.cxx')
-rw-r--r-- | bpkg/pkg-build.cxx | 1841 |
1 files changed, 1279 insertions, 562 deletions
diff --git a/bpkg/pkg-build.cxx b/bpkg/pkg-build.cxx index 32a680f..e15a9c9 100644 --- a/bpkg/pkg-build.cxx +++ b/bpkg/pkg-build.cxx @@ -20,9 +20,11 @@ #include <bpkg/common-options.hxx> +#include <bpkg/cfg-add.hxx> #include <bpkg/pkg-purge.hxx> #include <bpkg/pkg-fetch.hxx> #include <bpkg/rep-fetch.hxx> +#include <bpkg/cfg-create.hxx> #include <bpkg/pkg-unpack.hxx> #include <bpkg/pkg-update.hxx> #include <bpkg/pkg-verify.hxx> @@ -195,7 +197,6 @@ namespace bpkg // static pair<shared_ptr<available_package>, shared_ptr<repository_fragment>> make_available (const common_options& options, - const dir_path& c, database& db, const shared_ptr<selected_package>& sp) { @@ -214,7 +215,7 @@ namespace bpkg // moment). // shared_ptr<repository_fragment> af ( - db.find<repository_fragment> ( + db.main_database ().find<repository_fragment> ( sp->repository_fragment.canonical_name ())); // The package is in at least fetched state, which means we should @@ -225,10 +226,10 @@ namespace bpkg package_manifest m ( sp->state == package_state::fetched ? pkg_verify (options, - a->absolute () ? *a : c / *a, + a->absolute () ? *a : db.config_orig / *a, true /* ignore_unknown */, false /* expand_values */) - : pkg_verify (sp->effective_src_root (c), + : pkg_verify (sp->effective_src_root (db.config_orig), true /* ignore_unknown */, // Copy potentially fixed up version from selected package. [&sp] (version& v) {v = sp->version;})); @@ -311,6 +312,8 @@ namespace bpkg // optional<action_type> action; + reference_wrapper<database> db; // Needs to be move-assignable. + shared_ptr<selected_package> selected; // NULL if not selected. shared_ptr<available_package> available; // Can be NULL, fake/transient. @@ -337,12 +340,12 @@ namespace bpkg // struct constraint_type { + reference_wrapper<database> db; // Main database for non-packages. string dependent; version_constraint value; - constraint_type () = default; - constraint_type (string d, version_constraint v) - : dependent (move (d)), value (move (v)) {} + constraint_type (database& d, string dp, version_constraint v) + : db (d), dependent (move (dp)), value (move (v)) {} }; vector<constraint_type> constraints; @@ -373,12 +376,13 @@ namespace bpkg // Set of package names that caused this package to be built or adjusted. // Empty name signifies user selection. // - set<package_name> required_by; + set<config_package> required_by; bool user_selection () const { - return required_by.find (package_name ()) != required_by.end (); + return required_by.find (config_package {db.get ().main_database (), + ""}) != required_by.end (); } // Adjustment flags. @@ -434,17 +438,28 @@ namespace bpkg // assert (available != nullptr && (system - ? available->system_version () != nullptr + ? available->system_version (db) != nullptr : !available->stub ())); - return system ? *available->system_version () : available->version; + return system ? *available->system_version (db) : available->version; } string available_name_version () const { assert (available != nullptr); - return package_string (available->id.name, available_version (), system); + return package_string (available->id.name, + available_version (), + system); + } + + string + available_name_version_db () const + { + string s (db.get ().string ()); + return !s.empty () + ? available_name_version () + ' ' + s + : available_name_version (); } // Merge constraints, required-by package names, hold_* flags, @@ -453,6 +468,10 @@ namespace bpkg void merge (build_package&& p) { + // We don't merge objects from different configurations. + // + assert (db == p.db); + // We don't merge into pre-entered objects, and from/into drops. // assert (action && *action != drop && (!p.action || *p.action != drop)); @@ -483,7 +502,7 @@ namespace bpkg // Propagate the user-selection tag. // - required_by.insert (package_name ()); + required_by.emplace (db.get ().main_database (), package_name ()); } // Required-by package names have different semantics for different @@ -545,7 +564,10 @@ namespace bpkg { assert (!pkg.action); - auto p (map_.emplace (move (name), data_type {end (), move (pkg)})); + database& db (pkg.db); // Save before the move() call. + auto p (map_.emplace (config_package {db, move (name)}, + data_type {end (), move (pkg)})); + assert (p.second); } @@ -555,8 +577,6 @@ namespace bpkg // build_package* collect_build (const common_options& options, - const dir_path& cd, - database& db, build_package pkg, postponed_packages* recursively = nullptr) { @@ -569,7 +589,7 @@ namespace bpkg assert (pkg.action && *pkg.action == build_package::build && pkg.available != nullptr); - auto i (map_.find (pkg.available->id.name)); + auto i (map_.find (pkg.db, pkg.available->id.name)); // If we already have an entry for this package name, then we // have to pick one over the other. @@ -636,15 +656,15 @@ namespace bpkg // if (auto c1 = test (p2, p1)) { - const package_name& n (i->first); + const package_name& n (i->first.name); const string& d1 (c1->dependent); const string& d2 (c2->dependent); fail << "unable to satisfy constraints on package " << n << - info << d1 << " depends on (" << n << " " << c1->value - << ")" << - info << d2 << " depends on (" << n << " " << c2->value - << ")" << + info << d1 << c1->db << " depends on (" << n << " " + << c1->value << ")" << + info << d2 << c2->db << " depends on (" << n << " " + << c2->value << ")" << info << "available " << p1->available_name_version () << info << "available " << p2->available_name_version () << info << "explicitly specify " << n << " version to manually " @@ -654,8 +674,8 @@ namespace bpkg swap (p1, p2); } - l4 ([&]{trace << "pick " << p1->available_name_version () - << " over " << p2->available_name_version ();}); + l4 ([&]{trace << "pick " << p1->available_name_version_db () + << " over " << p2->available_name_version_db ();}); } // If versions are the same, then we still need to pick the entry as // one of them can build a package from source while another @@ -690,12 +710,14 @@ namespace bpkg { // This is the first time we are adding this package name to the map. // - l4 ([&]{trace << "add " << pkg.available_name_version ();}); + l4 ([&]{trace << "add " << pkg.available_name_version_db ();}); // Note: copy; see emplace() below. // + database& db (pkg.db); // Save before the move() call. package_name n (pkg.available->id.name); - i = map_.emplace (move (n), data_type {end (), move (pkg)}).first; + i = map_.emplace (config_package {db, move (n)}, + data_type {end (), move (pkg)}).first; } build_package& p (i->second.package); @@ -721,7 +743,7 @@ namespace bpkg // reasoning wrong. // if (recursively != nullptr) - collect_build_prerequisites (options, cd, db, p, recursively); + collect_build_prerequisites (options, p, recursively); return &p; } @@ -737,8 +759,6 @@ namespace bpkg // void collect_build_prerequisites (const common_options& options, - const dir_path& cd, - database& db, const build_package& pkg, postponed_packages* postponed) { @@ -761,13 +781,16 @@ namespace bpkg make_exception_guard ( [&pkg] () { - info << "while satisfying " << pkg.available_name_version (); + info << "while satisfying " << pkg.available_name_version_db (); })); const shared_ptr<available_package>& ap (pkg.available); const shared_ptr<repository_fragment>& af (pkg.repository_fragment); const package_name& name (ap->id.name); + database& pdb (pkg.db); + database& mdb (pdb.main_database ()); + for (const dependency_alternatives_ex& da: ap->dependencies) { if (da.conditional) // @@ TODO @@ -797,10 +820,6 @@ namespace bpkg continue; } - // else - // - // @@ TODO: in the future we would need to at least make sure the - // build and target machines are the same. See also pkg-configure. } bool system (false); @@ -823,7 +842,12 @@ namespace bpkg // const version_constraint* dep_constr (nullptr); - auto i (map_.find (dn)); + // If the dependency package build is already in the map, then switch + // to its configuration. + // + database* ddb (&pdb); + + auto i (map_.find_dependency (pdb, dn, da.buildtime)); if (i != map_.end ()) { const build_package& bp (i->second.package); @@ -849,13 +873,15 @@ namespace bpkg if (!wildcard (*dep_constr) && !satisfies (*dep_constr, dp.constraint)) fail << "unable to satisfy constraints on package " << dn << - info << name << " depends on (" << dn << " " + info << name << pdb << " depends on (" << dn << " " << *dp.constraint << ")" << - info << c.dependent << " depends on (" << dn << " " + info << c.dependent << c.db << " depends on (" << dn << " " << c.value << ")" << info << "specify " << dn << " version to satisfy " << name << " constraint"; } + + ddb = &bp.db.get (); } const dependency& d (!dep_constr @@ -863,11 +889,16 @@ namespace bpkg : dependency {dn, *dep_constr}); // First see if this package is already selected. If we already have - // it in the configuraion and it satisfies our dependency version + // it in the configuration and it satisfies our dependency version // constraint, then we don't want to be forcing its upgrade (or, // worse, downgrade). // - shared_ptr<selected_package> dsp (db.find<selected_package> (dn)); + // Search recursively in the explicitly associated configurations. + // + pair<shared_ptr<selected_package>, database*> spd ( + find_dependency (*ddb, dn, da.buildtime)); + + shared_ptr<selected_package>& dsp (spd.first); pair<shared_ptr<available_package>, shared_ptr<repository_fragment>> rp; @@ -878,8 +909,22 @@ namespace bpkg if (dsp != nullptr) { + // Fail if we end up building a dependency that is also configured + // in another configuration of the same type. + // + if (i != map_.end () && *ddb != *spd.second) + fail << "building package " << dn << " which is already " + << dsp->state << " in another configuration" << + info << "building in " << ddb->config_orig << + info << dsp->state << " in " << spd.second->config_orig << + info << "use --config-* to select package configuration"; + + // Switch to the selected package configuration. + // + ddb = spd.second; + if (dsp->state == package_state::broken) - fail << "unable to build broken package " << dn << + fail << "unable to build broken package " << dn << *ddb << info << "use 'pkg-purge --force' to remove"; // If the constraint is imposed by the user we also need to make sure @@ -897,11 +942,11 @@ namespace bpkg // doesn't really matter). // shared_ptr<repository_fragment> root ( - db.load<repository_fragment> ("")); + mdb.load<repository_fragment> ("")); rp = system - ? find_available_one (db, dn, nullopt, root) - : find_available_one (db, + ? find_available_one (mdb, dn, nullopt, root) + : find_available_one (mdb, dn, version_constraint (dsp->version), root); @@ -910,7 +955,7 @@ namespace bpkg // (returning stub as an available package feels wrong). // if (dap == nullptr || dap->stub ()) - rp = make_available (options, cd, db, dsp); + rp = make_available (options, *ddb, dsp); } else // Remember that we may be forcing up/downgrade; we will deal with @@ -919,6 +964,88 @@ namespace bpkg force = true; } + // If this is a build-time dependency and we build it for the first + // time, then we need to find a suitable configuration (of the host + // type) to build it in. + // + // If the current configuration (ddb) is of the host type, then we use + // that. Otherwise, we go through its immediate explicit associations. + // If only one of them has the host type, then we use that. If there + // are multiple of them, then we fail advising the user to pick one + // explicitly. If there are none, then we create the private host + // configuration and use that. + // + // Note that if the user has explicitly specified the configuration + // for this dependency on the command line (using --config-*), then + // this configuration is used as the starting point for this search. + // + if (da.buildtime && dsp == nullptr) + { + database* hdb (nullptr); + + // Note that the first returned association is for ddb itself. + // + for (const associated_config& ac: ddb->explicit_associations ()) + { + database& adb (ac.db); + + if (adb.type == "host") + { + // We are done if the self-association is of the host type. + // + if (ac.id == 0) + { + hdb = &adb; + break; + } + + if (hdb == nullptr) + hdb = &adb; + else + fail << "multiple possible host configurations for build-time " + << "dependency (" << dp << ")" << + info << hdb->config_orig << + info << adb.config_orig << + info << "use --config-* to select the configuration"; + } + } + + // If no host configuration is found, then create and associate it. + // + if (hdb == nullptr) + { + const strings mods {"cc"}; + const strings vars {"config.config.load=~host"}; + + // Wipe a potentially existing un-associated private configuration + // left from a previous faulty run. Note that trying to reuse it + // would be a bad idea since it can be half-prepared, with an + // outdated database schema version, etc. + // + cfg_create (options, + ddb->config_orig / host_dir, + optional<string> ("host") /* name */, + "host" /* type */, + mods, + vars, + false /* existing */, + true /* wipe */); + + // Note that we will copy the host name from the configuration + // unless it clashes with one of the existing associations. + // + shared_ptr<configuration> ac (cfg_add (*ddb, + ddb->config / host_dir, + true /* relative */, + nullopt /* name */, + true /* sys_rep */)); + + hdb = &ddb->find_attached (*ac->id); + } + + ddb = hdb; // Switch to the host configuration. + } + // If we didn't get the available package corresponding to the // selected package, look for any that satisfies the constraint. // @@ -930,7 +1057,7 @@ namespace bpkg // prerequisites). // if (af == nullptr) - fail << "package " << pkg.available_name_version () + fail << "package " << pkg.available_name_version_db () << " is orphaned" << info << "explicitly upgrade it to a new version"; @@ -976,7 +1103,7 @@ namespace bpkg // the package is recognized. An unrecognized package means the // broken/stale repository (see below). // - rp = find_available_one (db, + rp = find_available_one (mdb, dn, !system ? d.constraint : nullopt, af); @@ -998,7 +1125,7 @@ namespace bpkg if (d.constraint && (!dep_constr || !wildcard (*dep_constr))) dr << ' ' << *d.constraint; - dr << " of package " << name; + dr << " of package " << name << pdb; if (!af->location.empty () && (!dep_constr || system)) dr << info << "repository " << af->location << " appears to " @@ -1024,17 +1151,17 @@ namespace bpkg // version constraint). If it were, then the system version // wouldn't be NULL and would satisfy itself. // - if (dap->system_version () == nullptr) + if (dap->system_version (*ddb) == nullptr) fail << "dependency " << d << " of package " << name << " is " << "not available in source" << info << "specify ?sys:" << dn << " if it is available from " << "the system"; - if (!satisfies (*dap->system_version (), d.constraint)) + if (!satisfies (*dap->system_version (*ddb), d.constraint)) fail << "dependency " << d << " of package " << name << " is " << "not available in source" << info << package_string (dn, - *dap->system_version (), + *dap->system_version (*ddb), true /* system */) << " does not satisfy the constrains"; @@ -1042,7 +1169,7 @@ namespace bpkg } else { - auto p (dap->system_version_authoritative ()); + auto p (dap->system_version_authoritative (*ddb)); if (p.first != nullptr && p.second && // Authoritative. @@ -1053,19 +1180,20 @@ namespace bpkg build_package bp { build_package::build, + *ddb, dsp, dap, rp.second, - nullopt, // Hold package. - nullopt, // Hold version. - {}, // Constraints. + nullopt, // Hold package. + nullopt, // Hold version. + {}, // Constraints. system, - false, // Keep output directory. - nullopt, // Checkout root. - false, // Checkout purge. - strings (), // Configuration variables. - {name}, // Required by (dependent). - 0}; // Adjustments. + false, // Keep output directory. + nullopt, // Checkout root. + false, // Checkout purge. + strings (), // Configuration variables. + {config_package {pdb, name}}, // Required by (dependent). + 0}; // Adjustments. // Add our constraint, if we have one. // @@ -1075,7 +1203,7 @@ namespace bpkg // completeness. // if (dp.constraint) - bp.constraints.emplace_back (name.string (), *dp.constraint); + bp.constraints.emplace_back (pdb, name.string (), *dp.constraint); // Now collect this prerequisite. If it was actually collected // (i.e., it wasn't already there) and we are forcing a downgrade or @@ -1096,7 +1224,7 @@ namespace bpkg // build foo ?sys:bar/2 // const build_package* p ( - collect_build (options, cd, db, move (bp), postponed)); + collect_build (options, move (bp), postponed)); if (p != nullptr && force && !dep_optional) { @@ -1118,9 +1246,9 @@ namespace bpkg (f ? dr << fail : w ? dr << warn : dr << info) - << "package " << name << " dependency on " + << "package " << name << pdb << " dependency on " << (c ? "(" : "") << d << (c ? ")" : "") << " is forcing " - << (u ? "up" : "down") << "grade of " << *dsp << " to "; + << (u ? "up" : "down") << "grade of " << *dsp << *ddb << " to "; // Print both (old and new) package names in full if the system // attribution changes. @@ -1131,7 +1259,7 @@ namespace bpkg dr << av; // Can't be a system version so is never wildcard. if (dsp->hold_version) - dr << info << "package version " << *dsp << " is held"; + dr << info << "package version " << *dsp << *ddb << " is held"; if (f) dr << info << "explicitly request version " @@ -1144,12 +1272,13 @@ namespace bpkg // Collect the package being dropped. // void - collect_drop (shared_ptr<selected_package> sp) + collect_drop (database& db, shared_ptr<selected_package> sp) { const package_name& nm (sp->name); build_package p { build_package::drop, + db, move (sp), nullptr, nullptr, @@ -1164,7 +1293,7 @@ namespace bpkg {}, // Required by. 0}; // Adjustments. - auto i (map_.find (nm)); + auto i (map_.find (db, nm)); if (i != map_.end ()) { @@ -1180,15 +1309,16 @@ namespace bpkg bp = move (p); } else - map_.emplace (nm, data_type {end (), move (p)}); + map_.emplace (config_package {db, nm}, + data_type {end (), move (p)}); } // Collect the package being unheld. // void - collect_unhold (const shared_ptr<selected_package>& sp) + collect_unhold (database& db, const shared_ptr<selected_package>& sp) { - auto i (map_.find (sp->name)); + auto i (map_.find (db, sp->name)); // Currently, it must always be pre-entered. // @@ -1200,6 +1330,7 @@ namespace bpkg { build_package p { build_package::adjust, + db, sp, nullptr, nullptr, @@ -1223,20 +1354,17 @@ namespace bpkg void collect_build_prerequisites (const common_options& o, - const dir_path& cd, database& db, const package_name& name, postponed_packages& postponed) { - auto mi (map_.find (name)); + auto mi (map_.find (db, name)); assert (mi != map_.end ()); - collect_build_prerequisites (o, cd, db, mi->second.package, &postponed); + collect_build_prerequisites (o, mi->second.package, &postponed); } void collect_build_postponed (const common_options& o, - const dir_path& cd, - database& db, postponed_packages& pkgs) { // Try collecting postponed packages for as long as we are making @@ -1247,7 +1375,7 @@ namespace bpkg postponed_packages npkgs; for (const build_package* p: pkgs) - collect_build_prerequisites (o, cd, db, *p, prog ? &npkgs : nullptr); + collect_build_prerequisites (o, *p, prog ? &npkgs : nullptr); assert (prog); // collect_build_prerequisites() should have failed. prog = (npkgs != pkgs); @@ -1256,16 +1384,25 @@ namespace bpkg } // Order the previously-collected package with the specified name - // returning its positions. Recursively order the package dependencies - // being ordered failing if a dependency cycle is detected. If reorder is - // true, then reorder this package to be considered as "early" as - // possible. + // returning its positions. + // + // If buildtime is nullopt, then search for the specified package build in + // only the specified database. Otherwise, treat the package as a + // dependency and search for the build recursively (see + // config_package_map::find_dependency() for details). + // + // Recursively order the package dependencies being ordered failing if a + // dependency cycle is detected. If reorder is true, then reorder this + // package to be considered as "early" as possible. // iterator - order (const package_name& name, bool reorder = true) + order (database& db, + const package_name& name, + optional<bool> buildtime, + bool reorder = true) { - package_names chain; - return order (name, chain, reorder); + config_package_names chain; + return order (db, name, buildtime, chain, reorder); } // If a configured package is being up/down-graded then that means @@ -1284,7 +1421,7 @@ namespace bpkg // to also notice this. // void - collect_order_dependents (database& db) + collect_order_dependents () { // For each package on the list we want to insert all its dependents // before it so that they get configured after the package on which @@ -1305,18 +1442,20 @@ namespace bpkg // Dropped package may have no dependents. // if (*p.action != build_package::drop && p.reconfigure ()) - collect_order_dependents (db, i); + collect_order_dependents (i); } } void - collect_order_dependents (database& db, iterator pos) + collect_order_dependents (iterator pos) { tracer trace ("collect_order_dependents"); assert (pos != end ()); build_package& p (*pos); + + database& pdb (p.db); const shared_ptr<selected_package>& sp (p.selected); const package_name& n (sp->name); @@ -1328,166 +1467,173 @@ namespace bpkg ? sp->version.compare (p.available_version ()) : 0); - using query = query<package_dependent>; - - for (auto& pd: db.query<package_dependent> (query::name == n)) + for (database& ddb: pdb.dependent_configs ()) { - package_name& dn (pd.name); - auto i (map_.find (dn)); - - // First make sure the up/downgraded package still satisfies this - // dependent. - // - bool check (ud != 0 && pd.constraint); - - // There is one tricky aspect: the dependent could be in the process - // of being up/downgraded as well. In this case all we need to do is - // detect this situation and skip the test since all the (new) - // contraints of this package have been satisfied in collect_build(). - // - if (check && i != map_.end () && i->second.position != end ()) + for (auto& pd: query_dependents (ddb, n, pdb)) { - build_package& dp (i->second.package); - - check = dp.available == nullptr || - (dp.selected->system () == dp.system && - dp.selected->version == dp.available_version ()); - } + package_name& dn (pd.name); + auto i (map_.find (ddb, dn)); - if (check) - { - const version& av (p.available_version ()); - const version_constraint& c (*pd.constraint); + // First make sure the up/downgraded package still satisfies this + // dependent. + // + bool check (ud != 0 && pd.constraint); - if (!satisfies (av, c)) + // There is one tricky aspect: the dependent could be in the process + // of being up/downgraded as well. In this case all we need to do is + // detect this situation and skip the test since all the (new) + // contraints of this package have been satisfied in + // collect_build(). + // + if (check && i != map_.end () && i->second.position != end ()) { - diag_record dr (fail); + build_package& dp (i->second.package); - dr << "unable to " << (ud < 0 ? "up" : "down") << "grade " - << "package " << *sp << " to "; - - // Print both (old and new) package names in full if the system - // attribution changes. - // - if (p.system != sp->system ()) - dr << p.available_name_version (); - else - dr << av; // Can't be the wildcard otherwise would satisfy. + check = dp.available == nullptr || + (dp.selected->system () == dp.system && + dp.selected->version == dp.available_version ()); + } - dr << info << "because package " << dn << " depends on (" << n - << " " << c << ")"; + if (check) + { + const version& av (p.available_version ()); + const version_constraint& c (*pd.constraint); - string rb; - if (!p.user_selection ()) + if (!satisfies (av, c)) { - for (const package_name& n: p.required_by) - rb += ' ' + n.string (); - } + diag_record dr (fail); + + dr << "unable to " << (ud < 0 ? "up" : "down") << "grade " + << "package " << *sp << pdb << " to "; + + // Print both (old and new) package names in full if the system + // attribution changes. + // + if (p.system != sp->system ()) + dr << p.available_name_version (); + else + dr << av; // Can't be the wildcard otherwise would satisfy. - if (!rb.empty ()) - dr << info << "package " << p.available_name_version () - << " required by" << rb; + dr << info << "because package " << dn << ddb << " depends on (" + << n << " " << c << ")"; - dr << info << "explicitly request up/downgrade of package " << dn; + string rb; + if (!p.user_selection ()) + { + for (const config_package& cp: p.required_by) + rb += ' ' + cp.string (); + } - dr << info << "or explicitly specify package " << n << " version " - << "to manually satisfy these constraints"; - } + if (!rb.empty ()) + dr << info << "package " << p.available_name_version () + << " required by" << rb; - // Add this contraint to the list for completeness. - // - p.constraints.emplace_back (dn.string (), c); - } + dr << info << "explicitly request up/downgrade of package " + << dn; - auto adjustment = [&dn, &n, &db] () -> build_package - { - shared_ptr<selected_package> dsp (db.load<selected_package> (dn)); - bool system (dsp->system ()); // Save flag before the move(dsp) call. - - return build_package { - build_package::adjust, - move (dsp), - nullptr, // No available package/repository fragment. - nullptr, - nullopt, // Hold package. - nullopt, // Hold version. - {}, // Constraints. - system, - false, // Keep output directory. - nullopt, // Checkout root. - false, // Checkout purge. - strings (), // Configuration variables. - {n}, // Required by (dependency). - build_package::adjust_reconfigure}; - }; + dr << info << "or explicitly specify package " << n + << " version to manually satisfy these constraints"; + } - // We can have three cases here: the package is already on the - // list, the package is in the map (but not on the list) and it - // is in neither. - // - // If the existing entry is a drop, then we skip it. If it is - // pre-entered, is an adjustment, or is a build that is not supposed - // to be built (not in the list), then we merge it into the new - // adjustment entry. Otherwise (is a build in the list), we just add - // the reconfigure adjustment flag to it. - // - if (i != map_.end ()) - { - build_package& dp (i->second.package); - iterator& dpos (i->second.position); + // Add this contraint to the list for completeness. + // + p.constraints.emplace_back (ddb, dn.string (), c); + } - if (!dp.action || // Pre-entered. - *dp.action != build_package::build || // Non-build. - dpos == end ()) // Build not in the list. + auto adjustment = [&dn, &ddb, &n, &pdb] () -> build_package { - // Skip the droped package. - // - if (dp.action && *dp.action == build_package::drop) - continue; + shared_ptr<selected_package> dsp (ddb.load<selected_package> (dn)); + + bool system (dsp->system ()); // Save before the move(dsp) call. + + return build_package { + build_package::adjust, + ddb, + move (dsp), + nullptr, // No available pkg/repo fragment. + nullptr, + nullopt, // Hold package. + nullopt, // Hold version. + {}, // Constraints. + system, + false, // Keep output directory. + nullopt, // Checkout root. + false, // Checkout purge. + strings (), // Configuration variables. + {config_package {pdb, n}}, // Required by (dependency). + build_package::adjust_reconfigure}; + }; - build_package bp (adjustment ()); - bp.merge (move (dp)); - dp = move (bp); - } - else // Build in the list. - dp.adjustments |= build_package::adjust_reconfigure; - - // It may happen that the dependent is already in the list but is - // not properly ordered against its dependencies that get into the - // list via another dependency path. Thus, we check if the dependent - // is to the right of its dependency and, if that's the case, - // reinsert it in front of the dependency. + // We can have three cases here: the package is already on the + // list, the package is in the map (but not on the list) and it + // is in neither. // - if (dpos != end ()) + // If the existing entry is a drop, then we skip it. If it is + // pre-entered, is an adjustment, or is a build that is not supposed + // to be built (not in the list), then we merge it into the new + // adjustment entry. Otherwise (is a build in the list), we just add + // the reconfigure adjustment flag to it. + // + if (i != map_.end ()) { - for (auto i (pos); i != end (); ++i) + build_package& dp (i->second.package); + iterator& dpos (i->second.position); + + if (!dp.action || // Pre-entered. + *dp.action != build_package::build || // Non-build. + dpos == end ()) // Build not in the list. { - if (i == dpos) + // Skip the droped package. + // + if (dp.action && *dp.action == build_package::drop) + continue; + + build_package bp (adjustment ()); + bp.merge (move (dp)); + dp = move (bp); + } + else // Build in the list. + dp.adjustments |= build_package::adjust_reconfigure; + + // It may happen that the dependent is already in the list but is + // not properly ordered against its dependencies that get into the + // list via another dependency path. Thus, we check if the + // dependent is to the right of its dependency and, if that's the + // case, reinsert it in front of the dependency. + // + if (dpos != end ()) + { + for (auto i (pos); i != end (); ++i) { - erase (dpos); - dpos = insert (pos, dp); - break; + if (i == dpos) + { + erase (dpos); + dpos = insert (pos, dp); + break; + } } } + else + dpos = insert (pos, dp); } else - dpos = insert (pos, dp); - } - else - { - i = map_.emplace ( - move (dn), data_type {end (), adjustment ()}).first; + { + // Don't move dn since it is used by adjustment(). + // + i = map_.emplace (config_package {ddb, dn}, + data_type {end (), adjustment ()}).first; - i->second.position = insert (pos, i->second.package); - } + i->second.position = insert (pos, i->second.package); + } - // Recursively collect our own dependents inserting them before us. - // - // Note that we cannot end up with an infinite recursion for - // configured packages due to a dependency cycle (see order() for - // details). - // - collect_order_dependents (db, i->second.position); + // Recursively collect our own dependents inserting them before us. + // + // Note that we cannot end up with an infinite recursion for + // configured packages due to a dependency cycle (see order() for + // details). + // + collect_order_dependents (i->second.position); + } } } @@ -1508,34 +1654,54 @@ namespace bpkg } private: - using package_names = small_vector<reference_wrapper<const package_name>, - 16>; + struct config_package_name + { + database& db; + const package_name& name; + + bool + operator== (const config_package_name& v) + { + return name == v.name && db == v.db; + } + }; + using config_package_names = small_vector<config_package_name, 16>; iterator - order (const package_name& name, package_names& chain, bool reorder) + order (database& db, + const package_name& name, + optional<bool> buildtime, + config_package_names& chain, + bool reorder) { // Every package that we order should have already been collected. // - auto mi (map_.find (name)); + auto mi (!buildtime + ? map_.find (db, name) + : map_.find_dependency (db, name, *buildtime)); + assert (mi != map_.end ()); build_package& p (mi->second.package); assert (p.action); // Can't order just a pre-entered package. + database& pdb (p.db); + // Make sure there is no dependency cycle. // + config_package_name cp {pdb, name}; { - auto i (find (chain.begin (), chain.end (), name)); + auto i (find (chain.begin (), chain.end (), cp)); if (i != chain.end ()) { diag_record dr (fail); - dr << "dependency cycle detected involving package " << name; + dr << "dependency cycle detected involving package " << name << pdb; - auto nv = [this] (const package_name& name) + auto nv = [this] (const config_package_name& cp) { - auto mi (map_.find (name)); + auto mi (map_.find (cp.db, cp.name)); assert (mi != map_.end ()); build_package& p (mi->second.package); @@ -1548,14 +1714,14 @@ namespace bpkg // assert (p.available != nullptr); - return p.available_name_version (); + return p.available_name_version_db (); }; // Note: push_back() can invalidate the iterator. // size_t j (i - chain.begin ()); - for (chain.push_back (name); j != chain.size () - 1; ++j) + for (chain.push_back (cp); j != chain.size () - 1; ++j) dr << info << nv (chain[j]) << " depends on " << nv (chain[j + 1]); } } @@ -1577,7 +1743,7 @@ namespace bpkg // position of its "earliest" prerequisite -- this is where it // will be inserted. // - const shared_ptr<selected_package>& sp (p.selected); + const shared_ptr<selected_package>& sp (p.selected); const shared_ptr<available_package>& ap (p.available); bool build (*p.action == build_package::build); @@ -1624,7 +1790,7 @@ namespace bpkg bool order_disfigured (src_conf && disfigure (p)); - chain.push_back (name); + chain.push_back (cp); // Order the build dependencies. // @@ -1639,13 +1805,18 @@ namespace bpkg { for (const auto& p: sp->prerequisites) { + database& db (p.first.database ()); const package_name& name (p.first.object_id ()); // The prerequisites may not necessarily be in the map. // - auto i (map_.find (name)); + auto i (map_.find (db, name)); if (i != map_.end () && i->second.package.action) - update (order (name, chain, false /* reorder */)); + update (order (db, + name, + nullopt /* buildtime */, + chain, + false /* reorder */)); } // We just ordered them among other prerequisites. @@ -1671,7 +1842,11 @@ namespace bpkg if (da.buildtime && (dn == "build2" || dn == "bpkg")) continue; - update (order (d.name, chain, false /* reorder */)); + update (order (pdb, + d.name, + da.buildtime, + chain, + false /* reorder */)); } } } @@ -1682,14 +1857,19 @@ namespace bpkg { for (const auto& p: sp->prerequisites) { + database& db (p.first.database ()); const package_name& name (p.first.object_id ()); // The prerequisites may not necessarily be in the map. // - auto i (map_.find (name)); + auto i (map_.find (db, name)); if (i != map_.end () && disfigure (i->second.package)) - update (order (name, chain, false /* reorder */)); + update (order (db, + name, + nullopt /* buildtime */, + chain, + false /* reorder */)); } } @@ -1705,7 +1885,49 @@ namespace bpkg build_package package; }; - map<package_name, data_type> map_; + class config_package_map: public map<config_package, data_type> + { + public: + using base_type = map<config_package, data_type>; + + iterator + find (database& db, const package_name& pn) + { + return base_type::find (config_package {db, pn}); + } + + // Try to find a package build in the dependency configurations (see + // database::dependency_configs() for details). Return the end iterator + // if no build is found and issue diagnostics and fail if multiple + // builds (in multiple configurations) are found. + // + iterator + find_dependency (database& db, const package_name& pn, bool buildtime) + { + iterator r (end ()); + + associated_databases adbs (db.dependency_configs (buildtime)); + + for (database& adb: adbs) + { + iterator i (find (adb, pn)); + if (i != end ()) + { + if (r == end ()) + r = i; + else + fail << "building package " << pn << " in multiple " + << "configurations" << + info << r->first.db.config_orig << + info << adb.config_orig << + info << "use --config-* to select package configuration"; + } + } + + return r; + } + }; + config_package_map map_; }; // Return a patch version constraint for the selected package if it has a @@ -1759,15 +1981,16 @@ namespace bpkg // struct dependency_package { - package_name name; - optional<version_constraint> constraint; // nullopt if unspecified. - shared_ptr<selected_package> selected; // NULL if not present. - bool system; - bool patch; // Only for an empty version. - bool keep_out; - optional<dir_path> checkout_root; - bool checkout_purge; - strings config_vars; // Only if not system. + database& db; + package_name name; + optional<version_constraint> constraint; // nullopt if unspecified. + shared_ptr<selected_package> selected; // NULL if not present. + bool system; + bool patch; // Only for an empty version. + bool keep_out; + optional<dir_path> checkout_root; + bool checkout_purge; + strings config_vars; // Only if not system. }; using dependency_packages = vector<dependency_package>; @@ -1788,14 +2011,25 @@ namespace bpkg // struct evaluate_result { - shared_ptr<available_package> available; + shared_ptr<available_package> available; shared_ptr<bpkg::repository_fragment> repository_fragment; - bool unused; - bool system; // Is meaningless if unused. + bool unused; + bool system; // Is meaningless if unused. + }; + + struct config_package_dependent + { + database& db; + shared_ptr<selected_package> package; + optional<version_constraint> constraint; + + config_package_dependent (database& d, + shared_ptr<selected_package> p, + optional<version_constraint> c) + : db (d), package (move (p)), constraint (move (c)) {} }; - using package_dependents = vector<pair<shared_ptr<selected_package>, - optional<version_constraint>>>; + using config_package_dependents = vector<config_package_dependent>; static optional<evaluate_result> evaluate_dependency (database&, @@ -1805,13 +2039,13 @@ namespace bpkg bool patch, bool explicitly, const set<shared_ptr<repository_fragment>>&, - const package_dependents&, + const config_package_dependents&, bool ignore_unsatisfiable); static optional<evaluate_result> evaluate_dependency (database& db, - const dependency_packages& deps, const shared_ptr<selected_package>& sp, + const dependency_packages& deps, bool ignore_unsatisfiable) { tracer trace ("evaluate_dependency"); @@ -1820,14 +2054,49 @@ namespace bpkg const package_name& nm (sp->name); - // Query the dependents and bail out if the dependency is unused. + // If there are no user expectations regarding this dependency, then we + // give no up/down-grade recommendation, unless there are no dependents + // in which case we recommend to drop the dependency. // - auto pds (db.query<package_dependent> ( - query<package_dependent>::name == nm)); + // Note that it would be easier to check for the dependent's presence + // first and, if present, for the user expectations afterwords. We, + // however, don't want to needlessly query all the explicitly associated + // databases (which can be many) for dependents if we can bail out + // earlier. + // + auto i (find_if ( + deps.begin (), deps.end (), + [&nm, &db] (const dependency_package& i) + { + return i.name == nm && i.db == db; + })); + + bool no_rec (i == deps.end ()); + + vector<pair<database&, package_dependent>> pds; + for (database& ddb: db.dependent_configs ()) + { + auto ds (query_dependents (ddb, nm, db)); + + // Bail out if the dependency is used but there are no user expectations + // regrading it. + // + if (!ds.empty ()) + { + if (no_rec) + return nullopt; + + for (auto& d: ds) + pds.emplace_back (ddb, move (d)); + } + } + + // Bail out if the dependency is unused. + // if (pds.empty ()) { - l5 ([&]{trace << *sp << ": unused";}); + l5 ([&]{trace << *sp << db << ": unused";}); return evaluate_result {nullptr /* available */, nullptr /* repository_fragment */, @@ -1835,16 +2104,6 @@ namespace bpkg false /* system */}; } - // If there are no user expectations regarding this dependency, then we - // give no up/down-grade recommendation. - // - auto i (find_if ( - deps.begin (), deps.end (), - [&nm] (const dependency_package& i) {return i.name == nm;})); - - if (i == deps.end ()) - return nullopt; - // If the selected package matches the user expectations then no package // change is required. // @@ -1860,7 +2119,7 @@ namespace bpkg dvc && (ssys ? sv == *dvc->min_version : satisfies (sv, dvc))) { - l5 ([&]{trace << *sp << ": unchanged";}); + l5 ([&]{trace << *sp << db << ": unchanged";}); return evaluate_result {nullptr /* available */, nullptr /* repository_fragment */, @@ -1873,14 +2132,20 @@ namespace bpkg // dependency. // set<shared_ptr<repository_fragment>> repo_frags; - package_dependents dependents; + config_package_dependents dependents; + + database& mdb (db.main_database ()); for (auto& pd: pds) { - shared_ptr<selected_package> dsp (db.load<selected_package> (pd.name)); + database& ddb (pd.first); + package_dependent& dep (pd.second); + + shared_ptr<selected_package> dsp ( + ddb.load<selected_package> (dep.name)); shared_ptr<available_package> dap ( - db.find<available_package> ( + mdb.find<available_package> ( available_package_id (dsp->name, dsp->version))); if (dap != nullptr) @@ -1891,7 +2156,7 @@ namespace bpkg repo_frags.insert (pl.repository_fragment.load ()); } - dependents.emplace_back (move (dsp), move (pd.constraint)); + dependents.emplace_back (ddb, move (dsp), move (dep.constraint)); } return evaluate_dependency (db, @@ -1905,6 +2170,29 @@ namespace bpkg ignore_unsatisfiable); } + struct config_selected_package + { + database& db; + const shared_ptr<selected_package>& package; + + config_selected_package (database& d, + const shared_ptr<selected_package>& p) + : db (d), package (p) {} + + bool + operator== (const config_selected_package& v) const + { + return package->name == v.package->name && db == v.db; + } + + bool + operator< (const config_selected_package& v) const + { + int r (package->name.compare (v.package->name)); + return r != 0 ? (r < 0) : (db < v.db); + } + }; + static optional<evaluate_result> evaluate_dependency (database& db, const shared_ptr<selected_package>& sp, @@ -1913,7 +2201,7 @@ namespace bpkg bool patch, bool explicitly, const set<shared_ptr<repository_fragment>>& rfs, - const package_dependents& dependents, + const config_package_dependents& dependents, bool ignore_unsatisfiable) { tracer trace ("evaluate_dependency"); @@ -1947,7 +2235,7 @@ namespace bpkg if (!c) { - l5 ([&]{trace << *sp << ": non-patchable";}); + l5 ([&]{trace << *sp << db << ": non-patchable";}); return no_change (); } } @@ -1957,7 +2245,7 @@ namespace bpkg vector<pair<shared_ptr<available_package>, shared_ptr<repository_fragment>>> afs ( - find_available (db, + find_available (db.main_database (), nm, c, vector<shared_ptr<repository_fragment>> (rfs.begin (), @@ -1967,30 +2255,21 @@ namespace bpkg // satisfies all the dependents. Collect (and sort) unsatisfied dependents // per the unsatisfiable version in case we need to print them. // - struct compare_sp - { - bool - operator() (const shared_ptr<selected_package>& x, - const shared_ptr<selected_package>& y) const - { - return x->name < y->name; - } - }; - - using sp_set = set<reference_wrapper<const shared_ptr<selected_package>>, - compare_sp>; + using sp_set = set<config_selected_package>; vector<pair<version, sp_set>> unsatisfiable; bool stub (false); bool ssys (sp->system ()); - assert (!dsys || system_repository.find (nm) != nullptr); + assert (!dsys || + (db.system_repository && + db.system_repository->find (nm) != nullptr)); for (auto& af: afs) { shared_ptr<available_package>& ap (af.first); - const version& av (!dsys ? ap->version : *ap->system_version ()); + const version& av (!dsys ? ap->version : *ap->system_version (db)); // If we aim to upgrade to the latest version and it tends to be less // then the selected one, then what we currently have is the best that @@ -2007,7 +2286,7 @@ namespace bpkg // if (!ssys) { - l5 ([&]{trace << *sp << ": best";}); + l5 ([&]{trace << *sp << db << ": best";}); return no_change (); } @@ -2029,7 +2308,7 @@ namespace bpkg for (const auto& dp: dependents) { - if (!satisfies (av, dp.second)) + if (!satisfies (av, dp.constraint)) { satisfactory = false; @@ -2039,7 +2318,7 @@ namespace bpkg if (ignore_unsatisfiable) break; - unsatisfied_dependents.insert (dp.first); + unsatisfied_dependents.emplace (dp.db, dp.package); } } @@ -2064,11 +2343,11 @@ namespace bpkg // if (av == sv && ssys == dsys) { - l5 ([&]{trace << *sp << ": unchanged";}); + l5 ([&]{trace << *sp << db << ": unchanged";}); return no_change (); } - l5 ([&]{trace << *sp << ": update to " + l5 ([&]{trace << *sp << db << ": update to " << package_string (nm, av, dsys);}); return evaluate_result { @@ -2083,18 +2362,18 @@ namespace bpkg { assert (!dsys); // Version cannot be empty for the system package. - l5 ([&]{trace << *sp << ": only";}); + l5 ([&]{trace << *sp << db << ": only";}); return no_change (); } // If the version satisfying the desired dependency version constraint is // unavailable or unsatisfiable for some dependents then we fail, unless - // requested not to do so. In the later case we return the "no change" + // requested not to do so. In the latter case we return the "no change" // result. // if (ignore_unsatisfiable) { - l5 ([&]{trace << package_string (nm, dvc, dsys) + l5 ([&]{trace << package_string (nm, dvc, dsys) << db << (unsatisfiable.empty () ? ": no source" : ": unsatisfiable");}); @@ -2118,11 +2397,11 @@ namespace bpkg // assert (explicitly); - fail << "patch version for " << *sp << " is not available " + fail << "patch version for " << *sp << db << " is not available " << "from its dependents' repositories"; } else if (!stub) - fail << package_string (nm, dsys ? nullopt : dvc) + fail << package_string (nm, dsys ? nullopt : dvc) << db << " is not available from its dependents' repositories"; else // The only available package is a stub. { @@ -2131,15 +2410,15 @@ namespace bpkg // assert (!dvc && !dsys && ssys); - fail << package_string (nm, dvc) << " is not available in source " - << "from its dependents' repositories"; + fail << package_string (nm, dvc) << db << " is not available in " + << "source from its dependents' repositories"; } } // Issue the diagnostics and fail. // diag_record dr (fail); - dr << "package " << nm << " doesn't satisfy its dependents"; + dr << "package " << nm << db << " doesn't satisfy its dependents"; // Print the list of unsatisfiable versions together with dependents they // don't satisfy: up to three latest versions with no more than five @@ -2152,9 +2431,9 @@ namespace bpkg size_t n (0); const sp_set& ps (u.second); - for (const shared_ptr<selected_package>& p: ps) + for (const config_selected_package& p: ps) { - dr << ' ' << *p; + dr << ' ' << *p.package << p.db; if (++n == 5 && ps.size () != 6) // Printing 'and 1 more' looks stupid. break; @@ -2178,6 +2457,7 @@ namespace bpkg // struct recursive_package { + database& db; package_name name; bool upgrade; // true -- upgrade, false -- patch. bool recursive; // true -- recursive, false -- immediate. @@ -2191,18 +2471,18 @@ namespace bpkg static optional<bool> upgrade_dependencies (database& db, const package_name& nm, - const recursive_packages& recs, + const recursive_packages& rs, bool recursion = false) { - auto i (find_if (recs.begin (), recs.end (), - [&nm] (const recursive_package& i) -> bool + auto i (find_if (rs.begin (), rs.end (), + [&nm, &db] (const recursive_package& i) -> bool { - return i.name == nm; + return i.name == nm && i.db == db; })); optional<bool> r; - if (i != recs.end () && i->recursive >= recursion) + if (i != rs.end () && i->recursive >= recursion) { r = i->upgrade; @@ -2210,20 +2490,23 @@ namespace bpkg return r; } - for (const auto& pd: db.query<package_dependent> ( - query<package_dependent>::name == nm)) + for (database& ddb: db.dependent_configs ()) { - // Note that we cannot end up with an infinite recursion for configured - // packages due to a dependency cycle (see order() for details). - // - if (optional<bool> u = upgrade_dependencies (db, pd.name, recs, true)) + for (auto& pd: query_dependents (ddb, nm, db)) { - if (!r || *r < *u) // Upgrade wins patch. + // Note that we cannot end up with an infinite recursion for + // configured packages due to a dependency cycle (see order() for + // details). + // + if (optional<bool> u = upgrade_dependencies (ddb, pd.name, rs, true)) { - r = u; + if (!r || *r < *u) // Upgrade wins patch. + { + r = u; - if (*r) // Upgrade (vs patch)? - return r; + if (*r) // Upgrade (vs patch)? + return r; + } } } } @@ -2244,8 +2527,8 @@ namespace bpkg // static optional<evaluate_result> evaluate_recursive (database& db, - const recursive_packages& recs, const shared_ptr<selected_package>& sp, + const recursive_packages& recs, bool ignore_unsatisfiable) { tracer trace ("evaluate_recursive"); @@ -2257,10 +2540,7 @@ namespace bpkg // dependency. // set<shared_ptr<repository_fragment>> repo_frags; - package_dependents dependents; - - auto pds (db.query<package_dependent> ( - query<package_dependent>::name == sp->name)); + config_package_dependents dependents; // Only collect repository fragments (for best version selection) of // (immediate) dependents that have a hit (direct or indirect) in recs. @@ -2268,39 +2548,46 @@ namespace bpkg // optional<bool> upgrade; - for (const auto& pd: pds) - { - shared_ptr<selected_package> dsp (db.load<selected_package> (pd.name)); - dependents.emplace_back (dsp, move (pd.constraint)); + database& mdb (db.main_database ()); - if (optional<bool> u = upgrade_dependencies (db, pd.name, recs)) + for (database& ddb: db.dependent_configs ()) + { + for (auto& pd: query_dependents (ddb, sp->name, db)) { - if (!upgrade || *upgrade < *u) // Upgrade wins patch. - upgrade = u; - } - else - continue; + shared_ptr<selected_package> dsp ( + ddb.load<selected_package> (pd.name)); - // While we already know that the dependency upgrade is required, we - // continue to iterate over dependents, collecting the repository - // fragments and the constraints. - // - shared_ptr<available_package> dap ( - db.find<available_package> ( - available_package_id (dsp->name, dsp->version))); + dependents.emplace_back (ddb, dsp, move (pd.constraint)); - if (dap != nullptr) - { - assert (!dap->locations.empty ()); + if (optional<bool> u = upgrade_dependencies (ddb, pd.name, recs)) + { + if (!upgrade || *upgrade < *u) // Upgrade wins patch. + upgrade = u; + } + else + continue; - for (const auto& pl: dap->locations) - repo_frags.insert (pl.repository_fragment.load ()); + // While we already know that the dependency upgrade is required, we + // continue to iterate over dependents, collecting the repository + // fragments and the constraints. + // + shared_ptr<available_package> dap ( + mdb.find<available_package> ( + available_package_id (dsp->name, dsp->version))); + + if (dap != nullptr) + { + assert (!dap->locations.empty ()); + + for (const auto& pl: dap->locations) + repo_frags.insert (pl.repository_fragment.load ()); + } } } if (!upgrade) { - l5 ([&]{trace << *sp << ": no hit";}); + l5 ([&]{trace << *sp << db << ": no hit";}); return nullopt; } @@ -2323,12 +2610,10 @@ namespace bpkg return r && r->available == nullptr ? nullopt : r; } - static void - execute_plan (const pkg_build_options&, - const dir_path&, - database&, - build_package_list&, - bool simulate); + // Return false if the plan execution was noop. + // + static bool + execute_plan (const pkg_build_options&, build_package_list&, bool simulate); using pkg_options = pkg_build_pkg_options; @@ -2357,7 +2642,13 @@ namespace bpkg (o.upgrade_recursive () ? 1 : 0) + (o.patch_immediate () ? 1 : 0) + (o.patch_recursive () ? 1 : 0)) > 1) - fail << "multiple --(upgrade|patch)-(immediate|recursive) specified"; + dr << fail << "multiple --(upgrade|patch)-(immediate|recursive) " + << "specified"; + + if (((o.config_id_specified () ? 1 : 0) + + (o.config_name_specified () ? 1 : 0) + + (o.config_uuid_specified () ? 1 : 0)) > 1) + dr << fail << "multiple --config-* specified"; if (!dr.empty () && !pkg.empty ()) dr << info << "while validating options for " << pkg; @@ -2400,6 +2691,29 @@ namespace bpkg } dst.checkout_purge (src.checkout_purge () || dst.checkout_purge ()); + + if (!dst.config_id_specified () && + !dst.config_name_specified () && + !dst.config_uuid_specified ()) + { + if (src.config_id_specified ()) + { + dst.config_id (src.config_id ()); + dst.config_id_specified (true); + } + + if (src.config_name_specified ()) + { + dst.config_name (src.config_name ()); + dst.config_name_specified (true); + } + + if (src.config_uuid_specified ()) + { + dst.config_uuid (src.config_uuid ()); + dst.config_uuid_specified (true); + } + } } static bool @@ -2416,7 +2730,10 @@ namespace bpkg x.patch_immediate () == y.patch_immediate () && x.patch_recursive () == y.patch_recursive () && x.checkout_root () == y.checkout_root () && - x.checkout_purge () == y.checkout_purge (); + x.checkout_purge () == y.checkout_purge () && + x.config_id () == y.config_id () && + x.config_name () == y.config_name () && + x.config_uuid () == y.config_uuid (); } int @@ -2438,7 +2755,9 @@ namespace bpkg fail << "package name argument expected" << info << "run 'bpkg help pkg-build' for more information"; - database db (open (c, trace)); // Also populates the system repository. + // Also populates the system repository. + // + database mdb (c, trace, true /* pre_attach */, true /* sys_rep */); // Note that the session spans all our transactions. The idea here is that // selected_package objects in build_packages below will be cached in this @@ -2460,6 +2779,7 @@ namespace bpkg // struct pkg_spec { + database* db; // A pointer since we build these objects incrementally. string packages; repository_location location; pkg_options options; @@ -2513,7 +2833,7 @@ namespace bpkg vector<repository_location> locations; - transaction t (db); + transaction t (mdb); while (args.more ()) { @@ -2563,6 +2883,15 @@ namespace bpkg fail << e << " grouped for argument '" << a << "'"; } + // Note: main database if no --config-* option is specified. + // + if (ps.options.config_name_specified ()) + ps.db = &mdb.find_attached (ps.options.config_name ()); + else if (ps.options.config_uuid_specified ()) + ps.db = &mdb.find_dependency_config (ps.options.config_uuid ()); + else + ps.db = &mdb.find_attached (ps.options.config_id ()); + if (!a.empty () && a[0] == '?') { ps.options.dependency (true); @@ -2647,7 +2976,7 @@ namespace bpkg ( query::local && u + " COLLATE nocase = " + query::_val (l))); #endif - auto rs (db.query<repository> (q)); + auto rs (mdb.query<repository> (q)); auto i (rs.begin ()); if (i != rs.end ()) @@ -2690,10 +3019,14 @@ namespace bpkg t.commit (); + // Fetch the repositories in the main configuration. + // + // Note that during this build only the repositories information from + // the main database will be used. + // if (!locations.empty ()) rep_fetch (o, - c, - db, + mdb, locations, o.fetch_shallow (), string () /* reason for "fetching ..." */); @@ -2710,6 +3043,7 @@ namespace bpkg // struct pkg_arg { + reference_wrapper<database> db; package_scheme scheme; package_name name; optional<version_constraint> constraint; @@ -2720,7 +3054,8 @@ namespace bpkg // Create the parsed package argument. // - auto arg_package = [] (package_scheme sc, + auto arg_package = [] (database& db, + package_scheme sc, package_name nm, optional<version_constraint> vc, pkg_options os, @@ -2728,7 +3063,8 @@ namespace bpkg { assert (!vc || !vc->empty ()); // May not be empty if present. - pkg_arg r {sc, move (nm), move (vc), string (), move (os), move (vs)}; + pkg_arg r { + db, sc, move (nm), move (vc), string (), move (os), move (vs)}; switch (sc) { @@ -2742,14 +3078,20 @@ namespace bpkg // assert (r.constraint->min_version == r.constraint->max_version); - const system_package* sp (system_repository.find (r.name)); + assert (db.system_repository); + + const system_package* sp (db.system_repository->find (r.name)); // Will deal with all the duplicates later. // if (sp == nullptr || !sp->authoritative) - system_repository.insert (r.name, - *r.constraint->min_version, - true /* authoritative */); + { + assert (db.system_repository); + + db.system_repository->insert (r.name, + *r.constraint->min_version, + true /* authoritative */); + } break; } @@ -2761,9 +3103,13 @@ namespace bpkg // Create the unparsed package argument. // - auto arg_raw = [] (string v, pkg_options os, strings vs) -> pkg_arg + auto arg_raw = [] (database& db, + string v, + pkg_options os, + strings vs) -> pkg_arg { - return pkg_arg {package_scheme::none, + return pkg_arg {db, + package_scheme::none, package_name (), nullopt /* constraint */, move (v), @@ -2830,6 +3176,11 @@ namespace bpkg append (v, s); }; + auto add_num = [&add_string] (const char* o, auto v) + { + add_string (o, to_string (v)); + }; + const pkg_options& o (a.options); add_bool ("--keep-out", o.keep_out ()); @@ -2847,6 +3198,15 @@ namespace bpkg add_bool ("--checkout-purge", o.checkout_purge ()); + if (o.config_id_specified ()) + add_num ("--config-id", o.config_id ()); + + if (o.config_name_specified ()) + add_string ("--config-name", o.config_name ()); + + if (o.config_uuid_specified ()) + add_string ("--config-uuid", o.config_uuid ().string ()); + // Compose the option/variable group. // if (!s.empty () || !a.config_vars.empty ()) @@ -2879,7 +3239,7 @@ namespace bpkg // vector<shared_ptr<available_package>> stubs; - transaction t (db); + transaction t (mdb); // Don't fold the zero revision if building the package from source so // that we build the exact X+0 package revision if it is specified. @@ -2936,14 +3296,16 @@ namespace bpkg if (sys && vc) stubs.push_back (make_shared<available_package> (n)); - pkg_args.push_back (arg_package (sc, + pkg_args.push_back (arg_package (*ps.db, + sc, move (n), move (vc), move (ps.options), move (ps.config_vars))); } else // Add unparsed. - pkg_args.push_back (arg_raw (move (ps.packages), + pkg_args.push_back (arg_raw (*ps.db, + move (ps.packages), move (ps.options), move (ps.config_vars))); @@ -2955,7 +3317,7 @@ namespace bpkg // presence of --no-fetch option. // shared_ptr<repository> r ( - db.find<repository> (ps.location.canonical_name ())); + mdb.find<repository> (ps.location.canonical_name ())); if (r == nullptr) fail << "repository '" << ps.location @@ -2978,7 +3340,7 @@ namespace bpkg { using query = query<repository_fragment_package>; - for (const auto& rp: db.query<repository_fragment_package> ( + for (const auto& rp: mdb.query<repository_fragment_package> ( (query::repository_fragment::name == rf.fragment.load ()->name) + order_by_version_desc (query::package::id.version))) @@ -2993,7 +3355,7 @@ namespace bpkg if (ps.options.patch ()) { shared_ptr<selected_package> sp ( - db.find<selected_package> (nm)); + ps.db->find<selected_package> (nm)); // It seems natural in the presence of --patch option to only // patch the selected packages and not to build new packages if @@ -3044,7 +3406,8 @@ namespace bpkg info << "package " << pv.first << " is not present in " << "configuration"; else - pkg_args.push_back (arg_package (package_scheme::none, + pkg_args.push_back (arg_package (*ps.db, + package_scheme::none, pv.first, version_constraint (pv.second), ps.options, @@ -3100,12 +3463,14 @@ namespace bpkg optional<version_constraint> c; shared_ptr<selected_package> sp; + database& pdb (*ps.db); + if (!sys) { if (!vc) { if (ps.options.patch () && - (sp = db.find<selected_package> (n)) != nullptr) + (sp = pdb.find<selected_package> (n)) != nullptr) { c = patch_constraint (sp); @@ -3121,7 +3486,7 @@ namespace bpkg } shared_ptr<available_package> ap ( - find_available_one (db, n, c, rfs, false /* prereq */).first); + find_available_one (mdb, n, c, rfs, false /* prereq */).first); // Fail if no available package is found or only a stub is // available and we are building a source package. @@ -3133,7 +3498,7 @@ namespace bpkg // If the selected package is loaded then we aim to patch it. // if (sp != nullptr) - dr << "patch version for " << *sp << " is not found in " + dr << "patch version for " << *sp << pdb << " is not found in " << r->name; else if (ap == nullptr) dr << "package " << pkg << " is not found in " << r->name; @@ -3158,7 +3523,8 @@ namespace bpkg // Don't move options and variables as they may be reused. // - pkg_args.push_back (arg_package (sc, + pkg_args.push_back (arg_package (*ps.db, + sc, move (n), move (vc), ps.options, @@ -3184,14 +3550,14 @@ namespace bpkg // Check if the package is a duplicate. Return true if it is but // harmless. // - map<package_name, pkg_arg> package_map; + map<config_package, pkg_arg> package_map; auto check_dup = [&package_map, &arg_string, &arg_parsed] (const pkg_arg& pa) -> bool { assert (arg_parsed (pa)); - auto r (package_map.emplace (pa.name, pa)); + auto r (package_map.emplace (config_package {pa.db, pa.name}, pa)); const pkg_arg& a (r.first->second); assert (arg_parsed (a)); @@ -3216,9 +3582,10 @@ namespace bpkg return !r.second; }; - transaction t (db); + transaction t (mdb); - shared_ptr<repository_fragment> root (db.load<repository_fragment> ("")); + shared_ptr<repository_fragment> root ( + mdb.load<repository_fragment> ("")); // Here is what happens here: for unparsed package args we are going to // try and guess whether we are dealing with a package archive, package @@ -3226,13 +3593,15 @@ namespace bpkg // then as a directory, and then assume it is name/version. Sometimes, // however, it is really one of the first two but just broken. In this // case things are really confusing since we suppress all diagnostics - // for the first two "guesses". So what we are going to do here is re-run - // them with full diagnostics if the name/version guess doesn't pan out. + // for the first two "guesses". So what we are going to do here is + // re-run them with full diagnostics if the name/version guess doesn't + // pan out. // bool diag (false); for (auto i (pkg_args.begin ()); i != pkg_args.end (); ) { - pkg_arg& pa (*i); + pkg_arg& pa (*i); + database& pdb (pa.db); // Reduce all the potential variations (archive, directory, package // name, package name/version) to a single available_package object. @@ -3280,7 +3649,8 @@ namespace bpkg fail << "package archive '" << a << "' may not be built as a dependency"; - pa = arg_package (package_scheme::none, + pa = arg_package (pdb, + package_scheme::none, m.name, version_constraint (m.version), move (pa.options), @@ -3358,7 +3728,7 @@ namespace bpkg // if (optional<version> v = package_iteration (o, - c, + pdb, t, d, m.name, @@ -3366,7 +3736,8 @@ namespace bpkg true /* check_external */)) m.version = move (*v); - pa = arg_package (package_scheme::none, + pa = arg_package (pdb, + package_scheme::none, m.name, version_constraint (m.version), move (pa.options), @@ -3424,7 +3795,8 @@ namespace bpkg false /* allow_wildcard */, false /* fold_zero_revision */)); - pa = arg_package (package_scheme::none, + pa = arg_package (pdb, + package_scheme::none, move (n), move (vc), move (pa.options), @@ -3446,7 +3818,7 @@ namespace bpkg assert (!arg_sys (pa)); if (pa.options.patch () && - (sp = db.find<selected_package> (pa.name)) != nullptr) + (sp = pdb.find<selected_package> (pa.name)) != nullptr) { c = patch_constraint (sp); @@ -3465,7 +3837,7 @@ namespace bpkg else if (!arg_sys (pa)) c = pa.constraint; - auto rp (find_available_one (db, pa.name, c, root)); + auto rp (find_available_one (mdb, pa.name, c, root)); ap = move (rp.first); af = move (rp.second); } @@ -3503,7 +3875,7 @@ namespace bpkg l4 ([&]{trace << "stashing recursive package " << arg_string (pa);}); - rec_pkgs.push_back (recursive_package {pa.name, *u, *r}); + rec_pkgs.push_back (recursive_package {pdb, pa.name, *u, *r}); } } @@ -3519,23 +3891,24 @@ namespace bpkg // Make sure that the package is known. // auto apr (!pa.constraint || sys - ? find_available (db, pa.name, nullopt) - : find_available (db, pa.name, *pa.constraint)); + ? find_available (mdb, pa.name, nullopt) + : find_available (mdb, pa.name, *pa.constraint)); if (apr.empty ()) { diag_record dr (fail); dr << "unknown package " << arg_string (pa, false /* options */); - check_any_available (c, t, &dr); + check_any_available (mdb, t, &dr); } // Save before the name move. // - sp = db.find<selected_package> (pa.name); + sp = pdb.find<selected_package> (pa.name); dep_pkgs.push_back ( - dependency_package {move (pa.name), + dependency_package {pdb, + move (pa.name), move (pa.constraint), move (sp), sys, @@ -3558,10 +3931,10 @@ namespace bpkg // the same as the selected package). // if (sp == nullptr) - sp = db.find<selected_package> (pa.name); + sp = pdb.find<selected_package> (pa.name); if (sp != nullptr && sp->state == package_state::broken) - fail << "unable to build broken package " << pa.name << + fail << "unable to build broken package " << pa.name << pdb << info << "use 'pkg-purge --force' to remove"; bool found (true); @@ -3585,7 +3958,7 @@ namespace bpkg if (ap == nullptr) { if (pa.constraint && - find_available_one (db, + find_available_one (mdb, pa.name, nullopt, root).first != nullptr) @@ -3667,7 +4040,7 @@ namespace bpkg // Let's help the new user out here a bit. // - check_any_available (c, t, &dr); + check_any_available (mdb, t, &dr); } else { @@ -3690,7 +4063,7 @@ namespace bpkg { assert (sp != nullptr && sp->system () == arg_sys (pa)); - auto rp (make_available (o, c, db, sp)); + auto rp (make_available (o, pdb, sp)); ap = rp.first; af = rp.second; // Could be NULL (orphan). } @@ -3708,6 +4081,7 @@ namespace bpkg // build_package p { build_package::build, + pdb, move (sp), move (ap), move (af), @@ -3721,11 +4095,11 @@ namespace bpkg : optional<dir_path> ()), pa.options.checkout_purge (), move (pa.config_vars), - {package_name ()}, // Required by (command line). + {config_package {mdb, ""}}, // Required by (command line). 0}; // Adjustments. l4 ([&]{trace << "stashing held package " - << p.available_name_version ();}); + << p.available_name_version_db ();}); // "Fix" the version the user asked for by adding the constraint. // @@ -3733,7 +4107,8 @@ namespace bpkg // this build_package instance is never replaced). // if (pa.constraint) - p.constraints.emplace_back ("command line", move (*pa.constraint)); + p.constraints.emplace_back ( + mdb, "command line", move (*pa.constraint)); hold_pkgs.push_back (move (p)); } @@ -3741,6 +4116,10 @@ namespace bpkg // If this is just pkg-build -u|-p, then we are upgrading all held // packages. // + // Should we also upgrade the held packages in the explicitly associated + // configurations, recursively? Maybe later and we probably will need a + // command line option to enable this behavior. + // if (hold_pkgs.empty () && dep_pkgs.empty () && (o.upgrade () || o.patch ())) { @@ -3748,15 +4127,14 @@ namespace bpkg for (shared_ptr<selected_package> sp: pointer_result ( - db.query<selected_package> (query::state == "configured" && - query::hold_package))) + mdb.query<selected_package> (query::state == "configured" && + query::hold_package))) { // Let's skip upgrading system packages as they are, probably, // configured as such for a reason. // if (sp->system ()) continue; - const package_name& name (sp->name); optional<version_constraint> pc; @@ -3772,7 +4150,7 @@ namespace bpkg continue; } - auto apr (find_available_one (db, name, pc, root)); + auto apr (find_available_one (mdb, name, pc, root)); shared_ptr<available_package> ap (move (apr.first)); if (ap == nullptr || ap->stub ()) @@ -3788,7 +4166,7 @@ namespace bpkg // Let's help the new user out here a bit. // - check_any_available (c, t, &dr); + check_any_available (mdb, t, &dr); } // We will keep the output directory only if the external package is @@ -3797,23 +4175,24 @@ namespace bpkg bool keep_out (o.keep_out () && sp->external ()); build_package p { - build_package::build, + build_package::build, + mdb, move (sp), move (ap), move (apr.second), - true, // Hold package. - false, // Hold version. - {}, // Constraints. - false, // System package. + true, // Hold package. + false, // Hold version. + {}, // Constraints. + false, // System package. keep_out, - nullopt, // Checkout root. - false, // Checkout purge. - strings (), // Configuration variables. - {package_name ()}, // Required by (command line). - 0}; // Adjustments. + nullopt, // Checkout root. + false, // Checkout purge. + strings (), // Configuration variables. + {config_package {mdb, ""}}, // Required by (command line). + 0}; // Adjustments. l4 ([&]{trace << "stashing held package " - << p.available_name_version ();}); + << p.available_name_version_db ();}); hold_pkgs.push_back (move (p)); @@ -3822,7 +4201,7 @@ namespace bpkg // if (o.immediate () || o.recursive ()) rec_pkgs.push_back ( - recursive_package {name, o.upgrade (), o.recursive ()}); + recursive_package {mdb, name, o.upgrade (), o.recursive ()}); } } @@ -3885,14 +4264,15 @@ namespace bpkg { struct dep { - package_name name; // Empty if up/down-grade. + reference_wrapper<database> db; + package_name name; // Empty if up/down-grade. // Both are NULL if drop. // - shared_ptr<available_package> available; + shared_ptr<available_package> available; shared_ptr<bpkg::repository_fragment> repository_fragment; - bool system; + bool system; }; vector<dep> deps; @@ -3903,7 +4283,15 @@ namespace bpkg l4 ([&]{trace << "refining execution plan" << (scratch ? " from scratch" : "");}); - transaction t (db); + transaction t (mdb); + + // Save the total number of host configurations in the associated + // configurations cluster to later check if any private host + // configurations have been created during collection of the package + // builds (see below). + // + size_t host_configs ( + mdb.dependency_configs (true /* buildtime */).size ()); build_packages::postponed_packages postponed; @@ -3924,23 +4312,25 @@ namespace bpkg for (const dependency_package& p: dep_pkgs) { build_package bp { - nullopt, // Action. - nullptr, // Selected package. - nullptr, // Available package/repository frag. + nullopt, // Action. + p.db, + nullptr, // Selected package. + nullptr, // Available package/repository frag. nullptr, - false, // Hold package. - p.constraint.has_value (), // Hold version. - {}, // Constraints. + false, // Hold package. + p.constraint.has_value (), // Hold version. + {}, // Constraints. p.system, p.keep_out, p.checkout_root, p.checkout_purge, p.config_vars, - {package_name ()}, // Required by (command line). - 0}; // Adjustments. + {config_package {mdb, ""}}, // Required by (command line). + 0}; // Adjustments. if (p.constraint) - bp.constraints.emplace_back ("command line", *p.constraint); + bp.constraints.emplace_back ( + mdb, "command line", *p.constraint); pkgs.enter (p.name, move (bp)); } @@ -3950,12 +4340,12 @@ namespace bpkg // specify packages on the command line does not matter). // for (const build_package& p: hold_pkgs) - pkgs.collect_build (o, c, db, p); + pkgs.collect_build (o, p); // Collect all the prerequisites of the user selection. // for (const build_package& p: hold_pkgs) - pkgs.collect_build_prerequisites (o, c, db, p.name (), postponed); + pkgs.collect_build_prerequisites (o, p.db, p.name (), postponed); // Note that we need to collect unheld after prerequisites, not to // overwrite the pre-entered entries before they are used to provide @@ -3964,7 +4354,7 @@ namespace bpkg for (const dependency_package& p: dep_pkgs) { if (p.selected != nullptr && p.selected->hold_package) - pkgs.collect_unhold (p.selected); + pkgs.collect_unhold (p.db, p.selected); } scratch = false; @@ -3977,12 +4367,16 @@ namespace bpkg // for (const dep& d: deps) { + database& ddb (d.db); + if (d.available == nullptr) - pkgs.collect_drop (db.load<selected_package> (d.name)); + { + pkgs.collect_drop (ddb, ddb.load<selected_package> (d.name)); + } else { shared_ptr<selected_package> sp ( - db.find<selected_package> (d.name)); + ddb.find<selected_package> (d.name)); // We will keep the output directory only if the external package // is replaced with an external one (see above for details). @@ -3997,28 +4391,41 @@ namespace bpkg // build_package p { build_package::build, + ddb, move (sp), d.available, d.repository_fragment, - nullopt, // Hold package. - nullopt, // Hold version. - {}, // Constraints. + nullopt, // Hold package. + nullopt, // Hold version. + {}, // Constraints. d.system, keep_out, - nullopt, // Checkout root. - false, // Checkout purge. - strings (), // Configuration variables. - {package_name ()}, // Required by (command line). - 0}; // Adjustments. + nullopt, // Checkout root. + false, // Checkout purge. + strings (), // Configuration variables. + {config_package {mdb, ""}}, // Required by (command line). + 0}; // Adjustments. - pkgs.collect_build (o, c, db, p, &postponed /* recursively */); + pkgs.collect_build (o, move (p), &postponed /* recursively */); } } // Handle the (combined) postponed collection. // if (!postponed.empty ()) - pkgs.collect_build_postponed (o, c, db, postponed); + pkgs.collect_build_postponed (o, postponed); + + // If any private host configurations have been created while + // collecting the package builds, then commit the new associations and + // restart the transaction (there should be no changes other than + // that). + // + if (mdb.dependency_configs (true /* buildtime */).size () != + host_configs) + { + t.commit (); + t.start (mdb); + } // Now that we have collected all the package versions that we need to // build, arrange them in the "dependency order", that is, with every @@ -4034,16 +4441,19 @@ namespace bpkg // appear (e.g., on the plan) last. // for (const dep& d: deps) - pkgs.order (d.name, false /* reorder */); + pkgs.order (d.db, + d.name, + nullopt /* buildtime */, + false /* reorder */); for (const build_package& p: reverse_iterate (hold_pkgs)) - pkgs.order (p.name ()); + pkgs.order (p.db, p.name (), nullopt /* buildtime */); // Collect and order all the dependents that we will need to // reconfigure because of the up/down-grades of packages that are now // on the list. // - pkgs.collect_order_dependents (db); + pkgs.collect_order_dependents (); // And, finally, make sure all the packages that we need to unhold // are on the list. @@ -4051,7 +4461,10 @@ namespace bpkg for (const dependency_package& p: dep_pkgs) { if (p.selected != nullptr && p.selected->hold_package) - pkgs.order (p.name, false /* reorder */); + pkgs.order (p.db, + p.name, + nullopt /* buildtime */, + false /* reorder */); } // We are about to execute the plan on the database (but not on the @@ -4060,36 +4473,60 @@ namespace bpkg // below for details). // using selected_packages = session::object_map<selected_package>; - auto selected_packages_session = [&db, &ses] () -> selected_packages* + auto sp_session = [] (const auto& tm) -> selected_packages* { - auto& m (ses.map ()[&db]); - auto i (m.find (&typeid (selected_package))); - return (i != m.end () + auto i (tm.find (&typeid (selected_package))); + return (i != tm.end () ? &static_cast<selected_packages&> (*i->second) : nullptr); }; - selected_packages old_sp; - if (const selected_packages* sp = selected_packages_session ()) - old_sp = *sp; + map<const odb::database*, selected_packages> old_sp; + + for (const auto& dps: ses.map ()) + { + if (const selected_packages* sps = sp_session (dps.second)) + old_sp.emplace (dps.first, *sps); + } // Note that we need to perform the execution on the copies of the // build/drop_package objects to preserve the original ones. The // selected_package objects will still be changed so we will reload // them afterwards (see below). // + // After the plan execution, save the dependents (selected packages + // with prerequisites) for the subsequent built package dependency + // hierarchies verification. + // + bool changed; + vector<pair<database&, shared_ptr<selected_package>>> dependents; { vector<build_package> tmp (pkgs.begin (), pkgs.end ()); build_package_list bl (tmp.begin (), tmp.end ()); - execute_plan (o, c, db, bl, true /* simulate */); + changed = execute_plan (o, bl, true /* simulate */); + + if (changed) + { + for (build_package& p: bl) + { + if (p.selected != nullptr) + { + if (!p.selected->prerequisites.empty ()) + dependents.emplace_back (p.db, move (p.selected)); + } + else + assert (p.action && *p.action == build_package::drop); + } + } } // Return nullopt if no changes to the dependency are necessary. This // value covers both the "no change is required" and the "no // recommendation available" cases. // - auto eval_dep = [&db, &dep_pkgs, &rec_pkgs] ( + auto eval_dep = [&dep_pkgs, &rec_pkgs] ( + database& db, const shared_ptr<selected_package>& sp, bool ignore_unsatisfiable = true) -> optional<evaluate_result> { @@ -4098,7 +4535,7 @@ namespace bpkg // See if there is an optional dependency upgrade recommendation. // if (!sp->hold_package) - r = evaluate_dependency (db, dep_pkgs, sp, ignore_unsatisfiable); + r = evaluate_dependency (db, sp, dep_pkgs, ignore_unsatisfiable); // If none, then see for the recursive dependency upgrade // recommendation. @@ -4107,7 +4544,7 @@ namespace bpkg // configured as such for a reason. // if (!r && !sp->system () && !rec_pkgs.empty ()) - r = evaluate_recursive (db, rec_pkgs, sp, ignore_unsatisfiable); + r = evaluate_recursive (db, sp, rec_pkgs, ignore_unsatisfiable); // Translate the "no change" result to nullopt. // @@ -4117,16 +4554,18 @@ namespace bpkg // The empty version means that the package must be dropped. // const version ev; - auto target_version = [&ev] (const shared_ptr<available_package>& ap, - bool sys) -> const version& + auto target_version = [&ev] + (database& db, + const shared_ptr<available_package>& ap, + bool sys) -> const version& { if (ap == nullptr) return ev; if (sys) { - assert (ap->system_version () != nullptr); - return *ap->system_version (); + assert (ap->system_version (db) != nullptr); + return *ap->system_version (db); } return ap->version; @@ -4139,15 +4578,17 @@ namespace bpkg { bool s (false); + database& db (i->db); + // Here we scratch if evaluate changed its mind or if the resulting // version doesn't match what we expect it to be. // if (auto sp = db.find<selected_package> (i->name)) { - const version& dv (target_version (i->available, i->system)); + const version& dv (target_version (db, i->available, i->system)); - if (optional<evaluate_result> r = eval_dep (sp)) - s = dv != target_version (r->available, r->system) || + if (optional<evaluate_result> r = eval_dep (db, sp)) + s = dv != target_version (db, r->available, r->system) || i->system != r->system; else s = dv != sp->version || i->system != sp->system (); @@ -4164,7 +4605,24 @@ namespace bpkg ++i; } - if (!scratch) + // If the execute_plan() call was noop, there are no user expectations + // regarding any dependency, and no upgrade is requested, then the + // only possible refinement outcome can be recommendations to drop + // unused dependencies (that the user has refused to drop on the + // previous build or drop command run). Thus, if the --keep-unused|-K + // or --no-refinement option is also specified, then we omit the + // need_refinement() call altogether and assume that no refinement is + // required. + // + if (!changed && dep_pkgs.empty () && rec_pkgs.empty ()) + { + assert (!scratch); // No reason to change any previous decision. + + if (o.keep_unused () || o.no_refinement ()) + refine = false; + } + + if (!scratch && refine) { // First, we check if the refinement is required, ignoring the // unsatisfiable dependency version constraints. If we end up @@ -4174,12 +4632,12 @@ namespace bpkg // make sure that the unsatisfiable dependency, if left, is // reported. // - auto need_refinement = [&eval_dep, &deps, &rec_pkgs, &db, &o] ( + auto need_refinement = [&eval_dep, &deps, &rec_pkgs, &mdb, &o] ( bool diag = false) -> bool { // Examine the new dependency set for any up/down-grade/drops. // - bool r (false); // Presumably no more refinments are necessary. + bool r (false); // Presumably no more refinements are necessary. using query = query<selected_package>; @@ -4188,22 +4646,32 @@ namespace bpkg if (rec_pkgs.empty ()) q = q && !query::hold_package; - for (shared_ptr<selected_package> sp: - pointer_result (db.query<selected_package> (q))) + // It seems right to only evaluate dependencies in the explicitly + // associated configurations, recursively. Indeed, we shouldn't be + // up/down-grading or dropping packages in configurations that + // only contain dependents, some of which we may only reconfigure. + // + for (database& adb: mdb.dependency_configs ()) { - if (optional<evaluate_result> er = eval_dep (sp, !diag)) + for (shared_ptr<selected_package> sp: + pointer_result (adb.query<selected_package> (q))) { - // Skip unused if we were instructed to keep them. - // - if (o.keep_unused () && er->available == nullptr) - continue; + if (optional<evaluate_result> er = eval_dep (adb, sp, !diag)) + { + // Skip unused if we were instructed to keep them. + // + if (o.keep_unused () && er->available == nullptr) + continue; + + if (!diag) + deps.push_back (dep {adb, + sp->name, + move (er->available), + move (er->repository_fragment), + er->system}); - if (!diag) - deps.push_back (dep {sp->name, - move (er->available), - move (er->repository_fragment), - er->system}); - r = true; + r = true; + } } } @@ -4216,18 +4684,170 @@ namespace bpkg need_refinement (true /* diag */); } + // Note that we prevent building multiple instances of the same + // package dependency in different configurations (of the same type) + // while creating the build plan. However, we may potentially end up + // with the same dependency in multiple configurations since we do not + // descend into prerequisites of already configured packages which + // require no up/downgrade. + // + // To prevent this, we additionally verify that none of the built + // package dependency hierarchies contain the same runtime dependency, + // built in multiple configurations. + // + if (changed && !refine) + { + // Verify the specified package dependency hierarchy and return the + // set of packages plus their runtime dependencies, including + // indirect ones. Fail if a dependency cycle is detected. + // + // Note that all the encountered dependency sub-hierarchies that + // reside in configurations of different types (or beneath them) are + // also verified but not included into the resulting set. + // + using prerequisites = set<lazy_shared_ptr<selected_package>, + compare_lazy_ptr>; + + map<config_package, prerequisites> cache; + small_vector<config_selected_package, 16> chain; + + auto verify_dependencies = [&cache, &chain] + (database& db, + shared_ptr<selected_package> sp, + const auto& verify_dependencies) + -> const prerequisites& + { + // Return the cached value, if present. + // + config_package cp {db, sp->name}; + { + auto i (cache.find (cp)); + + if (i != cache.end ()) + return i->second; + } + + // Make sure there is no dependency cycle. + // + config_selected_package csp {db, sp}; + { + auto i (find (chain.begin (), chain.end (), csp)); + + if (i != chain.end ()) + { + diag_record dr (fail); + dr << "dependency cycle detected involving package " << *sp + << db; + + // Note: push_back() can invalidate the iterator. + // + size_t j (i - chain.begin ()); + + for (chain.push_back (csp); j != chain.size () - 1; ++j) + dr << info << *chain[j].package << chain[j].db + << " depends on " + << *chain[j + 1].package << chain[j + 1].db; + } + } + + chain.push_back (csp); + + // Verify all prerequisites, but only collect those that are from + // configurations of the same type. + // + // Indeed, we don't care if an associated host configuration + // contains a configured package that we also have configured in + // our target configuration. It's also fine if some of our runtime + // dependencies from different configurations build-time depend on + // the same package (of potentially different versions) configured + // in different host configurations. + // + prerequisites r; + const package_prerequisites& prereqs (sp->prerequisites); + + for (const auto& prereq: prereqs) + { + const lazy_shared_ptr<selected_package>& p (prereq.first); + database& pdb (p.database ()); + + // Validate prerequisite sub-hierarchy also in configuration of + // different type but do not collect it. + // + const prerequisites& ps ( + verify_dependencies (pdb, p.load (), verify_dependencies)); + + if (pdb.type != db.type) + continue; + + // Collect prerequisite sub-hierarchy, checking that none of the + // packages are already collected. + // + for (const lazy_shared_ptr<selected_package>& p: ps) + { + // Note: compare_lazy_ptr only considers package names. + // + auto i (r.find (p)); + + if (i != r.end ()) + { + database& db1 (p.database ()); + database& db2 (i->database ()); + + if (db1 != db2) + { + bool indirect (prereqs.find (p) == prereqs.end ()); + + fail << "package " << p.object_id () + << (indirect ? " indirectly" : "") << " required by " + << *sp << db << " is configured in multiple " + << "configurations" << + info << *p.load () << db1 << + info << *i->load () << db2; + } + } + else + r.insert (p); + } + } + + chain.pop_back (); + + // Collect the dependent package itself. + // + r.insert (lazy_shared_ptr<selected_package> (db, move (sp))); + + // Cache the resulting package prerequisites set and return a + // reference to it. + // + auto j (cache.emplace (move (cp), move (r))); + assert (j.second); // A package cannot depend on itself. + + return j.first->second; + }; + + for (auto& p: dependents) + verify_dependencies (p.first, + move (p.second), + verify_dependencies); + } + // Rollback the changes to the database and reload the changed // selected_package objects. // t.rollback (); { - transaction t (db); + transaction t (mdb); // First reload all the selected_package object that could have been // modified (conceptually, we should only modify what's on the // plan). And in case of drop the object is removed from the session // so we need to bring it back. // + // Make sure that selected packages are only owned by the session + // and the build package list. + // + dependents.clear (); + // Note: we use the original pkgs list since the executed ones may // contain newly created (but now gone) selected_package objects. // @@ -4235,58 +4855,112 @@ namespace bpkg { assert (p.action); + database& pdb (p.db); + if (*p.action == build_package::drop) { assert (p.selected != nullptr); ses.cache_insert<selected_package> ( - db, p.selected->name, p.selected); + pdb, p.selected->name, p.selected); } if (p.selected != nullptr) - db.reload (*p.selected); + pdb.reload (*p.selected); } // Now remove all the newly created selected_package objects from // the session. The tricky part is to distinguish newly created ones // from newly loaded (and potentially cached). // - if (selected_packages* sp = selected_packages_session ()) + for (bool rescan (true); rescan; ) { - for (bool rescan (true); rescan; ) - { - rescan = false; + rescan = false; - for (auto i (sp->begin ()); i != sp->end (); ) + for (const auto& dps: ses.map ()) + { + if (selected_packages* sps = sp_session (dps.second)) { - bool erased (false); - auto j (old_sp.find (i->first)); - + auto j (old_sp.find (dps.first)); // Find the database. + + // Note that if a database has been introduced only during + // simulation, then we could just clear all its selected + // packages in one shot. Let's however, be cautious and remove + // them iteratively to make sure that none of them are left at + // the end (no more rescan is necessary). If any of them is + // left, then that would mean that is is referenced from + // somewhere besides the session object, which would be a bug. + // if (j == old_sp.end ()) { - if (i->second.use_count () == 1) + if (!sps->empty ()) + { + for (auto i (sps->begin ()); i != sps->end (); ) + { + if (i->second.use_count () == 1) + { + // This might cause another object's use count to drop. + // + i = sps->erase (i); + rescan = true; + } + else + ++i; + } + } + + continue; + } + + const selected_packages& osp (j->second); + + for (auto i (sps->begin ()); i != sps->end (); ) + { + bool erased (false); + auto j (osp.find (i->first)); + + if (j == osp.end ()) + { + if (i->second.use_count () == 1) + { + // This might cause another object's use count to drop. + // + i = sps->erase (i); + erased = true; + rescan = true; + } + } + // It may also happen that the object was erased from the + // database and then recreated. In this case we restore the + // pointer that is stored in the session. + // + else if (i->second != j->second) { // This might cause another object's use count to drop. // - i = sp->erase (i); - erased = true; + i->second = j->second; rescan = true; } + + if (!erased) + ++i; } - // It may also happen that the object was erased from the - // database and then recreated. In this case we restore the - // pointer that is stored in the session. - // - else if (i->second != j->second) + } + } + + // Verify that all the selected packages of the newly introduced + // during simulation databases are erased (see above for the + // verification reasoning). + // + if (!rescan) + { + for (const auto& dps: ses.map ()) + { + if (const selected_packages* sps = sp_session (dps.second)) { - // This might cause another object's use count to drop. - // - i->second = j->second; - rescan = true; + if (old_sp.find (dps.first) == old_sp.end ()) + assert (sps->empty ()); } - - if (!erased) - ++i; } } } @@ -4317,6 +4991,7 @@ namespace bpkg for (const build_package& p: reverse_iterate (pkgs)) { + database& pdb (p.db); const shared_ptr<selected_package>& sp (p.selected); string act; @@ -4325,7 +5000,7 @@ namespace bpkg if (*p.action == build_package::drop) { - act = "drop " + sp->string () + " (unused)"; + act = "drop " + sp->string (pdb) + " (unused)"; need_prompt = true; } else @@ -4361,6 +5036,10 @@ namespace bpkg } act += ' ' + sp->name.string (); + + string s (pdb.string ()); + if (!s.empty ()) + act += ' ' + s; } else { @@ -4403,15 +5082,15 @@ namespace bpkg if (p.unhold ()) act += "/unhold"; - act += ' ' + p.available_name_version (); + act += ' ' + p.available_name_version_db (); cause = "required by"; } string rb; if (!p.user_selection ()) { - for (const package_name& n: p.required_by) - rb += ' ' + n.string (); + for (const config_package& cp: p.required_by) + rb += ' ' + cp.string (); // If not user-selected, then there should be another (implicit) // reason for the action. @@ -4491,7 +5170,7 @@ namespace bpkg // prerequsites got upgraded/downgraded and that the user may want to in // addition update (that update_dependents flag above). // - execute_plan (o, c, db, pkgs, false /* simulate */); + execute_plan (o, pkgs, false /* simulate */); if (o.configure_only ()) return 0; @@ -4512,11 +5191,14 @@ namespace bpkg if (*p.action != build_package::build) continue; + database& db (p.db); const shared_ptr<selected_package>& sp (p.selected); if (!sp->system () && // System package doesn't need update. p.user_selection ()) - upkgs.push_back (pkg_command_vars {sp, + upkgs.push_back (pkg_command_vars {db.config_orig, + db.main (), + sp, strings () /* vars */, false /* cwd */}); } @@ -4530,28 +5212,30 @@ namespace bpkg { assert (p.action); + database& db (p.db); + if (*p.action == build_package::adjust && p.reconfigure ()) - upkgs.push_back (pkg_command_vars {p.selected, + upkgs.push_back (pkg_command_vars {db.config_orig, + db.main (), + p.selected, strings () /* vars */, false /* cwd */}); } } - pkg_update (c, o, o.for_ (), strings (), upkgs); + pkg_update (o, o.for_ (), strings (), upkgs); if (verb && !o.no_result ()) { for (const pkg_command_vars& pv: upkgs) - text << "updated " << *pv.pkg; + text << "updated " << pv.string (); } return 0; } - static void + static bool execute_plan (const pkg_build_options& o, - const dir_path& c, - database& db, build_package_list& build_pkgs, bool simulate) { @@ -4559,6 +5243,7 @@ namespace bpkg l4 ([&]{trace << "simulate: " << (simulate ? "yes" : "no");}); + bool r (false); uint16_t verbose (!simulate ? verb : 0); // disfigure @@ -4573,12 +5258,13 @@ namespace bpkg if (*p.action != build_package::drop && !p.reconfigure ()) continue; + database& pdb (p.db); shared_ptr<selected_package>& sp (p.selected); // Each package is disfigured in its own transaction, so that we // always leave the configuration in a valid state. // - transaction t (db, !simulate /* start */); + transaction t (pdb, !simulate /* start */); // Reset the flag if the package being unpacked is not an external one. // @@ -4612,7 +5298,9 @@ namespace bpkg // Commits the transaction. // - pkg_disfigure (c, o, t, sp, !p.keep_out, simulate); + pkg_disfigure (o, pdb, t, sp, !p.keep_out, simulate); + + r = true; assert (sp->state == package_state::unpacked || sp->state == package_state::transient); @@ -4620,7 +5308,7 @@ namespace bpkg if (verbose && !o.no_result ()) text << (sp->state == package_state::transient ? "purged " - : "disfigured ") << *sp; + : "disfigured ") << *sp << pdb; // Selected system package is now gone from the database. Before we drop // the object we need to make sure the hold state is preserved in the @@ -4644,6 +5332,8 @@ namespace bpkg { assert (p.action); + database& pdb (p.db); + shared_ptr<selected_package>& sp (p.selected); const shared_ptr<available_package>& ap (p.available); @@ -4661,11 +5351,13 @@ namespace bpkg { assert (!sp->system ()); - transaction t (db, !simulate /* start */); - pkg_purge (c, t, sp, simulate); // Commits the transaction. + transaction t (pdb, !simulate /* start */); + pkg_purge (pdb, t, sp, simulate); // Commits the transaction. + + r = true; if (verbose && !o.no_result ()) - text << "purged " << *sp; + text << "purged " << *sp << pdb; sp = nullptr; } @@ -4690,11 +5382,13 @@ namespace bpkg { if (sp != nullptr && !sp->system ()) { - transaction t (db, !simulate /* start */); - pkg_purge (c, t, sp, simulate); // Commits the transaction. + transaction t (pdb, !simulate /* start */); + pkg_purge (pdb, t, sp, simulate); // Commits the transaction. + + r = true; if (verbose && !o.no_result ()) - text << "purged " << *sp; + text << "purged " << *sp << pdb; if (!p.hold_package) p.hold_package = sp->hold_package; @@ -4721,7 +5415,7 @@ namespace bpkg if (pl.repository_fragment.object_id () != "") // Special root? { - transaction t (db, !simulate /* start */); + transaction t (pdb, !simulate /* start */); // Go through package repository fragments to decide if we should // fetch, checkout or unpack depending on the available repository @@ -4754,7 +5448,7 @@ namespace bpkg case repository_basis::archive: { sp = pkg_fetch (o, - c, + pdb, t, ap->id.name, p.available_version (), @@ -4766,7 +5460,7 @@ namespace bpkg { sp = p.checkout_root ? pkg_checkout (o, - c, + pdb, t, ap->id.name, p.available_version (), @@ -4775,7 +5469,7 @@ namespace bpkg p.checkout_purge, simulate) : pkg_checkout (o, - c, + pdb, t, ap->id.name, p.available_version (), @@ -4786,7 +5480,7 @@ namespace bpkg case repository_basis::directory: { sp = pkg_unpack (o, - c, + pdb, t, ap->id.name, p.available_version (), @@ -4800,11 +5494,11 @@ namespace bpkg // else if (exists (pl.location)) { - transaction t (db, !simulate /* start */); + transaction t (pdb, !simulate /* start */); sp = pkg_fetch ( o, - c, + pdb, t, pl.location, // Archive path. true, // Replace @@ -4814,6 +5508,8 @@ namespace bpkg if (sp != nullptr) // Actually fetched or checked out something? { + r = true; + assert (sp->state == package_state::fetched || sp->state == package_state::unpacked); @@ -4833,19 +5529,19 @@ namespace bpkg case repository_basis::archive: { assert (sp->state == package_state::fetched); - dr << "fetched " << *sp; + dr << "fetched " << *sp << pdb; break; } case repository_basis::directory: { assert (sp->state == package_state::unpacked); - dr << "using " << *sp << " (external)"; + dr << "using " << *sp << pdb << " (external)"; break; } case repository_basis::version_control: { assert (sp->state == package_state::unpacked); - dr << "checked out " << *sp; + dr << "checked out " << *sp << pdb; break; } } @@ -4860,23 +5556,23 @@ namespace bpkg { if (sp != nullptr) { - transaction t (db, !simulate /* start */); + transaction t (pdb, !simulate /* start */); // Commits the transaction. // - sp = pkg_unpack (o, c, t, ap->id.name, simulate); + sp = pkg_unpack (o, pdb, t, ap->id.name, simulate); if (verbose && !o.no_result ()) - text << "unpacked " << *sp; + text << "unpacked " << *sp << pdb; } else { const package_location& pl (ap->locations[0]); assert (pl.repository_fragment.object_id () == ""); // Special root. - transaction t (db, !simulate /* start */); + transaction t (pdb, !simulate /* start */); sp = pkg_unpack (o, - c, + pdb, t, path_cast<dir_path> (pl.location), true, // Replace. @@ -4884,9 +5580,11 @@ namespace bpkg simulate); if (verbose && !o.no_result ()) - text << "using " << *sp << " (external)"; + text << "using " << *sp << pdb << " (external)"; } + r = true; + assert (sp->state == package_state::unpacked); } @@ -4919,14 +5617,25 @@ namespace bpkg if (sp != nullptr && sp->state == package_state::configured) continue; - transaction t (db, !simulate /* start */); + database& pdb (p.db); + + transaction t (pdb, !simulate /* start */); // Note that pkg_configure() commits the transaction. // if (p.system) - sp = pkg_configure_system (ap->id.name, p.available_version (), t); + sp = pkg_configure_system (ap->id.name, + p.available_version (), + pdb, + t); else if (ap != nullptr) - pkg_configure (c, o, t, sp, ap->dependencies, p.config_vars, simulate); + pkg_configure (o, + pdb, + t, + sp, + ap->dependencies, + p.config_vars, + simulate); else // Dependent. { // Must be in the unpacked state since it was disfigured on the first @@ -4935,12 +5644,12 @@ namespace bpkg assert (sp->state == package_state::unpacked); package_manifest m ( - pkg_verify (sp->effective_src_root (c), + pkg_verify (sp->effective_src_root (pdb.config_orig), true /* ignore_unknown */, [&sp] (version& v) {v = sp->version;})); - pkg_configure (c, - o, + pkg_configure (o, + p.db, t, sp, convert (move (m.dependencies)), @@ -4948,10 +5657,12 @@ namespace bpkg simulate); } + r = true; + assert (sp->state == package_state::configured); if (verbose && !o.no_result ()) - text << "configured " << *sp; + text << "configured " << *sp << pdb; } // Update the hold state. @@ -4966,6 +5677,8 @@ namespace bpkg if (*p.action == build_package::drop) continue; + database& pdb (p.db); + const shared_ptr<selected_package>& sp (p.selected); assert (sp != nullptr); @@ -4986,19 +5699,23 @@ namespace bpkg sp->hold_package = hp; sp->hold_version = hv; - transaction t (db, !simulate /* start */); - db.update (sp); + transaction t (pdb, !simulate /* start */); + pdb.update (sp); t.commit (); + r = true; + if (verbose > 1) { if (hp) - text << "holding package " << sp->name; + text << "holding package " << sp->name << pdb; if (hv) - text << "holding version " << *sp; + text << "holding version " << *sp << pdb; } } } + + return r; } } |