From 636d69c1b740d8975bb7c7a3b518c280ac224545 Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Fri, 16 Jun 2023 18:17:07 +0300 Subject: Add --mask-repository pkg-build option --- bpkg/database.hxx | 17 +- bpkg/package-query.cxx | 119 ++++--- bpkg/package.cxx | 3 +- bpkg/pkg-build-collect.cxx | 28 +- bpkg/pkg-build.cli | 13 + bpkg/pkg-build.cxx | 53 ++- bpkg/pkg-checkout.cxx | 16 +- bpkg/pkg-fetch.cxx | 16 +- bpkg/pkg-unpack.cxx | 4 +- bpkg/rep-mask.cxx | 266 ++++++++++++++ bpkg/rep-mask.hxx | 66 ++++ bpkg/rep-remove.cxx | 105 ++++-- tests/pkg-build.testscript | 864 ++++++++++++++++++++++++++++++++++++++++++--- 13 files changed, 1419 insertions(+), 151 deletions(-) create mode 100644 bpkg/rep-mask.cxx create mode 100644 bpkg/rep-mask.hxx diff --git a/bpkg/database.hxx b/bpkg/database.hxx index 3887d99..7ef2d40 100644 --- a/bpkg/database.hxx +++ b/bpkg/database.hxx @@ -717,9 +717,10 @@ namespace bpkg public small_vector, V>, 16> { public: - using value_type = pair, V>; - using base_type = small_vector; - using iterator = typename base_type::iterator; + using value_type = pair, V>; + using base_type = small_vector; + using iterator = typename base_type::iterator; + using const_iterator = typename base_type::const_iterator; using base_type::begin; using base_type::end; @@ -734,6 +735,16 @@ namespace bpkg }); } + const_iterator + find (database& db) const + { + return find_if (begin (), end (), + [&db] (const value_type& i) -> bool + { + return i.first == db; + }); + } + pair insert (database& db, V&& v) { diff --git a/bpkg/package-query.cxx b/bpkg/package-query.cxx index 9675d97..9705579 100644 --- a/bpkg/package-query.cxx +++ b/bpkg/package-query.cxx @@ -6,6 +6,7 @@ #include #include #include +#include using namespace std; @@ -159,6 +160,9 @@ namespace bpkg { const lazy_shared_ptr& lrf (pl.repository_fragment); + if (rep_masked_fragment (lrf)) + continue; + // First check the repository itself. // if (lrf.object_id () == rf->name) @@ -175,20 +179,26 @@ namespace bpkg for (const lazy_weak_ptr& r: cs) { - const auto& frs (r.load ()->fragments); + if (!rep_masked (r)) + { + const auto& frs (r.load ()->fragments); - if (find_if (frs.begin (), frs.end (), pr) != frs.end ()) - return lrf.load (); + if (find_if (frs.begin (), frs.end (), pr) != frs.end ()) + return lrf.load (); + } } if (prereq) { for (const lazy_weak_ptr& r: ps) { - const auto& frs (r.load ()->fragments); + if (!rep_masked (r)) + { + const auto& frs (r.load ()->fragments); - if (find_if (frs.begin (), frs.end (), pr) != frs.end ()) - return lrf.load (); + if (find_if (frs.begin (), frs.end (), pr) != frs.end ()) + return lrf.load (); + } } } } @@ -198,14 +208,17 @@ namespace bpkg // for (const lazy_weak_ptr& cr: cs) { - for (const auto& fr: cr.load ()->fragments) + if (!rep_masked (cr)) { - // Should we consider prerequisites of our complements as our - // prerequisites? I'd say not. - // - if (shared_ptr r = - find (fr.fragment.load (), ap, chain, false)) - return r; + for (const auto& fr: cr.load ()->fragments) + { + // Should we consider prerequisites of our complements as our + // prerequisites? I'd say not. + // + if (shared_ptr r = + find (fr.fragment.load (), ap, chain, false)) + return r; + } } } @@ -213,11 +226,14 @@ namespace bpkg { for (const lazy_weak_ptr& pr: ps) { - for (const auto& fr: pr.load ()->fragments) + if (!rep_masked (pr)) { - if (shared_ptr r = - find (fr.fragment.load (), ap, chain, false)) - return r; + for (const auto& fr: pr.load ()->fragments) + { + if (shared_ptr r = + find (fr.fragment.load (), ap, chain, false)) + return r; + } } } } @@ -345,15 +361,20 @@ namespace bpkg for (shared_ptr ap: pointer_result (query_available (db, name, c))) { - // An available package should come from at least one fetched - // repository fragment. + // All repository fragments the package comes from are equally good, + // so we pick the first unmasked one. // - assert (!ap->locations.empty ()); + for (const auto& pl: ap->locations) + { + const lazy_shared_ptr& lrf ( + pl.repository_fragment); - // All repository fragments the package comes from are equally good, so - // we pick the first one. - // - r.emplace_back (move (ap), ap->locations[0].repository_fragment); + if (!rep_masked_fragment (lrf)) + { + r.emplace_back (move (ap), lrf); + break; + } + } } } @@ -415,6 +436,8 @@ namespace bpkg const lazy_shared_ptr& rf, bool prereq) { + assert (!rep_masked_fragment (rf)); + vector> r; database& db (rf.database ()); @@ -438,6 +461,8 @@ namespace bpkg bool prereq, bool revision) { + assert (!rep_masked_fragment (rf)); + // Filter the result based on the repository fragment to which each // version belongs. // @@ -527,17 +552,22 @@ namespace bpkg const shared_ptr& sp) { available_package_id pid (sp->name, sp->version); + const string& cn (sp->repository_fragment.canonical_name ()); + for (database& ddb: dependent_repo_configs (db)) { shared_ptr ap (ddb.find (pid)); if (ap != nullptr && !ap->stub ()) { - if (shared_ptr f = ddb.find ( - sp->repository_fragment.canonical_name ())) - return make_pair (ap, - lazy_shared_ptr (ddb, - move (f))); + if (shared_ptr f = + ddb.find (cn)) + { + if (!rep_masked_fragment (ddb, f)) + return make_pair (ap, + lazy_shared_ptr (ddb, + move (f))); + } } } @@ -592,15 +622,20 @@ namespace bpkg pointer_result ( query_available (db, name, nullopt /* version_constraint */))) { - // An available package should come from at least one fetched - // repository fragment. + // All repository fragments the package comes from are equally good, + // so we pick the first unmasked one. // - assert (!ap->locations.empty ()); + for (const auto& pl: ap->locations) + { + const lazy_shared_ptr& lrf ( + pl.repository_fragment); - // All repository fragments the package comes from are equally good, so - // we pick the first one. - // - r.emplace_back (move (ap), ap->locations[0].repository_fragment); + if (!rep_masked_fragment (lrf)) + { + r.emplace_back (move (ap), lrf); + break; + } + } } } @@ -638,14 +673,18 @@ namespace bpkg // anyway. // lazy_shared_ptr rf; + const string& cn (sp->repository_fragment.canonical_name ()); for (database& ddb: dependent_repo_configs (db)) { - if (shared_ptr f = ddb.find ( - sp->repository_fragment.canonical_name ())) + if (shared_ptr f = + ddb.find (cn)) { - rf = lazy_shared_ptr (ddb, move (f)); - break; + if (!rep_masked_fragment (ddb, f)) + { + rf = lazy_shared_ptr (ddb, move (f)); + break; + } } } diff --git a/bpkg/package.cxx b/bpkg/package.cxx index c98e7a4..56f4221 100644 --- a/bpkg/package.cxx +++ b/bpkg/package.cxx @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -373,7 +374,7 @@ namespace bpkg { const shared_ptr& rf (prf.repository_fragment); - if (rf->location.directory_based ()) + if (!rep_masked_fragment (db, rf) && rf->location.directory_based ()) fail << "external package " << n << '/' << v << " is already available from " << rf->location.canonical_name (); diff --git a/bpkg/pkg-build-collect.cxx b/bpkg/pkg-build-collect.cxx index 86dcb24..036e5b6 100644 --- a/bpkg/pkg-build-collect.cxx +++ b/bpkg/pkg-build-collect.cxx @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -153,7 +154,23 @@ namespace bpkg // If adjustment or orphan, then new and old are the same. // - if (available == nullptr || available->locations.empty ()) + // Note that in the common case a package version doesn't come from too + // many repositories (8). + // + small_vector, 8> locations; + + if (available != nullptr) // Not adjustment? + { + locations.reserve (available->locations.size ()); + + for (const package_location& pl: available->locations) + { + if (!rep_masked_fragment (pl.repository_fragment)) + locations.push_back (pl); + } + } + + if (locations.empty ()) { assert (selected != nullptr); @@ -169,7 +186,7 @@ namespace bpkg } else { - const package_location& pl (available->locations[0]); + const package_location& pl (locations[0]); if (pl.repository_fragment.object_id () == "") // Special root? { @@ -189,7 +206,7 @@ namespace bpkg // Note that such repository fragments are always preferred over // others (see below). // - for (const package_location& pl: available->locations) + for (const package_location& pl: locations) { const repository_location& rl ( pl.repository_fragment.load ()->location); @@ -1127,7 +1144,7 @@ namespace bpkg void build_packages:: enter (package_name name, build_package pkg) { - assert (!pkg.action); + assert (!pkg.action && pkg.repository_fragment == nullptr); database& db (pkg.db); // Save before the move() call. auto p (map_.emplace (package_key {db, move (name)}, @@ -1157,6 +1174,9 @@ namespace bpkg tracer trace ("collect_build"); + assert (pkg.repository_fragment == nullptr || + !rep_masked_fragment (pkg.repository_fragment)); + // See the above notes. // bool recursive (dep_chain != nullptr); diff --git a/bpkg/pkg-build.cli b/bpkg/pkg-build.cli index c2902ea..3db6e9f 100644 --- a/bpkg/pkg-build.cli +++ b/bpkg/pkg-build.cli @@ -398,6 +398,19 @@ namespace bpkg option in \l{bpkg-rep-fetch(1)} for details." } + strings --mask-repository + { + "", + "For the duration of the command execution pretend the specified + repository was removed as if by performing the \cb{rep-remove} + command. The repository can be specified either as a repository name or + as a repository location (URL or a directory path). Note that the + repository's complement and prerequisite repositories are also + considered masked, recursively, unless they are complements and/or + prerequisites of other unmasked repositories. Repeat this option to + mask multiple repositories." + } + bool --no-refinement { "Don't try to refine the configuration by offering to drop any unused diff --git a/bpkg/pkg-build.cxx b/bpkg/pkg-build.cxx index 7d9aa6d..6101826 100644 --- a/bpkg/pkg-build.cxx +++ b/bpkg/pkg-build.cxx @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -93,11 +94,23 @@ namespace bpkg for (const auto& pl: dap->locations) { - shared_ptr rf (pl.repository_fragment.load ()); + const lazy_shared_ptr& lrf ( + pl.repository_fragment); - if (find (rfs.begin (), rfs.end (), rf) == rfs.end ()) - rfs.push_back (move (rf)); + if (!rep_masked_fragment (lrf)) + { + shared_ptr rf (lrf.load ()); + + if (find (rfs.begin (), rfs.end (), rf) == rfs.end ()) + rfs.push_back (move (rf)); + } } + + // Erase the entry from the map if it contains no fragments, which may + // happen if all the available package repositories are masked. + // + if (rfs.empty ()) + r.erase (i); } } } @@ -202,6 +215,11 @@ namespace bpkg // (see dependent_repo_configs() for details) and this exact version is // available from this repository fragment or from its complement. // + // Note that the orphan definition here is stronger than in the rest of the + // code, since we request the available package to also be present in the + // repository fragment. It feels that such a definition aligns better with + // the user expectations about deorphaning. + // static bool orphan_package (database& db, const shared_ptr& sp) { @@ -217,7 +235,7 @@ namespace bpkg const shared_ptr rf ( ddb.find (cn)); - if (rf != nullptr) + if (rf != nullptr && !rep_masked_fragment (ddb, rf)) { auto af ( find_available_one (sp->name, @@ -1574,6 +1592,8 @@ namespace bpkg if (!current (db)) current_configs.push_back (db); } + + t.commit (); } validate_options (o, ""); // Global package options. @@ -1977,6 +1997,12 @@ namespace bpkg string () /* reason for "fetching ..." */); } + // Now, as repo_configs is filled and the repositories are fetched mask + // the repositories, if any. + // + if (o.mask_repository_specified ()) + rep_mask (o.mask_repository ()); + // Expand the package specs into individual package args, parsing them // into the package scheme, name, and version constraint components, and // also saving associated options and configuration variables. @@ -5883,19 +5909,22 @@ namespace bpkg for (const package_location& l: ap->locations) { - const repository_location& rl ( - l.repository_fragment.load ()->location); - - if (!basis || rl.local ()) // First or local? + if (!rep_masked_fragment (l.repository_fragment)) { - basis = rl.basis (); + const repository_location& rl ( + l.repository_fragment.load ()->location); - if (rl.directory_based ()) - break; + if (!basis || rl.local ()) // First or local? + { + basis = rl.basis (); + + if (rl.directory_based ()) + break; + } } } - assert (basis); + assert (basis); // Shouldn't be here otherwise. // All calls commit the transaction. // diff --git a/bpkg/pkg-checkout.cxx b/bpkg/pkg-checkout.cxx index 9793c41..d7b36e4 100644 --- a/bpkg/pkg-checkout.cxx +++ b/bpkg/pkg-checkout.cxx @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -148,14 +149,17 @@ namespace bpkg for (const package_location& l: ap->locations) { - const repository_location& rl (l.repository_fragment.load ()->location); - - if (rl.version_control_based () && (pl == nullptr || rl.local ())) + if (!rep_masked_fragment (l.repository_fragment)) { - pl = &l; + const repository_location& rl (l.repository_fragment.load ()->location); + + if (rl.version_control_based () && (pl == nullptr || rl.local ())) + { + pl = &l; - if (rl.local ()) - break; + if (rl.local ()) + break; + } } } diff --git a/bpkg/pkg-fetch.cxx b/bpkg/pkg-fetch.cxx index 50f0937..009ddf9 100644 --- a/bpkg/pkg-fetch.cxx +++ b/bpkg/pkg-fetch.cxx @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -224,14 +225,17 @@ namespace bpkg for (const package_location& l: ap->locations) { - const repository_location& rl (l.repository_fragment.load ()->location); - - if (rl.archive_based () && (pl == nullptr || rl.local ())) + if (!rep_masked_fragment (l.repository_fragment)) { - pl = &l; + const repository_location& rl (l.repository_fragment.load ()->location); + + if (rl.archive_based () && (pl == nullptr || rl.local ())) + { + pl = &l; - if (rl.local ()) - break; + if (rl.local ()) + break; + } } } diff --git a/bpkg/pkg-unpack.cxx b/bpkg/pkg-unpack.cxx index 5c88cc6..6e99b36 100644 --- a/bpkg/pkg-unpack.cxx +++ b/bpkg/pkg-unpack.cxx @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -304,7 +305,8 @@ namespace bpkg for (const package_location& l: ap->locations) { - if (l.repository_fragment.load ()->location.directory_based ()) + if (!rep_masked_fragment (l.repository_fragment) && + l.repository_fragment.load ()->location.directory_based ()) { pl = &l; break; diff --git a/bpkg/rep-mask.cxx b/bpkg/rep-mask.cxx new file mode 100644 index 0000000..79aef42 --- /dev/null +++ b/bpkg/rep-mask.cxx @@ -0,0 +1,266 @@ +// file : bpkg/rep-mask.cxx -*- C++ -*- +// license : MIT; see accompanying LICENSE file + +#include + +#include +#include +#include +#include +#include // repo_configs +#include // repository_name() + +using namespace std; +using namespace butl; + +namespace bpkg +{ + static optional> unmasked_repositories; + static optional> unmasked_repository_fragments; + + // Note: defined in rep-remove.cxx. + // + void + rep_remove (database&, + transaction&, + const shared_ptr&, + bool mask); + + // The idea here is to start the transaction, remove all the specified + // repositories recursively in all the configurations specified by + // repo_configs, collect the remaining repositories and repository fragments + // as unmasked, and rollback the transaction. Later on, the rep_masked*() + // functions will refer to the configuration-specific unmasked repositories + // and repository fragments lists to decide if the repository is masked or + // not in the specific configuration. + // + void + rep_mask (const strings& repos) + { + tracer trace ("rep_mask"); + + assert (!repo_configs.empty ()); + + database& mdb (repo_configs.front ()); + tracer_guard tg (mdb, trace); + + // Temporary "suspend" session before modifying the database. + // + session* sess (session::current_pointer ()); + if (sess != nullptr) + session::reset_current (); + + vector> rs; + vector found_repos (repos.size(), false); + + transaction t (mdb); + + for (database& db: repo_configs) + { + for (size_t i (0); i != repos.size (); ++i) + { + // Add a repository, suppressing duplicates, and mark it as found. + // + auto add = [&db, &rs, &found_repos, i] (shared_ptr&& r) + { + if (find_if (rs.begin (), rs.end (), + [&db, &r] (const lazy_weak_ptr& lr) + { + return lr.database () == db && + lr.object_id () == r->name; + }) == rs.end ()) + rs.emplace_back (db, move (r)); + + found_repos[i] = true; + }; + + const string& rp (repos[i]); + + if (repository_name (rp)) + { + if (shared_ptr r = db.find (rp)) + add (move (r)); + } + else + { + using query = query; + + // Verify that the repository URL is not misspelled or empty. + // + try + { + repository_url u (rp); + assert (!u.empty ()); + } + catch (const invalid_argument& e) + { + fail << "repository '" << rp << "' cannot be masked: " + << "invalid repository location: " << e; + } + + for (shared_ptr r: + pointer_result ( + db.query (query::location.url == rp))) + add (move (r)); + } + } + } + + // Fail if any of the specified repositories is not found in any database. + // + for (size_t i (0); i != repos.size (); ++i) + { + if (!found_repos[i]) + fail << "repository '" << repos[i] << "' cannot be masked: not found"; + } + + // First, remove the repository references from the dependent repository + // fragments. Note that rep_remove() removes the dangling repositories. + // + // Note that for efficiency we un-reference all the repositories before + // starting to delete them. + // + for (const lazy_weak_ptr& r: rs) + { + database& db (r.database ()); + const string& nm (r.object_id ()); + + // Remove from complements of the dependents. + // + for (const auto& rf: db.query ( + query::complement::name == nm)) + { + const shared_ptr& f (rf); + repository_fragment::dependencies& cs (f->complements); + + auto i (cs.find (r)); + assert (i != cs.end ()); + + cs.erase (i); + db.update (f); + } + + // Remove from prerequisites of the dependents. + // + for (const auto& rf: + db.query ( + query::prerequisite::name == + nm)) + { + const shared_ptr& f (rf); + repository_fragment::dependencies& ps (f->prerequisites); + + auto i (ps.find (r)); + assert (i != ps.end ()); + + ps.erase (i); + db.update (f); + } + } + + // Remove the now dangling repositories. + // + for (const lazy_weak_ptr& r: rs) + rep_remove (r.database (), t, r.load (), true /* mask */); + + // Collect the repositories and fragments which have remained after the + // removal. + // + unmasked_repositories = database_map (); + unmasked_repository_fragments = database_map (); + + for (database& db: repo_configs) + { + // Add the repository location canonical name to the database-specific + // unmasked repositories or repository fragments lists. Note that + // repository location is used only for tracing. + // + auto add = [&db, &trace] (string&& n, + database_map& m, + const repository_location& loc, + const char* what) + { + auto i (m.find (db)); + if (i == m.end ()) + i = m.insert (db, strings ()).first; + + l4 ([&]{trace << "unmasked " << what << ": '" << n + << "' '" << loc.url () << "'" << db;}); + + i->second.push_back (move (n)); + }; + + for (shared_ptr r: pointer_result (db.query ())) + add (move (r->name), + *unmasked_repositories, + r->location, + "repository"); + + for (shared_ptr f: + pointer_result (db.query ())) + add (move (f->name), + *unmasked_repository_fragments, + f->location, + "repository fragment"); + } + + // Rollback the transaction and restore the session, if present. + // + t.rollback (); + + if (sess != nullptr) + session::current_pointer (sess); + } + + static inline bool + masked (database& db, + const string& name, + const optional>& m) + { + if (!m) + return false; + + auto i (m->find (db)); + if (i != m->end ()) + { + const strings& ns (i->second); + return find (ns.begin (), ns.end (), name) == ns.end (); + } + + return true; + } + + bool + rep_masked (database& db, const shared_ptr& r) + { + return masked (db, r->name, unmasked_repositories); + } + + bool + rep_masked (const lazy_weak_ptr& r) + { + // Should not be transient. + // + assert (!(r.lock ().get_eager () != nullptr && !r.loaded ())); + + return masked (r.database (), r.object_id (), unmasked_repositories); + } + + bool + rep_masked_fragment (database& db, const shared_ptr& f) + { + return masked (db, f->name, unmasked_repository_fragments); + } + + bool + rep_masked_fragment (const lazy_shared_ptr& f) + { + // Should not be transient. + // + assert (!(f.get_eager () != nullptr && !f.loaded ())); + + return masked (f.database (), + f.object_id (), + unmasked_repository_fragments); + } +} diff --git a/bpkg/rep-mask.hxx b/bpkg/rep-mask.hxx new file mode 100644 index 0000000..dd9cedf --- /dev/null +++ b/bpkg/rep-mask.hxx @@ -0,0 +1,66 @@ +// file : bpkg/rep-mask.hxx -*- C++ -*- +// license : MIT; see accompanying LICENSE file + +#ifndef BPKG_REP_MASK_HXX +#define BPKG_REP_MASK_HXX + +#include +#include // database, repository +#include + +namespace bpkg +{ + // Note: not a command (at least not yet). + // + // Mask repositories to pretend they don't exist in the configurations that + // are used as the repository information sources (repo_configs). Also mask + // their complement and prerequisite repositories, recursively, excluding + // those which are complements and/or prerequisites of other unmasked + // repositories. The repositories can be specified either as repository + // location canonical names or URLs. Issue diagnostics and fail if any of + // the specified repositories don't exist in any configuration. + // + // Notes: + // + // - A repository may end up being masked in one configuration but not in + // another. + // + // - Using a canonical name potentially masks repositories with different + // URLs in different configurations (think of local and remote pkg + // repository locations). + // + // - Using a URL potentially masks repositories with different canonical + // names in the same configuration (think of directory and local git + // repository locations). + // + // NOTE: repo_configs needs to be filled prior to the function call. + // + void + rep_mask (const strings&); + + // Return true if a repository is masked in the specified configuration. + // + bool + rep_masked (database&, const shared_ptr&); + + // Note: the argument must refer to a persistent object which incorporates + // the configuration information (database). + // + bool + rep_masked (const lazy_weak_ptr&); + + // Return true if a repository fragment in the specified configuration + // belongs to the masked repositories only and is therefore masked (see + // package.hxx for the fragment/repository relationship details). + // + bool + rep_masked_fragment (database&, const shared_ptr&); + + // Note: the argument must refer to a persistent object which incorporates + // the configuration information (database). + // + bool + rep_masked_fragment (const lazy_shared_ptr&); +} + +#endif // BPKG_REP_MASK_HXX diff --git a/bpkg/rep-remove.cxx b/bpkg/rep-remove.cxx index 700534f..ad10f56 100644 --- a/bpkg/rep-remove.cxx +++ b/bpkg/rep-remove.cxx @@ -143,8 +143,20 @@ namespace bpkg rm_r (td, true /* dir_itself */, 3, rm_error_mode::warn); } + static void + rep_remove_fragment (database&, + transaction&, + const shared_ptr&, + bool mask); + + // In the mask repositories mode don't cleanup the repository state in the + // filesystem (see rep-mask.hxx for the details on repository masking). + // void - rep_remove (database& db, transaction& t, const shared_ptr& r) + rep_remove (database& db, + transaction& t, + const shared_ptr& r, + bool mask) { assert (!r->name.empty ()); // Can't be the root repository. @@ -164,7 +176,7 @@ namespace bpkg // Remove dangling repository fragments. // for (const repository::fragment_type& fr: r->fragments) - rep_remove_fragment (db, t, fr.fragment.load ()); + rep_remove_fragment (db, t, fr.fragment.load (), mask); // If there are no repositories stayed in the database then no repository // fragments should stay either. @@ -172,8 +184,8 @@ namespace bpkg if (db.query_value () == 0) assert (db.query_value () == 0); - // Cleanup the repository state if present and there are no more - // repositories referring this state. + // Unless in the mask repositories mode, cleanup the repository state if + // present and there are no more repositories referring this state. // // Note that this step is irreversible on failure. If something goes wrong // we will end up with a state-less fetched repository and the @@ -184,44 +196,58 @@ namespace bpkg // then remove them after committing the transaction. Though, we still may // fail in the middle due to the filesystem error. // - dir_path d (repository_state (r->location)); - - if (!d.empty ()) + if (!mask) { - dir_path sd (db.config_orig / repos_dir / d); + dir_path d (repository_state (r->location)); - if (exists (sd)) + if (!d.empty ()) { - // There is no way to get the list of repositories that share this - // state other than traversing all repositories of this type. - // - bool rm (true); - - using query = query; + dir_path sd (db.config_orig / repos_dir / d); - for (shared_ptr rp: - pointer_result ( - db.query ( - query::name != "" && - query::location.type == to_string (r->location.type ())))) + if (exists (sd)) { - if (repository_state (rp->location) == d) + // There is no way to get the list of repositories that share this + // state other than traversing all repositories of this type. + // + bool rm (true); + + using query = query; + + for (shared_ptr rp: + pointer_result ( + db.query ( + query::name != "" && + query::location.type == to_string (r->location.type ())))) { - rm = false; - break; + if (repository_state (rp->location) == d) + { + rm = false; + break; + } } - } - if (rm) - rmdir (db.config_orig, sd); + if (rm) + rmdir (db.config_orig, sd); + } } } } void + rep_remove (database& db, transaction& t, const shared_ptr& r) + { + rep_remove (db, t, r, false /* mask */); + } + + // In the mask repositories mode don't remove the repository fragment from + // locations of the available packages it contains (see rep-mask.hxx for the + // details on repository masking). + // + static void rep_remove_fragment (database& db, transaction& t, - const shared_ptr& rf) + const shared_ptr& rf, + bool mask) { tracer trace ("rep_remove_fragment"); @@ -235,11 +261,12 @@ namespace bpkg "fragment=" + query::_val (rf->name)) != 0) return; - // Remove the repository fragment from locations of the available packages - // it contains. Note that this must be done before the repository fragment - // removal. + // Unless in the mask repositories mode, remove the repository fragment + // from locations of the available packages it contains. Note that this + // must be done before the repository fragment removal. // - rep_remove_package_locations (db, t, rf->name); + if (!mask) + rep_remove_package_locations (db, t, rf->name); // Remove the repository fragment. // @@ -255,8 +282,8 @@ namespace bpkg // if (db.query_value () == 0) { - assert (db.query_value () == 0); - assert (db.query_value () == 0); + assert (db.query_value () == 0); + assert (mask || db.query_value () == 0); } // Remove dangling complements and prerequisites. @@ -264,10 +291,10 @@ namespace bpkg // Prior to removing a prerequisite/complement we need to make sure it // still exists, which may not be the case due to the dependency cycle. // - auto remove = [&db, &t] (const lazy_weak_ptr& rp) + auto remove = [&db, &t, mask] (const lazy_weak_ptr& rp) { if (shared_ptr r = db.find (rp.object_id ())) - rep_remove (db, t, r); + rep_remove (db, t, r, mask); }; for (const lazy_weak_ptr& cr: rf->complements) @@ -284,6 +311,14 @@ namespace bpkg } void + rep_remove_fragment (database& db, + transaction& t, + const shared_ptr& rf) + { + return rep_remove_fragment (db, t, rf, false /* mask */); + } + + void rep_remove_clean (const common_options& o, database& db, bool quiet) { tracer trace ("rep_remove_clean"); diff --git a/tests/pkg-build.testscript b/tests/pkg-build.testscript index a00ba5a..c3ae96f 100644 --- a/tests/pkg-build.testscript +++ b/tests/pkg-build.testscript @@ -582,6 +582,20 @@ test.arguments += --sys-no-query info: use 'bpkg rep-add' to add a repository EOE + : mask-repository-not-found + : + $clone_root_cfg; + $* --mask-repository 'https://example.com/1' libfoo 2>>EOE != 0 + error: repository 'https://example.com/1' cannot be masked: not found + EOE + + : mask-repository-empty + : + $clone_root_cfg; + $* --mask-repository '' libfoo 2>>EOE != 0 + error: repository '' cannot be masked: invalid repository location: empty URL + EOE + : archive : $clone_root_cfg; @@ -1811,6 +1825,12 @@ test.arguments += --sys-no-query $pkg_status libbar >'!libbar configured 1.2.0'; + # While at it, test using --mask-repository instead of rep-remove. + # + $* --upgrade --mask-repository $rep/t2 --mask-repository $rep/t5 2>>/EOE != 0; + error: libbar is not available + EOE + $rep_remove $rep/t2 $rep/t5; $* --upgrade 2>>/EOE != 0; @@ -3240,6 +3260,71 @@ test.arguments += --sys-no-query $pkg_drop libbar libbox } + : satisfy-masked + : + : As above but using --mask-repository instead of rep-remove. + : + { + $clone_cfg; + $rep_fetch $rep/t0b; + + $* libbar/0.0.1 2>!; + + $pkg_status libbaz >'libbaz configured 0.0.1 available 0.1.0 0.0.4 0.0.3 0.0.2'; + + $* libbar/0.0.2 ?libbaz 2>>EOE; + disfigured libbar/0.0.1 + disfigured libbaz/0.0.1 + disfigured libfox/0.0.1 + purged libfox/0.0.1 + fetched libfoo/1.0.0 + unpacked libfoo/1.0.0 + fetched libbaz/0.0.2 + unpacked libbaz/0.0.2 + fetched libbar/0.0.2 + unpacked libbar/0.0.2 + configured libfoo/1.0.0 + configured libbaz/0.0.2 + configured libbar/0.0.2 + EOE + + $pkg_status libbaz >'libbaz configured 0.0.2 available 0.1.0 0.0.4 0.0.3'; + + # Test that the selected package, that is "better" than the available + # one, is left. + # + $* --mask-repository $rep/t0b libbox ?libbaz 2>>EOE; + fetched libbox/0.0.1 + unpacked libbox/0.0.1 + configured libbox/0.0.1 + EOE + + $pkg_status libbaz >'libbaz configured 0.0.2 available 0.1.0 0.0.4 0.0.3'; + + # Test that the selected package is left as there is no satisfactory + # available package. + # + $* --mask-repository $rep/t0b --mask-repository $rep/t0a ?libbaz; + + # Test that the above behavior is not triggered for the system package. + # + $* --mask-repository $rep/t0b --mask-repository $rep/t0a '?sys:libbaz' 2>>EOE; + disfigured libbar/0.0.2 + disfigured libbox/0.0.1 + disfigured libbaz/0.0.2 + disfigured libfoo/1.0.0 + purged libfoo/1.0.0 + purged libbaz/0.0.2 + configured sys:libbaz/* + configured libbox/0.0.1 + configured libbar/0.0.2 + EOE + + $pkg_status libbaz >'libbaz configured,system !* available 0.1.0 0.0.4 0.0.3 0.0.2 0.0.1'; + + $pkg_drop libbar libbox + } + : unsatisfied : { @@ -3761,6 +3846,17 @@ test.arguments += --sys-no-query $* libbar --recursive --yes } + : unavailable-masked + : + : As above but using --mask-repository instead of rep-remove. + : + { + $clone_cfg; + + $* libbar --mask-repository $rep/t0a --mask-repository $rep/t0b \ + --mask-repository $rep/t0c --recursive --yes + } + -$pkg_drop libbar libbaz libfoo } @@ -14194,6 +14290,32 @@ test.arguments += --sys-no-query EOE } + : unavailable-masked + : + : As above but using --mask-repository instead of rep-remove. + : + { + $clone_cfg; + + $* '?sys:libbaz/0.0.3' 2>>EOE; + disfigured libbox/0.0.1 + disfigured libfix/0.0.3 + disfigured libbaz/0.0.3 + disfigured libfoo/1.0.0 + purged libfoo/1.0.0 + purged libbaz/0.0.3 + configured sys:libbaz/0.0.3 + configured libfix/0.0.3 + configured libbox/0.0.1 + EOE + + $rep_fetch $rep/t0b; + + $* --mask-repository $rep/t0c ?libbaz --patch --yes 2>>EOE != 0 + error: patch version for sys:libbaz/0.0.3 is not available from its dependents' repositories + EOE + } + -$pkg_drop libbox libfix libbaz libfoo } @@ -16358,6 +16480,64 @@ else EOO } + : orphan-repointed-masked + : + : As above but using --mask-repository instead of rep-remove. + : + { + $cfg_create -d h1 --type host --name h1 &h1/***; + $cfg_create -d h2 --type host --name h2 &h2/***; + + $cfg_link -d h1 h2; + + $rep_add -d h1 $rep/t7b && $rep_fetch -d h1; + + test.arguments = $regex.apply($test.arguments, cfg, h1); + + $* foo --yes 2>!; + + $rep_add -d h1 $rep/t7a && $rep_fetch -d h1; + $rep_add -d h2 $rep/t7a && $rep_fetch -d h2; + + $* libbaz +{ --config-name h2 } --mask-repository $rep/t7b 2>>EOE != 0; + error: package foo/1.1.0 is orphaned + info: explicitly upgrade it to a new version + info: while satisfying foo/1.1.0 + EOE + + # While at it, test foo deorphaning. + # + $* foo +{ --deorphan } libbaz +{ --config-name h2 } --yes --plan "" \ + --mask-repository $rep/t7b 2>>~%EOE%; + % new libbuild2-bar/1.0.0 \[h1.\.bpkg.build2.\] \(required by foo\)% + % new libbaz/1.0.0 \[h2.\]% + drop libbaz/1.1.0 (unused) + deorphan/downgrade foo/1.0.0 + disfigured foo/1.1.0 + disfigured libbaz/1.1.0 + %fetched libbuild2-bar/1.0.0 \[h1.\.bpkg.build2.\]% + %unpacked libbuild2-bar/1.0.0 \[h1.\.bpkg.build2.\]% + %fetched libbaz/1.0.0 \[h2.\]% + %unpacked libbaz/1.0.0 \[h2.\]% + purged libbaz/1.1.0 + fetched foo/1.0.0 + unpacked foo/1.0.0 + %configured libbuild2-bar/1.0.0 \[h1.\.bpkg.build2.\]% + %configured libbaz/1.0.0 \[h2.\]% + configured foo/1.0.0 + %info: h2.+libbaz-1.0.0.+ is up to date% + %info: h1.+foo-1.0.0.+ is up to date% + %updated libbaz/1.0.0 \[h2.\]% + updated foo/1.0.0 + EOE + + $pkg_status -d h1 -r >>/EOO + !foo configured 1.0.0 available 1.1.0 + !libbaz [h2/] configured 1.0.0 available 1.1.0 + libbuild2-bar [h1/.bpkg/build2/] configured 1.0.0 + EOO + } + : unhold-repointed : { @@ -17777,7 +17957,9 @@ else $pkg_drop libbar } - : drop + : basics-masked + : + : As above but using --mask-repository instead of rep-remove. : { $clone_root_cfg; @@ -17790,69 +17972,288 @@ else $* libbar 2>!; + $pkg_status -r libbar >>EOO; + !libbar configured 1.1.0 + !libfoo configured 1.1.0 + EOO + echo "" >+ libfoo/manifest; $rep_fetch; - $* libfoo 2>!; $pkg_status -r libbar >>EOO; !libbar configured 1.1.0 - !libfoo configured 1.1.0#1 + !libfoo configured 1.1.0 available 1.1.0#1 EOO - $rep_remove $~/libfoo/; - - $* --deorphan ?libfoo ?libbar 2>>EOE; - drop libfoo/1.1.0#1 (unused) - drop libbar/1.1.0 (unused) + # Deorphan libfoo/1.1.0 to libfoo/1.1.0#1. + # + # Note that libfoo/1.1.0 is considered as an orphan since its version + # is replaced with 1.1.0#1 in its existing repository fragment. This is + # in contrast to the subsequent tests where the package repository is + # removed. + # + $* --deorphan libfoo 2>>~%EOE%; + deorphan/upgrade libfoo/1.1.0#1 + reconfigure libbar (dependent of libfoo) disfigured libbar/1.1.0 - disfigured libfoo/1.1.0#1 - purged libfoo/1.1.0#1 - purged libbar/1.1.0 + disfigured libfoo/1.1.0 + using libfoo/1.1.0#1 (external) + configured libfoo/1.1.0#1 + configured libbar/1.1.0 + %info: .+libfoo.+ is up to date% + %info: .+libbar-1.1.0.+ is up to date% + updated libfoo/1.1.0#1 + updated libbar/1.1.0 EOE - $pkg_status -ar 2>'info: no packages in the configuration' - } - - : no-dependent - : - { - $clone_root_cfg; - - cp -r $src/libfoo-1.1.0 libfoo; - $rep_add --type dir libfoo/ && $rep_fetch; + $pkg_status -r libbar >>EOO; + !libbar configured 1.1.0 + !libfoo configured 1.1.0#1 + EOO - echo "" >+ libfoo/manifest; - $rep_fetch; - $* libfoo 2>!; + # Noop. + # + $* --deorphan libfoo 2>'info: nothing to build'; - $rep_fetch $rep/t4b; - $rep_remove $~/libfoo/; + $pkg_status -r libbar >>EOO; + !libbar configured 1.1.0 + !libfoo configured 1.1.0#1 + EOO - $pkg_status libfoo >'!libfoo configured 1.1.0'; + # Deorphan libfoo/1.1.0#1 to ?libfoo/1.1.0. + # + # Note that on Windows the local repository canonical name path part + # is converted to lower case. + # + cn = "$canonicalize([dir_path] $~/libfoo)"; + if! $posix + cn = $lcase([string] $cn) + end; + cn = "dir:$cn"; - $* --deorphan ?libfoo 2>>EOE; - drop libfoo/1.1.0 (unused) - disfigured libfoo/1.1.0 - purged libfoo/1.1.0 + $* --mask-repository $cn --deorphan ?libfoo 2>>~%EOE%; + deorphan/downgrade/unhold libfoo/1.1.0 + reconfigure libbar (dependent of libfoo) + disfigured libbar/1.1.0 + disfigured libfoo/1.1.0#1 + fetched libfoo/1.1.0 + unpacked libfoo/1.1.0 + configured libfoo/1.1.0 + configured libbar/1.1.0 + %info: .+libbar-1.1.0.+ is up to date% + updated libbar/1.1.0 EOE - $pkg_status -ar 2>'info: no packages in the configuration' - } - - : preference - : - { - $clone_root_cfg; + $pkg_status -r libbar >>EOO; + !libbar configured 1.1.0 + libfoo configured 1.1.0 available 1.1.0#1 + EOO - tar (!$posix ? --force-local : ) -xf $src/t14d/libfoo-1.1.0+2.tar.gz &libfoo-1.1.0+2/***; - mv libfoo-1.1.0+2 libfoo; + # Noop. + # + $* --mask-repository $cn --deorphan ?libfoo; - $rep_add --type dir libfoo/ && $rep_fetch; + $pkg_status -r libbar >>EOO; + !libbar configured 1.1.0 + libfoo configured 1.1.0 available 1.1.0#1 + EOO + # Deorphan libfoo/1.1.0#1 to ?libfoo/1.1.0. + # $* libfoo 2>!; - tar (!$posix ? --force-local : ) -xf $src/t2/libbar-1.0.0.tar.gz &libbar-1.0.0/***; - mv libbar-1.0.0 libbar; + $pkg_status -r libbar >>EOO; + !libbar configured 1.1.0 + !libfoo configured 1.1.0#1 + EOO + + $* --mask-repository $cn --deorphan ?libfoo 2>>~%EOE%; + deorphan/downgrade/unhold libfoo/1.1.0 + reconfigure libbar (dependent of libfoo) + disfigured libbar/1.1.0 + disfigured libfoo/1.1.0#1 + fetched libfoo/1.1.0 + unpacked libfoo/1.1.0 + configured libfoo/1.1.0 + configured libbar/1.1.0 + %info: .+libbar-1.1.0.+ is up to date% + updated libbar/1.1.0 + EOE + + $pkg_status -r libbar >>EOO; + !libbar configured 1.1.0 + libfoo configured 1.1.0 available 1.1.0#1 + EOO + + # Noop. + # + $* --mask-repository $cn --deorphan ?libfoo; + + $pkg_status -r libbar >>EOO; + !libbar configured 1.1.0 + libfoo configured 1.1.0 available 1.1.0#1 + EOO + + $pkg_drop libbar + } + + : drop + : + { + $clone_root_cfg; + + cp -r $src/libfoo-1.1.0 libfoo; + $rep_add --type dir libfoo/ && $rep_fetch; + + $* libfoo 2>!; + $rep_fetch $rep/t4b; + + $* libbar 2>!; + + echo "" >+ libfoo/manifest; + $rep_fetch; + $* libfoo 2>!; + + $pkg_status -r libbar >>EOO; + !libbar configured 1.1.0 + !libfoo configured 1.1.0#1 + EOO + + $rep_remove $~/libfoo/; + + $* --deorphan ?libfoo ?libbar 2>>EOE; + drop libfoo/1.1.0#1 (unused) + drop libbar/1.1.0 (unused) + disfigured libbar/1.1.0 + disfigured libfoo/1.1.0#1 + purged libfoo/1.1.0#1 + purged libbar/1.1.0 + EOE + + $pkg_status -ar 2>'info: no packages in the configuration' + } + + : drop-masked + : + : As above but using --mask-repository instead of rep-remove. + : + { + $clone_root_cfg; + + cp -r $src/libfoo-1.1.0 libfoo; + $rep_add --type dir libfoo/ && $rep_fetch; + + $* libfoo 2>!; + $rep_fetch $rep/t4b; + + $* libbar 2>!; + + echo "" >+ libfoo/manifest; + $rep_fetch; + $* libfoo 2>!; + + $pkg_status -r libbar >>EOO; + !libbar configured 1.1.0 + !libfoo configured 1.1.0#1 + EOO + + # Note that on Windows the local repository canonical name path part + # is converted to lower case. + # + cn = "$canonicalize([dir_path] $~/libfoo)"; + if! $posix + cn = $lcase([string] $cn) + end; + cn = "dir:$cn"; + + $* --mask-repository $cn --deorphan ?libfoo ?libbar 2>>EOE; + drop libfoo/1.1.0#1 (unused) + drop libbar/1.1.0 (unused) + disfigured libbar/1.1.0 + disfigured libfoo/1.1.0#1 + purged libfoo/1.1.0#1 + purged libbar/1.1.0 + EOE + + $pkg_status -ar 2>'info: no packages in the configuration' + } + + : no-dependent + : + { + $clone_root_cfg; + + cp -r $src/libfoo-1.1.0 libfoo; + $rep_add --type dir libfoo/ && $rep_fetch; + + echo "" >+ libfoo/manifest; + $rep_fetch; + $* libfoo 2>!; + + $rep_fetch $rep/t4b; + $rep_remove $~/libfoo/; + + $pkg_status libfoo >'!libfoo configured 1.1.0'; + + $* --deorphan ?libfoo 2>>EOE; + drop libfoo/1.1.0 (unused) + disfigured libfoo/1.1.0 + purged libfoo/1.1.0 + EOE + + $pkg_status -ar 2>'info: no packages in the configuration' + } + + : no-dependent-masked + : + : As above but using --mask-repository instead of rep-remove. + : + { + $clone_root_cfg; + + cp -r $src/libfoo-1.1.0 libfoo; + $rep_add --type dir libfoo/ && $rep_fetch; + + echo "" >+ libfoo/manifest; + $rep_fetch; + $* libfoo 2>!; + + $rep_fetch $rep/t4b; + + $pkg_status libfoo >'!libfoo configured 1.1.0'; + + # Note that on Windows the local repository canonical name path part + # is converted to lower case. + # + cn = "$canonicalize([dir_path] $~/libfoo)"; + if! $posix + cn = $lcase([string] $cn) + end; + cn = "dir:$cn"; + + $* --mask-repository $cn --deorphan ?libfoo 2>>EOE; + drop libfoo/1.1.0 (unused) + disfigured libfoo/1.1.0 + purged libfoo/1.1.0 + EOE + + $pkg_status -ar 2>'info: no packages in the configuration' + } + + : preference + : + { + $clone_root_cfg; + + tar (!$posix ? --force-local : ) -xf $src/t14d/libfoo-1.1.0+2.tar.gz &libfoo-1.1.0+2/***; + mv libfoo-1.1.0+2 libfoo; + + $rep_add --type dir libfoo/ && $rep_fetch; + + $* libfoo 2>!; + + tar (!$posix ? --force-local : ) -xf $src/t2/libbar-1.0.0.tar.gz &libbar-1.0.0/***; + mv libbar-1.0.0 libbar; cat <<"EOI" >=libbar/repositories.manifest; : 1 @@ -18272,6 +18673,56 @@ else $pkg_drop libbar } + : immediate-masked + : + : As above but using --mask-repository instead of rep-remove. + : + { + $clone_root_cfg; + cp -rp ../libbar ./; + + $rep_add --type dir libbar/ && $rep_fetch; + $* libbar ?libfoo/1.1.0 2>!; + + $pkg_status -or libbar >>EOO; + !libbar configured 1.0.0 available (1.0.0) + libfoo configured !1.1.0 available [1.2.0] [1.1.1] (1.1.0) + EOO + + $rep_add $rep/t2 $rep/t4b $rep/t14c && $rep_fetch; + + # Note that on Windows the local repository canonical name path part + # is converted to lower case. + # + cn = "$canonicalize([dir_path] $~/libbar)"; + if! $posix + cn = $lcase([string] $cn) + end; + cn = "dir:$cn"; + + $* --mask-repository $cn --deorphan --immediate libbar 2>>~%EOE%; + deorphan/downgrade libfoo/1.0.0 + deorphan/update libbar/1.0.0 + disfigured libbar/1.0.0 + disfigured libfoo/1.1.0 + fetched libfoo/1.0.0 + unpacked libfoo/1.0.0 + fetched libbar/1.0.0 + unpacked libbar/1.0.0 + configured libfoo/1.0.0 + configured libbar/1.0.0 + %info: .+libbar.+ is up to date% + updated libbar/1.0.0 + EOE + + $pkg_status -or libbar >>EOO; + !libbar configured 1.0.0 available 1.1.0 (1.0.0) + libfoo configured !1.0.0 available [1.2.0] [1.1.1] 1.1.0+1 [1.1.0] (1.0.0) + EOO + + $pkg_drop libbar + } + : recursive : { @@ -18616,6 +19067,107 @@ else $pkg_drop libfoo } + : basics-masked + : + : As above but using --mask-repository instead of rep-remove. + : + { + $clone_root_cfg; + + cp -r $src/libfoo-1.1.0 libfoo; + sed -i -e 's/(version:).+/\1 1.0.0/' libfoo/manifest; + + $rep_add --type dir libfoo/ && $rep_fetch; + + $* libfoo 2>!; + + echo "" >+ libfoo/manifest; + $rep_fetch; + + $pkg_status -ro libfoo >>EOO; + !libfoo configured 1.0.0 available 1.0.0#1 + EOO + + # Deorphan libfoo/1.0.0 to libfoo/1.0.0#1. + # + $* --deorphan libfoo 2>>~%EOE%; + deorphan/upgrade libfoo/1.0.0#1 + disfigured libfoo/1.0.0 + using libfoo/1.0.0#1 (external) + configured libfoo/1.0.0#1 + %info: .+libfoo.+ is up to date% + updated libfoo/1.0.0#1 + EOE + + $pkg_status -ro libfoo >>EOO; + !libfoo configured 1.0.0#1 available (1.0.0#1) + EOO + + # Noop. + # + $* --deorphan libfoo 2>'info: nothing to build'; + + $rep_fetch $rep/t4a $rep/t4c; + + $pkg_status -ro libfoo >>EOO; + !libfoo configured 1.0.0#1 available 1.1.0 (1.0.0#1) 1.0.0 + EOO + + # Deorphan libfoo/1.0.0#1 to libfoo/1.0.0. + # + # Note that on Windows the local repository canonical name path part is + # converted to lower case. + # + cn = "$canonicalize([dir_path] $~/libfoo)"; + if! $posix + cn = $lcase([string] $cn) + end; + cn = "dir:$cn"; + + $* --mask-repository $cn --deorphan libfoo 2>>~%EOE%; + deorphan/downgrade libfoo/1.0.0 + disfigured libfoo/1.0.0#1 + fetched libfoo/1.0.0 + unpacked libfoo/1.0.0 + configured libfoo/1.0.0 + %info: .+libfoo-1.0.0.+ is up to date% + updated libfoo/1.0.0 + EOE + + $pkg_status libfoo >'!libfoo configured 1.0.0 available 1.1.0 1.0.0#1'; + + # Noop. + # + $* --mask-repository $cn --deorphan libfoo 2>'info: nothing to build'; + + $pkg_status libfoo >'!libfoo configured 1.0.0 available 1.1.0 1.0.0#1'; + + # Deorphan libfoo/1.0.0 to libfoo/1.1.0. + # + # While at it, use the 'deorphan all held packages' form. + # + $* --mask-repository $cn --mask-repository $rep/t4c --deorphan 2>>~%EOE%; + deorphan/upgrade libfoo/1.1.0 + disfigured libfoo/1.0.0 + fetched libfoo/1.1.0 + unpacked libfoo/1.1.0 + configured libfoo/1.1.0 + %info: .+libfoo-1.1.0.+ is up to date% + updated libfoo/1.1.0 + EOE + + $pkg_status libfoo >'!libfoo configured 1.1.0'; + + # Noop. + # + $* --mask-repository $cn --mask-repository $rep/t4c --deorphan libfoo \ + 2>'info: nothing to build'; + + $pkg_status libfoo >'!libfoo configured 1.1.0'; + + $pkg_drop libfoo + } + : preference : { @@ -19051,5 +19603,231 @@ else $pkg_drop libfoo } + + : preference-all-held-masked + : + : As above but using --mask-repository instead of rep-remove. + : + { + $clone_root_cfg; + + tar (!$posix ? --force-local : ) -xf $src/t14d/libfoo-1.1.0+2.tar.gz &libfoo-1.1.0+2/***; + mv libfoo-1.1.0+2 libfoo; + + $rep_add --type dir libfoo/ && $rep_fetch; + + $* libfoo 2>!; + + $rep_fetch $rep/t14a $rep/t14b $rep/t14c $rep/t14d $rep/t14e $rep/t14f $rep/t14i; + + $pkg_status -o libfoo >>EOO; + !libfoo configured 1.1.0+2 available 1.2.0 1.1.1 1.1.0+3 (1.1.0+2) 1.1.0+1 1.1.0 1.0.0 + EOO + + # Deorphan libfoo/1.1.0+2 to the exactly same version. + # + # Note that on Windows the local repository canonical name path part is + # converted to lower case. + # + cn = "$canonicalize([dir_path] $~/libfoo)"; + if! $posix + cn = $lcase([string] $cn) + end; + cn = "dir:$cn"; + + mask = --mask-repository $cn; + + $* $mask --deorphan 2>>~%EOE%; + deorphan/update libfoo/1.1.0+2 + disfigured libfoo/1.1.0+2 + fetched libfoo/1.1.0+2 + unpacked libfoo/1.1.0+2 + configured libfoo/1.1.0+2 + %info: .+libfoo-1.1.0\+2.+ is up to date% + updated libfoo/1.1.0+2 + EOE + + $pkg_status -o libfoo >>EOO; + !libfoo configured 1.1.0+2 available 1.2.0 1.1.1 1.1.0+3 (1.1.0+2) 1.1.0+1 1.1.0 1.0.0 + EOO + + # Noop. + # + $* $mask --deorphan 2>'info: nothing to build'; + + $pkg_status -o libfoo >>EOO; + !libfoo configured 1.1.0+2 available 1.2.0 1.1.1 1.1.0+3 (1.1.0+2) 1.1.0+1 1.1.0 1.0.0 + EOO + + # Deorphan libfoo/1.1.0+2 to the later revision of same version (1.1.0+3). + # + mask += --mask-repository $rep/t14d; + + $* $mask --deorphan 2>>~%EOE%; + deorphan/upgrade libfoo/1.1.0+3 + disfigured libfoo/1.1.0+2 + fetched libfoo/1.1.0+3 + unpacked libfoo/1.1.0+3 + configured libfoo/1.1.0+3 + %info: .+libfoo-1.1.0\+3.+ is up to date% + updated libfoo/1.1.0+3 + EOE + + $pkg_status -o libfoo >>EOO; + !libfoo configured 1.1.0+3 available 1.2.0 1.1.1 (1.1.0+3) 1.1.0+2 1.1.0+1 1.1.0 1.0.0 + EOO + + # Noop. + # + $* $mask --deorphan 2>'info: nothing to build'; + + $pkg_status -o libfoo >>EOO; + !libfoo configured 1.1.0+3 available 1.2.0 1.1.1 (1.1.0+3) 1.1.0+2 1.1.0+1 1.1.0 1.0.0 + EOO + + # Deorphan libfoo/1.1.0+3 to the later patch of same version (1.1.1). + # + mask += --mask-repository $rep/t14e; + + $* $mask --deorphan 2>>~%EOE%; + deorphan/upgrade libfoo/1.1.1 + disfigured libfoo/1.1.0+3 + fetched libfoo/1.1.1 + unpacked libfoo/1.1.1 + configured libfoo/1.1.1 + %info: .+libfoo-1.1.1.+ is up to date% + updated libfoo/1.1.1 + EOE + + $pkg_status -o libfoo >>EOO; + !libfoo configured 1.1.1 available 1.2.0 (1.1.1) 1.1.0+3 1.1.0+2 1.1.0+1 1.1.0 1.0.0 + EOO + + # Noop. + # + $* $mask --deorphan 2>'info: nothing to build'; + + $pkg_status -o libfoo >>EOO; + !libfoo configured 1.1.1 available 1.2.0 (1.1.1) 1.1.0+3 1.1.0+2 1.1.0+1 1.1.0 1.0.0 + EOO + + # Deorphan libfoo/1.1.1 to later minor of same version (1.2.0). + # + mask += --mask-repository $rep/t14f; + + $* $mask --deorphan 2>>~%EOE%; + deorphan/upgrade libfoo/1.2.0 + disfigured libfoo/1.1.1 + fetched libfoo/1.2.0 + unpacked libfoo/1.2.0 + configured libfoo/1.2.0 + %info: .+libfoo-1.2.0.+ is up to date% + updated libfoo/1.2.0 + EOE + + $pkg_status -o libfoo >>EOO; + !libfoo configured 1.2.0 available (1.2.0) 1.1.1 1.1.0+3 1.1.0+2 1.1.0+1 1.1.0 1.0.0 + EOO + + # Noop. + # + $* $mask --deorphan 2>'info: nothing to build'; + + $pkg_status -o libfoo >>EOO; + !libfoo configured 1.2.0 available (1.2.0) 1.1.1 1.1.0+3 1.1.0+2 1.1.0+1 1.1.0 1.0.0 + EOO + + # Deorphan libfoo/1.2.0 to latest available version (1.1.0+1). + # + mask += --mask-repository $rep/t14i; + + $* $mask --deorphan 2>>~%EOE%; + deorphan/downgrade libfoo/1.1.0+1 + disfigured libfoo/1.2.0 + fetched libfoo/1.1.0+1 + unpacked libfoo/1.1.0+1 + configured libfoo/1.1.0+1 + %info: .+libfoo-1.1.0\+1.+ is up to date% + updated libfoo/1.1.0+1 + EOE + + $pkg_status -o libfoo >>EOO; + !libfoo configured 1.1.0+1 available 1.2.0 1.1.1 1.1.0+3 1.1.0+2 (1.1.0+1) 1.1.0 1.0.0 + EOO + + # Noop. + # + $* $mask --deorphan 2>'info: nothing to build'; + + $pkg_status -o libfoo >>EOO; + !libfoo configured 1.1.0+1 available 1.2.0 1.1.1 1.1.0+3 1.1.0+2 (1.1.0+1) 1.1.0 1.0.0 + EOO + + # Deorphan libfoo/1.1.0+1 to latest available version (1.1.0). + # + mask += --mask-repository $rep/t14c; + + $* $mask --deorphan 2>>~%EOE%; + deorphan/downgrade libfoo/1.1.0 + disfigured libfoo/1.1.0+1 + fetched libfoo/1.1.0 + unpacked libfoo/1.1.0 + configured libfoo/1.1.0 + %info: .+libfoo-1.1.0.+ is up to date% + updated libfoo/1.1.0 + EOE + + $pkg_status -o libfoo >>EOO; + !libfoo configured 1.1.0 available 1.2.0 1.1.1 1.1.0+3 1.1.0+2 1.1.0+1 (1.1.0) 1.0.0 + EOO + + # Noop. + # + $* $mask --deorphan 2>'info: nothing to build'; + + $pkg_status -o libfoo >>EOO; + !libfoo configured 1.1.0 available 1.2.0 1.1.1 1.1.0+3 1.1.0+2 1.1.0+1 (1.1.0) 1.0.0 + EOO + + # Deorphan libfoo/1.1.0 to latest available version (1.0.0). + # + mask += --mask-repository $rep/t14b; + + $* $mask --deorphan 2>>~%EOE%; + deorphan/downgrade libfoo/1.0.0 + disfigured libfoo/1.1.0 + fetched libfoo/1.0.0 + unpacked libfoo/1.0.0 + configured libfoo/1.0.0 + %info: .+libfoo-1.0.0.+ is up to date% + updated libfoo/1.0.0 + EOE + + $pkg_status -o libfoo >>EOO; + !libfoo configured 1.0.0 available 1.2.0 1.1.1 1.1.0+3 1.1.0+2 1.1.0+1 1.1.0 (1.0.0) + EOO + + # Noop. + # + $* $mask --deorphan 2>'info: nothing to build'; + + $pkg_status -o libfoo >>EOO; + !libfoo configured 1.0.0 available 1.2.0 1.1.1 1.1.0+3 1.1.0+2 1.1.0+1 1.1.0 (1.0.0) + EOO + + # Deorphan fails (none available). + # + mask += --mask-repository $rep/t14a; + + $* $mask --deorphan 2>>/EOE != 0; + error: libfoo is not available + EOE + + $pkg_status -o libfoo >>EOO; + !libfoo configured 1.0.0 available 1.2.0 1.1.1 1.1.0+3 1.1.0+2 1.1.0+1 1.1.0 (1.0.0) + EOO + + $pkg_drop libfoo + } } } -- cgit v1.1