From 56e0a851185136dbdd6f1eaa75f44da774a61e51 Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Thu, 23 Dec 2021 21:10:53 +0300 Subject: Add support for multiple dependencies in alternative --- bpkg/pkg-build.cxx | 1085 ++++++++++---------- bpkg/pkg-configure.cxx | 160 +-- bpkg/pkg-verify.cxx | 81 +- .../dependency-alternatives/t8a/foo-1.0.0.tar.gz | Bin 0 -> 373 bytes .../t8a/libbar-1.0.0.tar.gz | Bin 0 -> 350 bytes .../t8a/libbaz-1.0.0.tar.gz | Bin 0 -> 360 bytes .../t8a/repositories.manifest | 1 + tests/pkg-build.testscript | 35 + tests/pkg-build/t8a | 1 + tests/pkg-configure.testscript | 49 +- tests/pkg-configure/t8a | 1 + 11 files changed, 769 insertions(+), 644 deletions(-) create mode 100644 tests/common/dependency-alternatives/t8a/foo-1.0.0.tar.gz create mode 100644 tests/common/dependency-alternatives/t8a/libbar-1.0.0.tar.gz create mode 100644 tests/common/dependency-alternatives/t8a/libbaz-1.0.0.tar.gz create mode 100644 tests/common/dependency-alternatives/t8a/repositories.manifest create mode 120000 tests/pkg-build/t8a create mode 120000 tests/pkg-configure/t8a diff --git a/bpkg/pkg-build.cxx b/bpkg/pkg-build.cxx index da29cc9..df5350a 100644 --- a/bpkg/pkg-build.cxx +++ b/bpkg/pkg-build.cxx @@ -1139,616 +1139,621 @@ namespace bpkg for (const dependency_alternatives_ex& das: ap->dependencies) { - if (das.conditional ()) // @@ TODO + if (das.conditional ()) // @@ DEP fail << "conditional dependencies are not yet supported"; - if (das.size () != 1) // @@ TODO + if (das.size () != 1) // @@ DEP fail << "multiple dependency alternatives not yet supported"; - const dependency_alternative& da (das.front ()); - - assert (da.size () == 1); // @@ DEP - - const dependency& dp (da[0]); - const package_name& dn (dp.name); - - if (das.buildtime) + bool postpone (false); + for (const dependency& dp: das.front ()) { - // Handle special names. - // - if (dn == "build2") - { - if (dp.constraint && !satisfy_build2 (options, dp)) - fail << "unable to satisfy constraint (" << dp - << ") for package " << name << - info << "available build2 version is " << build2_version; - - continue; - } - else if (dn == "bpkg") - { - if (dp.constraint && !satisfy_bpkg (options, dp)) - fail << "unable to satisfy constraint (" << dp - << ") for package " << name << - info << "available bpkg version is " << bpkg_version; + const package_name& dn (dp.name); - continue; - } - else if (pdb.type == build2_config_type) + if (das.buildtime) { - // Note that the dependent is not necessarily a build system - // module. + // Handle special names. // - fail << "build-time dependency " << dn << " in build system " - << "module configuration" << - info << "build system modules cannot have build-time " - << "dependencies"; - } - } + if (dn == "build2") + { + if (dp.constraint && !satisfy_build2 (options, dp)) + fail << "unable to satisfy constraint (" << dp + << ") for package " << name << + info << "available build2 version is " << build2_version; - bool system (false); - bool dep_optional (false); + continue; + } + else if (dn == "bpkg") + { + if (dp.constraint && !satisfy_bpkg (options, dp)) + fail << "unable to satisfy constraint (" << dp + << ") for package " << name << + info << "available bpkg version is " << bpkg_version; - // If the user specified the desired dependency version constraint, - // then we will use it to overwrite the constraint imposed by the - // dependent package, checking that it is still satisfied. - // - // Note that we can't just rely on the execution plan refinement that - // will pick up the proper dependency version at the end of the day. - // We may just not get to the plan execution simulation, failing due - // to inability for dependency versions collected by two dependents to - // satisfy each other constraints (for an example see the - // pkg-build/dependency/apply-constraints/resolve-conflict{1,2} - // tests). - - // Points to the desired dependency version constraint, if specified, - // and is NULL otherwise. Can be used as boolean flag. - // - const version_constraint* dep_constr (nullptr); + continue; + } + else if (pdb.type == build2_config_type) + { + // Note that the dependent is not necessarily a build system + // module. + // + fail << "build-time dependency " << dn << " in build system " + << "module configuration" << + info << "build system modules cannot have build-time " + << "dependencies"; + } + } - database* ddb (fdb (pdb, dn, das.buildtime)); + bool system (false); + bool dep_optional (false); - auto i (ddb != nullptr - ? map_.find (*ddb, dn) - : map_.find_dependency (pdb, dn, das.buildtime)); + // If the user specified the desired dependency version constraint, + // then we will use it to overwrite the constraint imposed by the + // dependent package, checking that it is still satisfied. + // + // Note that we can't just rely on the execution plan refinement + // that will pick up the proper dependency version at the end of the + // day. We may just not get to the plan execution simulation, + // failing due to inability for dependency versions collected by two + // dependents to satisfy each other constraints (for an example see + // the pkg-build/dependency/apply-constraints/resolve-conflict{1,2} + // tests). + + // Points to the desired dependency version constraint, if + // specified, and is NULL otherwise. Can be used as boolean flag. + // + const version_constraint* dep_constr (nullptr); - if (i != map_.end ()) - { - const build_package& bp (i->second.package); + database* ddb (fdb (pdb, dn, das.buildtime)); - dep_optional = !bp.action; // Is pre-entered. + auto i (ddb != nullptr + ? map_.find (*ddb, dn) + : map_.find_dependency (pdb, dn, das.buildtime)); - if (dep_optional && - // - // The version constraint is specified, - // - bp.hold_version && *bp.hold_version) + if (i != map_.end ()) { - assert (bp.constraints.size () == 1); + const build_package& bp (i->second.package); + + dep_optional = !bp.action; // Is pre-entered. + + if (dep_optional && + // + // The version constraint is specified, + // + bp.hold_version && *bp.hold_version) + { + assert (bp.constraints.size () == 1); - const build_package::constraint_type& c (bp.constraints[0]); + const build_package::constraint_type& c (bp.constraints[0]); - dep_constr = &c.value; - system = bp.system; + dep_constr = &c.value; + system = bp.system; - // If the user-specified dependency constraint is the wildcard - // version, then it satisfies any dependency constraint. - // - if (!wildcard (*dep_constr) && - !satisfies (*dep_constr, dp.constraint)) - fail << "unable to satisfy constraints on package " << dn << - info << name << pdb << " depends on (" << dn << " " - << *dp.constraint << ")" << - info << c.dependent << c.db << " depends on (" << dn << " " + // If the user-specified dependency constraint is the wildcard + // version, then it satisfies any dependency constraint. + // + if (!wildcard (*dep_constr) && + !satisfies (*dep_constr, dp.constraint)) + fail << "unable to satisfy constraints on package " << dn << + info << name << pdb << " depends on (" << dn << " " + << *dp.constraint << ")" << + info << c.dependent << c.db << " depends on (" << dn << " " << c.value << ")" << - info << "specify " << dn << " version to satisfy " << name + info << "specify " << dn << " version to satisfy " << name << " constraint"; + } } - } - const dependency& d (!dep_constr - ? dp - : dependency {dn, *dep_constr}); + const dependency& d (!dep_constr + ? dp + : dependency {dn, *dep_constr}); - // First see if this package is already selected. If we already have - // it in the configuration and it satisfies our dependency version - // constraint, then we don't want to be forcing its upgrade (or, - // worse, downgrade). - // - // If the prerequisite configuration is explicitly specified by the - // user, then search for the prerequisite in this specific - // configuration. Otherwise, search recursively in the explicitly - // linked configurations of the dependent configuration. - // - // Note that for the repointed dependent we will always find the - // prerequisite replacement rather than the prerequisite being - // replaced. - // - pair, database*> spd ( - ddb != nullptr - ? make_pair (ddb->find (dn), ddb) - : find_dependency (pdb, dn, das.buildtime)); - - if (ddb == nullptr) - ddb = &pdb; + // First see if this package is already selected. If we already have + // it in the configuration and it satisfies our dependency version + // constraint, then we don't want to be forcing its upgrade (or, + // worse, downgrade). + // + // If the prerequisite configuration is explicitly specified by the + // user, then search for the prerequisite in this specific + // configuration. Otherwise, search recursively in the explicitly + // linked configurations of the dependent configuration. + // + // Note that for the repointed dependent we will always find the + // prerequisite replacement rather than the prerequisite being + // replaced. + // + pair, database*> spd ( + ddb != nullptr + ? make_pair (ddb->find (dn), ddb) + : find_dependency (pdb, dn, das.buildtime)); - shared_ptr& dsp (spd.first); + if (ddb == nullptr) + ddb = &pdb; - pair, - lazy_shared_ptr> rp; + shared_ptr& dsp (spd.first); - shared_ptr& dap (rp.first); + pair, + lazy_shared_ptr> rp; - bool force (false); + shared_ptr& dap (rp.first); - if (dsp != nullptr) - { - // Switch to the selected package configuration. - // - ddb = spd.second; + bool force (false); - // If we are collecting prerequisites of the repointed dependent, - // then only proceed further if this is either a replacement or - // unamended prerequisite and we are up/down-grading (only for the - // latter). - // - if (rpt_prereq_flags != nullptr) + if (dsp != nullptr) { - auto i (rpt_prereq_flags->find (config_package {*ddb, dn})); - - bool unamended (i == rpt_prereq_flags->end ()); - bool replacement (!unamended && i->second); + // Switch to the selected package configuration. + // + ddb = spd.second; - // We can never end up with the prerequisite being replaced, since - // the fdb() function should always return the replacement instead - // (see above). + // If we are collecting prerequisites of the repointed dependent, + // then only proceed further if this is either a replacement or + // unamended prerequisite and we are up/down-grading (only for the + // latter). // - assert (unamended || replacement); + if (rpt_prereq_flags != nullptr) + { + auto i (rpt_prereq_flags->find (config_package {*ddb, dn})); - if (!(replacement || (unamended && ud))) - continue; - } + bool unamended (i == rpt_prereq_flags->end ()); + bool replacement (!unamended && i->second); - if (dsp->state == package_state::broken) - fail << "unable to build broken package " << dn << *ddb << - info << "use 'pkg-purge --force' to remove"; + // We can never end up with the prerequisite being replaced, + // since the fdb() function should always return the replacement + // instead (see above). + // + assert (unamended || replacement); - // If the constraint is imposed by the user we also need to make sure - // that the system flags are the same. - // - if (satisfies (dsp->version, d.constraint) && - (!dep_constr || dsp->system () == system)) - { - system = dsp->system (); + if (!(replacement || (unamended && ud))) + continue; + } - // First try to find an available package for this exact version. - // In particular, this handles the case where a package moves from - // one repository to another (e.g., from testing to stable). For a - // system package we pick the latest one (its exact version - // doesn't really matter). - // - rp = find_available_one (dependent_repo_configs (*ddb), - dn, - (!system - ? version_constraint (dsp->version) - : optional ())); - - // A stub satisfies any version constraint so we weed them out - // (returning stub as an available package feels wrong). - // - if (dap == nullptr || dap->stub ()) - rp = make_available (options, *ddb, dsp); - } - else - // Remember that we may be forcing up/downgrade; we will deal with - // it below. - // - force = true; - } + if (dsp->state == package_state::broken) + fail << "unable to build broken package " << dn << *ddb << + info << "use 'pkg-purge --force' to remove"; - // 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 or - // build2 type) to build it in. - // - // If the current configuration (ddb) is of the suitable type, then we - // use that. Otherwise, we go through its immediate explicit links. If - // only one of them has the suitable 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 - // configuration and use that. If the current configuration is - // private, then search/create in the parent configuration instead. - // - // 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 (das.buildtime && - dsp == nullptr && - ddb->type != buildtime_dependency_type (dn)) - { - database* db (nullptr); - database& sdb (ddb->private_ () ? ddb->parent_config () : *ddb); + // If the constraint is imposed by the user we also need to make + // sure that the system flags are the same. + // + if (satisfies (dsp->version, d.constraint) && + (!dep_constr || dsp->system () == system)) + { + system = dsp->system (); - const string& type (buildtime_dependency_type (dn)); + // First try to find an available package for this exact + // version. In particular, this handles the case where a package + // moves from one repository to another (e.g., from testing to + // stable). For a system package we pick the latest one (its + // exact version doesn't really matter). + // + rp = find_available_one (dependent_repo_configs (*ddb), + dn, + (!system + ? version_constraint (dsp->version) + : optional ())); + + // A stub satisfies any version constraint so we weed them out + // (returning stub as an available package feels wrong). + // + if (dap == nullptr || dap->stub ()) + rp = make_available (options, *ddb, dsp); + } + else + // Remember that we may be forcing up/downgrade; we will deal + // with it below. + // + force = true; + } - // Skip the self-link. + // 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 + // or build2 type) to build it in. + // + // If the current configuration (ddb) is of the suitable type, then + // we use that. Otherwise, we go through its immediate explicit + // links. If only one of them has the suitable 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 configuration and use that. If the current + // configuration is private, then search/create in the parent + // configuration instead. // - const linked_configs& lcs (sdb.explicit_links ()); - for (auto i (lcs.begin_linked ()); i != lcs.end (); ++i) + // 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 (das.buildtime && + dsp == nullptr && + ddb->type != buildtime_dependency_type (dn)) { - database& ldb (i->db); + database* db (nullptr); + database& sdb (ddb->private_ () ? ddb->parent_config () : *ddb); + + const string& type (buildtime_dependency_type (dn)); - if (ldb.type == type) + // Skip the self-link. + // + const linked_configs& lcs (sdb.explicit_links ()); + for (auto i (lcs.begin_linked ()); i != lcs.end (); ++i) { - if (db == nullptr) - db = &ldb; - else - fail << "multiple possible " << type << " configurations for " - << "build-time dependency (" << dp << ")" << - info << db->config_orig << - info << ldb.config_orig << - info << "use --config-* to select the configuration"; + database& ldb (i->db); + + if (ldb.type == type) + { + if (db == nullptr) + db = &ldb; + else + fail << "multiple possible " << type << " configurations for " + << "build-time dependency (" << dp << ")" << + info << db->config_orig << + info << ldb.config_orig << + info << "use --config-* to select the configuration"; + } } - } - // If no suitable configuration is found, then create and link it, - // unless the --no-private-config options is specified. In the - // latter case, print the dependency chain to stdout and exit with - // the specified code. - // - if (db == nullptr) - { - if (options.no_private_config_specified ()) - try + // If no suitable configuration is found, then create and link it, + // unless the --no-private-config options is specified. In the + // latter case, print the dependency chain to stdout and exit with + // the specified code. + // + if (db == nullptr) { - // Note that we don't have the dependency package version yet. - // We could probably rearrange the code and obtain the available - // dependency package by now, given that it comes from the main - // database and may not be specified as system (we would have - // the configuration otherwise). However, let's not complicate - // the code further and instead print the package name and the - // constraint, if present. - // - // Also, in the future, we may still need the configuration to - // obtain the available dependency package for some reason (may - // want to fetch repositories locally, etc). - // - cout << d << '\n'; - - // Note that we also need to clean the dependency chain, to - // prevent the exception guard from printing it to stderr. - // - for (build_package_refs dc (move (dep_chain)); !dc.empty (); ) + if (options.no_private_config_specified ()) + try { - const build_package& p (dc.back ()); + // Note that we don't have the dependency package version yet. + // We could probably rearrange the code and obtain the + // available dependency package by now, given that it comes + // from the main database and may not be specified as system + // (we would have the configuration otherwise). However, let's + // not complicate the code further and instead print the + // package name and the constraint, if present. + // + // Also, in the future, we may still need the configuration to + // obtain the available dependency package for some reason + // (may want to fetch repositories locally, etc). + // + cout << d << '\n'; + + // Note that we also need to clean the dependency chain, to + // prevent the exception guard from printing it to stderr. + // + for (build_package_refs dc (move (dep_chain)); !dc.empty (); ) + { + const build_package& p (dc.back ()); + + cout << p.available_name_version () << ' ' + << p.db.get ().config << '\n'; - cout << p.available_name_version () << ' ' - << p.db.get ().config << '\n'; + dc.pop_back (); + } - dc.pop_back (); + throw failed (options.no_private_config ()); + } + catch (const io_error&) + { + fail << "unable to write to stdout"; } - throw failed (options.no_private_config ()); - } - catch (const io_error&) - { - fail << "unable to write to stdout"; - } + const strings mods {"cc"}; - const strings mods {"cc"}; + const strings vars { + "config.config.load=~" + type, + "config.config.persist+='config.*'@unused=drop"}; - const strings vars { - "config.config.load=~" + type, - "config.config.persist+='config.*'@unused=drop"}; + dir_path cd (bpkg_dir / dir_path (type)); - dir_path cd (bpkg_dir / dir_path (type)); + // Wipe a potentially existing un-linked 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, + sdb.config_orig / cd, + optional (type) /* name */, + type /* type */, + mods, + vars, + false /* existing */, + true /* wipe */); + + // Note that we will copy the name from the configuration unless + // it clashes with one of the existing links. + // + shared_ptr lc (cfg_link (sdb, + sdb.config / cd, + true /* relative */, + nullopt /* name */, + true /* sys_rep */)); + + // Save the newly-created private configuration, together with + // the containing configuration database, for their subsequent + // re- link. + // + apc (sdb, move (cd)); - // Wipe a potentially existing un-linked 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, - sdb.config_orig / cd, - optional (type) /* name */, - type /* type */, - mods, - vars, - false /* existing */, - true /* wipe */); - - // Note that we will copy the name from the configuration unless - // it clashes with one of the existing links. - // - shared_ptr lc (cfg_link (sdb, - sdb.config / cd, - true /* relative */, - nullopt /* name */, - true /* sys_rep */)); - - // Save the newly-created private configuration, together with the - // containing configuration database, for their subsequent re- - // link. - // - apc (sdb, move (cd)); + db = &sdb.find_attached (*lc->id); + } - db = &sdb.find_attached (*lc->id); + ddb = db; // Switch to the dependency configuration. } - ddb = db; // Switch to the dependency configuration. - } - - // Note that building a dependent which is not a build2 module in the - // same configuration with the build2 module it depends upon is an - // error. - // - if (das.buildtime && - !build2_module (name) && - build2_module (dn) && - pdb == *ddb) - { - // Note that the dependent package information is printed by the - // above exception guard. + // Note that building a dependent which is not a build2 module in + // the same configuration with the build2 module it depends upon is + // an error. // - fail << "unable to build build system module " << dn << " in its " - << "dependent package configuration " << pdb.config_orig << - info << "use --config-* to select suitable configuration"; - } + if (das.buildtime && + !build2_module (name) && + build2_module (dn) && + pdb == *ddb) + { + // Note that the dependent package information is printed by the + // above exception guard. + // + fail << "unable to build build system module " << dn << " in its " + << "dependent package configuration " << pdb.config_orig << + info << "use --config-* to select suitable configuration"; + } - // If we didn't get the available package corresponding to the - // selected package, look for any that satisfies the constraint. - // - if (dap == nullptr) - { - // And if we have no repository fragment to look in, then that means - // the package is an orphan (we delay this check until we actually - // need the repository fragment to allow orphans without - // prerequisites). - // - if (af == nullptr) - fail << "package " << pkg.available_name_version_db () - << " is orphaned" << - info << "explicitly upgrade it to a new version"; - - // We look for prerequisites only in the repositories of this - // package (and not in all the repositories of this configuration). - // At first this might look strange, but it also kind of makes - // sense: we only use repositories "approved" for this package - // version. Consider this scenario as an example: hello/1.0.0 and - // libhello/1.0.0 in stable and libhello/2.0.0 in testing. As a - // prerequisite of hello, which version should libhello resolve to? - // While one can probably argue either way, resolving it to 1.0.0 is - // the conservative choice and the user can always override it by - // explicitly building libhello. - // - // Note though, that if this is a test package, then its special - // test dependencies (main packages that refer to it) should be - // searched upstream through the complement repositories - // recursively, since the test packages may only belong to the main - // package's repository and its complements. - // - // @@ Currently we don't implement the reverse direction search for - // the test dependencies, effectively only supporting the common - // case where the main and test packages belong to the same - // repository. Will need to fix this eventually. - // - // Note that this logic (naturally) does not apply if the package is - // already selected by the user (see above). - // - // Also note that for the user-specified dependency version - // constraint we rely on the satisfying package version be present - // in repositories of the first dependent met. As a result, we may - // fail too early if such package version doesn't belong to its - // repositories, but belongs to the ones of some dependent that - // we haven't met yet. Can we just search all repositories for an - // available package of the appropriate version and just take it, - // if present? We could, but then which repository should we pick? - // The wrong choice can introduce some unwanted repositories and - // package versions into play. So instead, we will postpone - // collecting the problematic dependent, expecting that some other - // one will find the appropriate version in its repositories. + // If we didn't get the available package corresponding to the + // selected package, look for any that satisfies the constraint. // - // For a system package we pick the latest version just to make sure - // the package is recognized. An unrecognized package means the - // broken/stale repository (see below). - // - rp = find_available_one (dn, !system ? d.constraint : nullopt, af); - if (dap == nullptr) { - if (dep_constr && !system && postponed) - { - postponed->insert (&pkg); - break; - } - - diag_record dr (fail); - - // Issue diagnostics differently based on the presence of - // available packages for the unsatisfied dependency. + // And if we have no repository fragment to look in, then that + // means the package is an orphan (we delay this check until we + // actually need the repository fragment to allow orphans without + // prerequisites). + // + if (af == nullptr) + fail << "package " << pkg.available_name_version_db () + << " is orphaned" << + info << "explicitly upgrade it to a new version"; + + // We look for prerequisites only in the repositories of this + // package (and not in all the repositories of this + // configuration). At first this might look strange, but it also + // kind of makes sense: we only use repositories "approved" for + // this package version. Consider this scenario as an example: + // hello/1.0.0 and libhello/1.0.0 in stable and libhello/2.0.0 in + // testing. As a prerequisite of hello, which version should + // libhello resolve to? While one can probably argue either way, + // resolving it to 1.0.0 is the conservative choice and the user + // can always override it by explicitly building libhello. + // + // Note though, that if this is a test package, then its special + // test dependencies (main packages that refer to it) should be + // searched upstream through the complement repositories + // recursively, since the test packages may only belong to the main + // package's repository and its complements. + // + // @@ Currently we don't implement the reverse direction search for + // the test dependencies, effectively only supporting the common + // case where the main and test packages belong to the same + // repository. Will need to fix this eventually. // - // Note that there can't be any stubs, since they satisfy any - // constraint and we won't be here if they were. + // Note that this logic (naturally) does not apply if the package + // is already selected by the user (see above). // - vector> aps ( - find_available (dn, nullopt /* version_constraint */, af)); + // Also note that for the user-specified dependency version + // constraint we rely on the satisfying package version be present + // in repositories of the first dependent met. As a result, we may + // fail too early if such package version doesn't belong to its + // repositories, but belongs to the ones of some dependent that + // we haven't met yet. Can we just search all repositories for an + // available package of the appropriate version and just take it, + // if present? We could, but then which repository should we pick? + // The wrong choice can introduce some unwanted repositories and + // package versions into play. So instead, we will postpone + // collecting the problematic dependent, expecting that some other + // one will find the appropriate version in its repositories. + // + // For a system package we pick the latest version just to make + // sure the package is recognized. An unrecognized package means + // the broken/stale repository (see below). + // + rp = find_available_one (dn, !system ? d.constraint : nullopt, af); - if (!aps.empty ()) + if (dap == nullptr) { - dr << "unable to satisfy dependency constraint (" << dn; + if (dep_constr && !system && postponed) + { + postponed->insert (&pkg); + postpone = true; + break; + } + + diag_record dr (fail); - // We need to be careful not to print the wildcard-based - // constraint. + // Issue diagnostics differently based on the presence of + // available packages for the unsatisfied dependency. // - if (d.constraint && (!dep_constr || !wildcard (*dep_constr))) - dr << ' ' << *d.constraint; + // Note that there can't be any stubs, since they satisfy any + // constraint and we won't be here if they were. + // + vector> aps ( + find_available (dn, nullopt /* version_constraint */, af)); - dr << ") of package " << name << pdb << - info << "available " << dn << " versions:"; + if (!aps.empty ()) + { + dr << "unable to satisfy dependency constraint (" << dn; - for (const shared_ptr& ap: aps) - dr << ' ' << ap->version; - } - else - { - dr << "no package available for dependency " << dn - << " of package " << name << pdb; - } + // We need to be careful not to print the wildcard-based + // constraint. + // + if (d.constraint && (!dep_constr || !wildcard (*dep_constr))) + dr << ' ' << *d.constraint; - // Avoid printing this if the dependent package is external since - // it's more often confusing than helpful (they are normally not - // fetched manually). - // - if (!af->location.empty () && - !af->location.directory_based () && - (!dep_constr || system)) - dr << info << "repository " << af->location << " appears to " - << "be broken" << - info << "or the repository state could be stale" << - info << "run 'bpkg rep-fetch' to update"; - } + dr << ") of package " << name << pdb << + info << "available " << dn << " versions:"; - // If all that's available is a stub then we need to make sure the - // package is present in the system repository and it's version - // satisfies the constraint. If a source package is available but - // there is a system package specified on the command line and it's - // version satisfies the constraint then the system package should - // be preferred. To recognize such a case we just need to check if - // the authoritative system version is set and it satisfies the - // constraint. If the corresponding system package is non-optional - // it will be preferred anyway. - // - if (dap->stub ()) - { - // Note that the constraint can safely be printed as it can't - // be a wildcard (produced from the user-specified dependency - // version constraint). If it were, then the system version - // wouldn't be NULL and would satisfy itself. + for (const shared_ptr& ap: aps) + dr << ' ' << ap->version; + } + else + { + dr << "no package available for dependency " << dn + << " of package " << name << pdb; + } + + // Avoid printing this if the dependent package is external + // since it's more often confusing than helpful (they are + // normally not fetched manually). + // + if (!af->location.empty () && + !af->location.directory_based () && + (!dep_constr || system)) + dr << info << "repository " << af->location << " appears to " + << "be broken" << + info << "or the repository state could be stale" << + info << "run 'bpkg rep-fetch' to update"; + } + + // If all that's available is a stub then we need to make sure the + // package is present in the system repository and it's version + // satisfies the constraint. If a source package is available but + // there is a system package specified on the command line and + // it's version satisfies the constraint then the system package + // should be preferred. To recognize such a case we just need to + // check if the authoritative system version is set and it + // satisfies the constraint. If the corresponding system package + // is non-optional it will be preferred anyway. // - 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 (*ddb), d.constraint)) - fail << "dependency " << d << " of package " << name << " is " - << "not available in source" << - info << package_string (dn, - *dap->system_version (*ddb), - true /* system */) - << " does not satisfy the constrains"; - - system = true; - } - else - { - auto p (dap->system_version_authoritative (*ddb)); + if (dap->stub ()) + { + // Note that the constraint can safely be printed as it can't be + // a wildcard (produced from the user-specified dependency + // version constraint). If it were, then the system version + // wouldn't be NULL and would satisfy itself. + // + 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 (*ddb), d.constraint)) + fail << "dependency " << d << " of package " << name << " is " + << "not available in source" << + info << package_string (dn, + *dap->system_version (*ddb), + true /* system */) + << " does not satisfy the constrains"; - if (p.first != nullptr && - p.second && // Authoritative. - satisfies (*p.first, d.constraint)) system = true; + } + else + { + auto p (dap->system_version_authoritative (*ddb)); + + if (p.first != nullptr && + p.second && // Authoritative. + satisfies (*p.first, d.constraint)) + system = true; + } } - } - build_package bp { - build_package::build, - *ddb, - dsp, - dap, - move (rp.second), - nullopt, // Hold package. - nullopt, // Hold version. - {}, // Constraints. - system, - false, // Keep output directory. - false, // Disfigure (from-scratch reconf). - false, // Configure-only. - nullopt, // Checkout root. - false, // Checkout purge. - strings (), // Configuration variables. - {config_package {pdb, name}}, // Required by (dependent). - true, // Required by dependents. - 0}; // State flags. - - // Add our constraint, if we have one. - // - // Note that we always add the constraint implied by the dependent. - // The user-implied constraint, if present, will be added when merging - // from the pre-entered entry. So we will have both constraints for - // completeness. - // - if (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 - // upgrade, then refuse for a held version, warn for a held package, - // and print the info message otherwise, unless the verbosity level is - // less than two. - // - // Note though that while the prerequisite was collected it could have - // happen because it is an optional package and so not being - // pre-collected earlier. Meanwhile the package was specified - // explicitly and we shouldn't consider that as a dependency-driven - // up/down-grade enforcement. - // - // Here is an example of the situation we need to handle properly: - // - // repo: foo/2(->bar/2), bar/0+1 - // build sys:bar/1 - // build foo ?sys:bar/2 - // - const build_package* p ( - collect_build (options, - move (bp), - fdb, - rpt_depts, - apc, - postponed, - &dep_chain)); - - if (p != nullptr && force && !dep_optional) - { - // Fail if the version is held. Otherwise, warn if the package is - // held. + build_package bp { + build_package::build, + *ddb, + dsp, + dap, + move (rp.second), + nullopt, // Hold package. + nullopt, // Hold version. + {}, // Constraints. + system, + false, // Keep output directory. + false, // Disfigure (from-scratch reconf). + false, // Configure-only. + nullopt, // Checkout root. + false, // Checkout purge. + strings (), // Configuration variables. + {config_package {pdb, name}}, // Required by (dependent). + true, // Required by dependents. + 0}; // State flags. + + // Add our constraint, if we have one. + // + // Note that we always add the constraint implied by the dependent. + // The user-implied constraint, if present, will be added when + // merging from the pre-entered entry. So we will have both + // constraints for completeness. + // + if (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 + // upgrade, then refuse for a held version, warn for a held package, + // and print the info message otherwise, unless the verbosity level + // is less than two. + // + // Note though that while the prerequisite was collected it could + // have happen because it is an optional package and so not being + // pre-collected earlier. Meanwhile the package was specified + // explicitly and we shouldn't consider that as a dependency-driven + // up/down-grade enforcement. + // + // Here is an example of the situation we need to handle properly: + // + // repo: foo/2(->bar/2), bar/0+1 + // build sys:bar/1 + // build foo ?sys:bar/2 // - bool f (dsp->hold_version); - bool w (!f && dsp->hold_package); + const build_package* p ( + collect_build (options, + move (bp), + fdb, + rpt_depts, + apc, + postponed, + &dep_chain)); - if (f || w || verb >= 2) + if (p != nullptr && force && !dep_optional) { - const version& av (p->available_version ()); + // Fail if the version is held. Otherwise, warn if the package is + // held. + // + bool f (dsp->hold_version); + bool w (!f && dsp->hold_package); - bool u (av > dsp->version); - bool c (d.constraint); + if (f || w || verb >= 2) + { + const version& av (p->available_version ()); - diag_record dr; + bool u (av > dsp->version); + bool c (d.constraint); - (f ? dr << fail : - w ? dr << warn : - dr << info) - << "package " << name << pdb << " dependency on " - << (c ? "(" : "") << d << (c ? ")" : "") << " is forcing " - << (u ? "up" : "down") << "grade of " << *dsp << *ddb << " to "; + diag_record dr; - // Print both (old and new) package names in full if the system - // attribution changes. - // - if (dsp->system ()) - dr << p->available_name_version (); - else - dr << av; // Can't be a system version so is never wildcard. + (f ? dr << fail : + w ? dr << warn : + dr << info) + << "package " << name << pdb << " dependency on " + << (c ? "(" : "") << d << (c ? ")" : "") << " is forcing " + << (u ? "up" : "down") << "grade of " << *dsp << *ddb + << " to "; + + // Print both (old and new) package names in full if the system + // attribution changes. + // + if (dsp->system ()) + dr << p->available_name_version (); + else + dr << av; // Can't be a system version so is never wildcard. - if (dsp->hold_version) - dr << info << "package version " << *dsp << *ddb << " is held"; + if (dsp->hold_version) + dr << info << "package version " << *dsp << *ddb << " is held"; - if (f) - dr << info << "explicitly request version " - << (u ? "up" : "down") << "grade to continue"; + if (f) + dr << info << "explicitly request version " + << (u ? "up" : "down") << "grade to continue"; + } } } + + if (postpone) + break; } dep_chain.pop_back (); @@ -2479,30 +2484,28 @@ namespace bpkg for (const dependency_alternatives_ex& das: reverse_iterate (ap->dependencies)) { - assert (!das.conditional () && das.size () == 1); // @@ TODO - - const dependency_alternative& da (das.front ()); + assert (!das.conditional () && das.size () == 1); // @@ DEP - assert (da.size () == 1); // @@ DEP - - const dependency& d (da[0]); - const package_name& dn (d.name); + for (const dependency& d: das.front ()) + { + const package_name& dn (d.name); - // Skip special names. - // - if (das.buildtime && (dn == "build2" || dn == "bpkg")) - continue; + // Skip special names. + // + if (das.buildtime && (dn == "build2" || dn == "bpkg")) + continue; - // Note that for the repointed dependent we only order its new and - // unamended prerequisites here. Its replaced prerequisites will - // be ordered below. - // - update (order (pdb, - d.name, - das.buildtime, - chain, - fdb, - false /* reorder */)); + // Note that for the repointed dependent we only order its new + // and unamended prerequisites here. Its replaced prerequisites + // will be ordered below. + // + update (order (pdb, + d.name, + das.buildtime, + chain, + fdb, + false /* reorder */)); + } } } } diff --git a/bpkg/pkg-configure.cxx b/bpkg/pkg-configure.cxx index 1aa3615..ebd6545 100644 --- a/bpkg/pkg-configure.cxx +++ b/bpkg/pkg-configure.cxx @@ -30,91 +30,125 @@ namespace bpkg for (const dependency_alternatives_ex& das: deps) { - assert (!das.conditional ()); //@@ TODO + // @@ DEP Currently we just pick the first alternative with dependencies + // that can all be resolved to the configured packages, satisfying + // the respective constraints. Later, we should also evaluate the + // alternative enable conditions. + // + assert (!das.conditional ()); bool satisfied (false); for (const dependency_alternative& da: das) { - assert (da.size () == 1); // @@ DEP + // Cache the selected packages which correspond to the alternative + // dependencies, pairing them with the respective constraints. If the + // alternative turns out to be fully resolvable, we will add the + // cached packages into the dependent's prerequisites map. + // + small_vector< + pair, + const optional&>, 1> prerequisites; - const dependency& d (da[0]); - const package_name& n (d.name); + dependency_alternative::const_iterator b (da.begin ()); + dependency_alternative::const_iterator i (b); + dependency_alternative::const_iterator e (da.end ()); - if (das.buildtime) + assert (b != e); + + for (; i != e; ++i) { - // Handle special names. - // - if (n == "build2") - { - if (d.constraint && !satisfy_build2 (o, d)) - fail << "unable to satisfy constraint (" << d - << ") for package " << package << - info << "available build2 version is " << build2_version; + const dependency& d (*i); + const package_name& n (d.name); - satisfied = true; - break; - } - else if (n == "bpkg") + if (das.buildtime) { - if (d.constraint && !satisfy_bpkg (o, d)) - fail << "unable to satisfy constraint (" << d - << ") for package " << package << - info << "available bpkg version is " << bpkg_version; - - satisfied = true; - break; + // Handle special names. + // + if (n == "build2") + { + if (d.constraint && !satisfy_build2 (o, d)) + fail << "unable to satisfy constraint (" << d + << ") for package " << package << + info << "available build2 version is " << build2_version; + + continue; + } + else if (n == "bpkg") + { + if (d.constraint && !satisfy_bpkg (o, d)) + fail << "unable to satisfy constraint (" << d + << ") for package " << package << + info << "available bpkg version is " << bpkg_version; + + continue; + } } - } - database* ddb (fdb ? fdb (db, n, das.buildtime) : nullptr); + database* ddb (fdb ? fdb (db, n, das.buildtime) : nullptr); - pair, database*> spd ( - ddb != nullptr - ? make_pair (ddb->find (n), ddb) - : find_dependency (db, n, das.buildtime)); + pair, database*> spd ( + ddb != nullptr + ? make_pair (ddb->find (n), ddb) + : find_dependency (db, n, das.buildtime)); - if (const shared_ptr& dp = spd.first) - { - if (dp->state != package_state::configured) - continue; + const shared_ptr& dp (spd.first); - if (!satisfies (dp->version, d.constraint)) - continue; + if (dp == nullptr || + dp->state != package_state::configured || + !satisfies (dp->version, d.constraint)) + break; // See the package_prerequisites definition for details on creating // the map keys with the database passed. // - auto p ( - r.emplace (lazy_shared_ptr (*spd.second, dp), - d.constraint)); - - // Currently we can only capture a single constraint, so if we - // already have a dependency on this package and one constraint is - // not a subset of the other, complain. - // - if (!p.second) - { - auto& c (p.first->second); - - bool s1 (satisfies (c, d.constraint)); - bool s2 (satisfies (d.constraint, c)); - - if (!s1 && !s2) - fail << "multiple dependencies on package " << n << - info << n << " " << *c << - info << n << " " << *d.constraint; - - if (s2 && !s1) - c = d.constraint; - } - - satisfied = true; - break; + prerequisites.emplace_back ( + lazy_shared_ptr (*spd.second, dp), + d.constraint); + } + + // Try the next alternative if there are unresolved dependencies for + // this alternative. + // + if (i != e) + continue; + + // Now add the selected packages resolved for the alternative into the + // dependent's prerequisites map and skip the remaining alternatives. + // + for (auto& pr: prerequisites) + { + const package_name& pn (pr.first.object_id ()); + const optional& pc (pr.second); + + auto p (r.emplace (pr.first, pc)); + + // Currently we can only capture a single constraint, so if we + // already have a dependency on this package and one constraint is + // not a subset of the other, complain. + // + if (!p.second) + { + auto& c (p.first->second); + + bool s1 (satisfies (c, pc)); + bool s2 (satisfies (pc, c)); + + if (!s1 && !s2) + fail << "multiple dependencies on package " << pn << + info << pn << " " << *c << + info << pn << " " << *pc; + + if (s2 && !s1) + c = pc; + } } + + satisfied = true; + break; } if (!satisfied) - fail << "no configured package satisfies dependency on " << das; + fail << "unable to satisfy dependency on " << das; } return r; diff --git a/bpkg/pkg-verify.cxx b/bpkg/pkg-verify.cxx index a65eeef..f2d9881 100644 --- a/bpkg/pkg-verify.cxx +++ b/bpkg/pkg-verify.cxx @@ -68,58 +68,67 @@ namespace bpkg { for (const dependency_alternative& da: das) { - assert (da.size () == 1); // @@ DEP - - const dependency& d (da[0]); - const package_name& dn (d.name); - - if (dn != "build2" && dn != "bpkg") - continue; - - if (das.size () != 1) + for (const dependency& d: da) { - if (diag_level != 0) - error (p.name (), nv.value_line, nv.value_column) - << "alternatives in " << dn << " dependency"; + const package_name& dn (d.name); - throw failed (); - } + if (dn != "build2" && dn != "bpkg") + continue; - if (dn == "build2") - { - if (d.constraint && !satisfy_build2 (co, d)) + if (da.size () != 1) { if (diag_level != 0) - { - diag_record dr (error); - dr << "unable to satisfy constraint (" << d << ")"; + error (p.name (), nv.value_line, nv.value_column) + << "multiple names in " << dn << " dependency"; - if (!what.empty ()) - dr << " for package " << what; + throw failed (); + } - dr << info << "available build2 version is " - << build2_version; - } + if (das.size () != 1) + { + if (diag_level != 0) + error (p.name (), nv.value_line, nv.value_column) + << "alternatives in " << dn << " dependency"; throw failed (); } - } - else - { - if (d.constraint && !satisfy_bpkg (co, d)) + + if (dn == "build2") { - if (diag_level != 0) + if (d.constraint && !satisfy_build2 (co, d)) { - diag_record dr (error); - dr << "unable to satisfy constraint (" << d << ")"; + if (diag_level != 0) + { + diag_record dr (error); + dr << "unable to satisfy constraint (" << d << ")"; - if (!what.empty ()) - dr << " for package " << what; + if (!what.empty ()) + dr << " for package " << what; - dr << "available bpkg version is " << bpkg_version; + dr << info << "available build2 version is " + << build2_version; + } + + throw failed (); } + } + else + { + if (d.constraint && !satisfy_bpkg (co, d)) + { + if (diag_level != 0) + { + diag_record dr (error); + dr << "unable to satisfy constraint (" << d << ")"; - throw failed (); + if (!what.empty ()) + dr << " for package " << what; + + dr << "available bpkg version is " << bpkg_version; + } + + throw failed (); + } } } } diff --git a/tests/common/dependency-alternatives/t8a/foo-1.0.0.tar.gz b/tests/common/dependency-alternatives/t8a/foo-1.0.0.tar.gz new file mode 100644 index 0000000..bca3658 Binary files /dev/null and b/tests/common/dependency-alternatives/t8a/foo-1.0.0.tar.gz differ diff --git a/tests/common/dependency-alternatives/t8a/libbar-1.0.0.tar.gz b/tests/common/dependency-alternatives/t8a/libbar-1.0.0.tar.gz new file mode 100644 index 0000000..ce7f270 Binary files /dev/null and b/tests/common/dependency-alternatives/t8a/libbar-1.0.0.tar.gz differ diff --git a/tests/common/dependency-alternatives/t8a/libbaz-1.0.0.tar.gz b/tests/common/dependency-alternatives/t8a/libbaz-1.0.0.tar.gz new file mode 100644 index 0000000..1dd802e Binary files /dev/null and b/tests/common/dependency-alternatives/t8a/libbaz-1.0.0.tar.gz differ diff --git a/tests/common/dependency-alternatives/t8a/repositories.manifest b/tests/common/dependency-alternatives/t8a/repositories.manifest new file mode 100644 index 0000000..5b70556 --- /dev/null +++ b/tests/common/dependency-alternatives/t8a/repositories.manifest @@ -0,0 +1 @@ +: 1 diff --git a/tests/pkg-build.testscript b/tests/pkg-build.testscript index 058a08b..213290d 100644 --- a/tests/pkg-build.testscript +++ b/tests/pkg-build.testscript @@ -136,6 +136,12 @@ # | |-- libbox-1.1.0.tar.gz -> * foo ^1.0.0 # | `-- repositories.manifest # | +# |-- t8a +# | |-- foo-1.0.0.tar.gz -> {libbar libbaz} ^1.0.0 +# | |-- libbar-1.0.0.tar.gz +# | |-- libbaz-1.0.0.tar.gz +# | `-- repositories.manifest +# | # `-- git # |-- libbar.git -> style-basic.git (prerequisite repository) # |-- libbaz.git @@ -164,6 +170,7 @@ posix = ($cxx.target.class != 'windows') cp -r $src/t6 $out/t6 && $rep_create $out/t6 &$out/t6/packages.manifest cp -r $src/t7a $out/t7a && $rep_create $out/t7a &$out/t7a/packages.manifest cp -r $src/t7b $out/t7b && $rep_create $out/t7b &$out/t7b/packages.manifest + cp -r $src/t8a $out/t8a && $rep_create $out/t8a &$out/t8a/packages.manifest # Create git repositories. # @@ -2972,6 +2979,34 @@ test.options += --no-progress -$pkg_drop libbar libbaz libfoo } + + : alternative + : + { + +$clone_root_cfg && $rep_add $rep/t8a && $rep_fetch + + : multiple-dependencies + : + { + $clone_cfg; + + $* foo --yes 2>>~%EOE%; + fetched libbaz/1.0.0 + unpacked libbaz/1.0.0 + fetched libbar/1.0.0 + unpacked libbar/1.0.0 + fetched foo/1.0.0 + unpacked foo/1.0.0 + configured libbaz/1.0.0 + configured libbar/1.0.0 + configured foo/1.0.0 + %info: .+foo-1.0.0.+ is up to date% + updated foo/1.0.0 + EOE + + $pkg_drop foo + } + } } : dependent diff --git a/tests/pkg-build/t8a b/tests/pkg-build/t8a new file mode 120000 index 0000000..8fa2bda --- /dev/null +++ b/tests/pkg-build/t8a @@ -0,0 +1 @@ +../common/dependency-alternatives/t8a/ \ No newline at end of file diff --git a/tests/pkg-configure.testscript b/tests/pkg-configure.testscript index 5a7d8aa..eff0a2e 100644 --- a/tests/pkg-configure.testscript +++ b/tests/pkg-configure.testscript @@ -35,6 +35,9 @@ # | | |-- driver.cxx # | | `-- test.out # | `-- version +# | +# |-- t8a (see pkg-build for details) +# | # `-- stable # |-- libbar-1.0.0.tar.gz -> libfoo # |-- libbar-1.1.0.tar.gz -> libfoo >= 1.1.0 @@ -63,6 +66,8 @@ # cp -r $src/stable $out/stable $rep_create $out/stable &$out/stable/packages.manifest + + cp -r $src/t8a $out/t8a && $rep_create $out/t8a &$out/t8a/packages.manifest end test.arguments += config.cxx=$quote($recall($cxx.path) $cxx.config.mode, true) @@ -280,7 +285,7 @@ if ($posix && "$uid" != '0') $pkg_fetch libbar/1.0.0 && $pkg_unpack libbar; $* libbar 2>>EOE != 0; - error: no configured package satisfies dependency on libfoo + error: unable to satisfy dependency on libfoo EOE $pkg_status libbar/1.0.0 1>'libbar unpacked 1.0.0'; @@ -288,7 +293,7 @@ if ($posix && "$uid" != '0') $pkg_unpack libfoo; $* libbar 2>>EOE != 0; - error: no configured package satisfies dependency on libfoo + error: unable to satisfy dependency on libfoo EOE $* libfoo 2>'configured libfoo/1.0.0'; @@ -317,7 +322,7 @@ if ($posix && "$uid" != '0') $pkg_unpack libbar; $* libbar 2>>EOE != 0; - error: no configured package satisfies dependency on libfoo >= 1.1.0 + error: unable to satisfy dependency on libfoo >= 1.1.0 EOE $pkg_disfigure libfoo 2>'disfigured libfoo/1.0.0'; @@ -344,7 +349,7 @@ if ($posix && "$uid" != '0') $pkg_unpack libbar; $* libbar 2>>EOE != 0; - error: no configured package satisfies dependency on libfox | libfoo >= 1.2.0 + error: unable to satisfy dependency on libfox | libfoo >= 1.2.0 EOE $pkg_disfigure libfoo 2>'disfigured libfoo/1.1.0'; @@ -411,3 +416,39 @@ if ($posix && "$uid" != '0') test -d cfg/libhello != 0 } } + +: dependency-alternatives +: +{ + +$clone_root_cfg && $rep_add $rep/t8a && $rep_fetch --trust-yes + + : multiple-dependencies + : + { + $clone_cfg; + + $pkg_fetch foo/1.0.0 && $pkg_unpack foo; + + $pkg_fetch libbar/1.0.0 && $pkg_unpack libbar; + $* libbar 2>!; + + # Make sure that dependent configuration fails if some of the alternative + # dependencies is not configured. + # + $* foo 2>>EOE != 0; + error: unable to satisfy dependency on {libbar ^1.0.0 libbaz ^1.0.0} + EOE + + $pkg_fetch libbaz/1.0.0 && $pkg_unpack libbaz; + $* libbaz 2>!; + + $* foo 2>'configured foo/1.0.0'; + + $pkg_disfigure foo 2>!; + $pkg_purge foo 2>!; + $pkg_disfigure libbaz 2>!; + $pkg_purge libbaz 2>!; + $pkg_disfigure libbar 2>!; + $pkg_purge libbar 2>! + } +} diff --git a/tests/pkg-configure/t8a b/tests/pkg-configure/t8a new file mode 120000 index 0000000..8fa2bda --- /dev/null +++ b/tests/pkg-configure/t8a @@ -0,0 +1 @@ +../common/dependency-alternatives/t8a/ \ No newline at end of file -- cgit v1.1