[Aptitude-svn-commit] r4064 - in branches/aptitude-0.3/aptitude: .
src/generic src/generic/problemresolver
Daniel Burrows
dburrows at costa.debian.org
Wed Sep 7 19:03:18 UTC 2005
Author: dburrows
Date: Wed Sep 7 19:03:15 2005
New Revision: 4064
Added:
branches/aptitude-0.3/aptitude/src/generic/dense_setset.h
Modified:
branches/aptitude-0.3/aptitude/ChangeLog
branches/aptitude-0.3/aptitude/src/generic/aptitude_resolver_universe.h
branches/aptitude-0.3/aptitude/src/generic/problemresolver/problemresolver.h
Log:
Add a dense set-set class optimized for the case where an array
works better than a hash table.
Modified: branches/aptitude-0.3/aptitude/ChangeLog
==============================================================================
--- branches/aptitude-0.3/aptitude/ChangeLog (original)
+++ branches/aptitude-0.3/aptitude/ChangeLog Wed Sep 7 19:03:15 2005
@@ -1,3 +1,12 @@
+2005-09-07 Daniel Burrows <dburrows at debian.org>
+
+ * src/generic/aptitude_resolver_universe.h, src/generic/dense_setset.h, src/generic/problemresolver/problemresolver.h:
+
+ Add a 'dense' set-set class optimized for the case where the set
+ elements can be mapped to a dense domain of integers (i.e., use
+ an array instead of a hash table); yields a small but noticable
+ improvement in the code's performance.
+
2005-09-06 Daniel Burrows <dburrows at debian.org>
* src/download_list.cc:
Modified: branches/aptitude-0.3/aptitude/src/generic/aptitude_resolver_universe.h
==============================================================================
--- branches/aptitude-0.3/aptitude/src/generic/aptitude_resolver_universe.h (original)
+++ branches/aptitude-0.3/aptitude/src/generic/aptitude_resolver_universe.h Wed Sep 7 19:03:15 2005
@@ -884,6 +884,11 @@
// PackageCount is added to make room for the UNINST versions.
return cache->Head().VersionCount+cache->Head().PackageCount;
}
+
+ unsigned long get_package_count() const
+ {
+ return cache->Head().PackageCount;
+ }
};
std::ostream &operator<<(ostream &out, aptitude_resolver_dep d);
Added: branches/aptitude-0.3/aptitude/src/generic/dense_setset.h
==============================================================================
--- (empty file)
+++ branches/aptitude-0.3/aptitude/src/generic/dense_setset.h Wed Sep 7 19:03:15 2005
@@ -0,0 +1,463 @@
+// dense_setset.h -*-c++-*-
+//
+// Copyright (C) 2005 Daniel Burrows
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 2 of
+// the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; see the file COPYING. If not, write to
+// the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+// Boston, MA 02111-1307, USA.
+//
+// Efficient representation of sets-of-sets and sets-of-maps which can
+// be indexed by a subset/superset relation. In particular, given a
+// set of sets SS and a set T, provides the operation "is there an
+// element S' of SS such that each element of S' stands in a relation
+// R to an equivalent element of T" as well as its dual. (with R
+// being the universal relation, this is just a straight subset
+// relation, but other general types of subsumption are also posible)
+//
+// WARNING: this class is completely non-threadsafe. Because
+// find_subset is very heavily used (over a million calls in many test
+// cases), it has a number of optimizations, including one that means
+// YOU MAY NOT HAVE MULTIPLE SIMULTANEOUS CALLERS OF FIND_SUBSET as it
+// uses pre-allocated data structures to speed up its operation (by a
+// factor of 3 or so).
+
+// This is almost the same as setset, but optimized for the case where
+// the individual values are indexed by integers from 0 to N for some
+// N, and it's OK to allocate extra memory at creation time.
+
+#ifndef DENSE_SETSET_H
+#define DENSE_SETSET_H
+
+#ifdef HAVE_CONFIG_H
+#include "../../config.h"
+#endif
+
+#include <set>
+#include <vector>
+#include "immset.h"
+
+/** Maintains a set of imm::sets of Val objects, arranged so that
+ * quick answers to the query "does the set S subsume any element of
+ * this set?" can be produced. At the moment this object increases
+ * monotonically, to keep things simple.
+ */
+template<typename Val, typename IdFunc, typename Compare = std::less<Val> >
+class dense_setset
+{
+private:
+ struct entry
+ {
+ imm::set<Val, Compare> s;
+ /** Field used in the subset-testing algorithm. */
+ mutable unsigned int hit_count;
+
+ entry(const imm::set<Val, Compare> &_s)
+ :s(_s), hit_count(0)
+ {
+ }
+
+ entry()
+ :hit_count(0)
+ {
+ }
+ };
+
+ typedef std::vector<entry> entries_list;
+ entries_list entries;
+
+ typedef std::pair<typename entries_list::size_type, Val> index_entry;
+
+ typedef std::vector<index_entry> * index_type;
+
+ index_type sets_by_key;
+
+ IdFunc extractid;
+
+ void reset_counts() const
+ {
+ for(typename entries_list::const_iterator i = entries.begin();
+ i != entries.end(); ++i)
+ i->hit_count = 0;
+ }
+
+public:
+ class const_iterator
+ {
+ typename entries_list::const_iterator real_iter;
+ public:
+ const_iterator(const typename entries_list::const_iterator &_real_iter)
+ :real_iter(_real_iter)
+ {
+ }
+
+ const_iterator()
+ {
+ }
+
+ const imm::set<Val, Compare> &operator*() const
+ {
+ return real_iter->s;
+ }
+
+ const imm::set<Val, Compare> *operator->() const
+ {
+ return &real_iter->s;
+ }
+
+ const_iterator &operator++()
+ {
+ ++real_iter;
+ return *this;
+ }
+
+ const_iterator &operator--()
+ {
+ --real_iter;
+ return *this;
+ }
+
+ bool operator<(const const_iterator &other) const
+ {
+ return real_iter < other.real_iter;
+ }
+
+ bool operator<=(const const_iterator &other) const
+ {
+ return real_iter <= other.real_iter;
+ }
+
+ bool operator>(const const_iterator &other) const
+ {
+ return real_iter > other.real_iter;
+ }
+
+ bool operator>=(const const_iterator &other) const
+ {
+ return real_iter >= other.real_iter;
+ }
+
+ bool operator==(const const_iterator &other) const
+ {
+ return real_iter == other.real_iter;
+ }
+
+ bool operator!=(const const_iterator &other) const
+ {
+ return real_iter != other.real_iter;
+ }
+
+ const_iterator operator-(typename entries_list::size_type x) const
+ {
+ return real_iter - x;
+ }
+
+ const_iterator operator+(typename entries_list::size_type x) const
+ {
+ return real_iter + x;
+ }
+
+ const_iterator &operator-=(typename entries_list::size_type x)
+ {
+ real_iter -= x;
+ return *this;
+ }
+
+ const_iterator &operator+=(typename entries_list::size_type x)
+ {
+ real_iter += x;
+ return *this;
+ }
+ };
+
+ typedef typename entries_list::size_type size_type;
+
+private:
+ // Used to construct a set traversal that populates the sets_by_key
+ // structure.
+ struct populate_sets_by_key
+ {
+ const typename entries_list::size_type index;
+ const IdFunc &extractid;
+
+ index_type &sets_by_key;
+ public:
+ populate_sets_by_key(index_type &_sets_by_key,
+ const IdFunc &_extractid,
+ size_type _index)
+ :index(_index), extractid(_extractid), sets_by_key(_sets_by_key)
+ {
+ }
+
+ void operator()(const Val &v) const
+ {
+ sets_by_key[extractid(v)].push_back(index_entry(index, v));
+ }
+ };
+
+ // Used by find_subset to tally up intersections.
+ template<typename R>
+ struct tally_intersections
+ {
+ const entries_list &entries;
+ const index_type &sets_by_key;
+ const IdFunc &extractid;
+
+ const R &r;
+
+ public:
+ tally_intersections(const entries_list &_entries,
+ const index_type &_sets_by_key,
+ const IdFunc &_extractid,
+ const R &_r)
+ :entries(_entries), sets_by_key(_sets_by_key), extractid(_extractid), r(_r)
+ {
+ }
+
+ // For each set containing v, add 1 to its hit count.
+ void operator()(const Val &v) const
+ {
+ const std::vector<index_entry> &vals = sets_by_key[extractid(v)];
+
+ for(typename std::vector<index_entry>::const_iterator vi
+ = vals.begin(); vi != vals.end(); ++vi)
+ if(r(vi->second, v))
+ ++entries[vi->first].hit_count;
+ }
+ };
+
+ template<class T>
+ struct universal_relation
+ {
+ public:
+ bool operator()(const T &t1, const T &t2) const
+ {
+ return true;
+ }
+ };
+
+public:
+ dense_setset(size_type n, const IdFunc _extractid = IdFunc())
+ :sets_by_key(new std::vector<index_entry>[n]), extractid(_extractid)
+ {
+ }
+
+ ~dense_setset()
+ {
+ delete[] sets_by_key;
+ }
+
+ const_iterator begin() const
+ {
+ return entries.begin();
+ }
+
+ const_iterator end() const
+ {
+ return entries.end();
+ }
+
+ size_type size() const
+ {
+ return entries.size();
+ }
+
+ /** Add a new set into this set. */
+ void insert(const imm::set<Val, Compare> &s)
+ {
+ typename entries_list::size_type index = entries.size();
+
+ entries.push_back(s);
+ s.for_each(populate_sets_by_key(sets_by_key, extractid, index));
+ }
+
+ /** Find an arbitrary element that is a subset of s. */
+ template<typename R>
+ const_iterator find_subset(const imm::set<Val, Compare> &s,
+ const R &r) const
+ {
+ reset_counts();
+ // For each element that intersects s, count how many times it
+ // intersects. If every element of a set intersects s (i.e., the
+ // count is equal to the set's size) then it is a subset of s.
+ s.for_each(tally_intersections<R>(entries, sets_by_key, extractid, r));
+
+ for(typename entries_list::const_iterator i
+ = entries.begin(); i != entries.end(); ++i)
+ if(i->s.size() == i->hit_count)
+ return i;
+
+ return end();
+ }
+
+ const_iterator find_subset(const imm::set<Val, Compare> &s) const
+ {
+ return find_subset(s, universal_relation<Val>());
+ }
+};
+
+template<typename Key, typename Val, typename IdFunc,
+ typename Compare = std::less<Key> >
+class dense_mapset
+{
+ struct key_id
+ {
+ IdFunc extractid;
+ public:
+ key_id(const IdFunc &f)
+ :extractid(f)
+ {
+ }
+
+ size_t operator()(const std::pair<Key, Val> &p) const
+ {
+ return extractid(p.first);
+ }
+ };
+
+ typedef dense_setset<std::pair<Key, Val>, key_id,
+ imm::key_compare<Key, Val, Compare> > dense_mapset_type;
+
+ dense_mapset_type S;
+
+public:
+ typedef typename dense_mapset_type::size_type size_type;
+
+ class const_iterator
+ {
+ typename dense_mapset_type::const_iterator realiter;
+ public:
+ const_iterator(const typename dense_mapset_type::const_iterator &_realiter)
+ :realiter(_realiter)
+ {
+ }
+
+ const_iterator(const const_iterator &other)
+ :realiter(other.realiter)
+ {
+ }
+
+ const_iterator &operator=(const const_iterator &other)
+ {
+ realiter = other.realiter;
+ return *this;
+ }
+
+ imm::map<Key, Val> operator*() const
+ {
+ return *realiter;
+ }
+
+ bool operator==(const const_iterator &other) const
+ {
+ return realiter == other.realiter;
+ }
+
+ bool operator!=(const const_iterator &other) const
+ {
+ return realiter != other.realiter;
+ }
+
+ bool operator<(const const_iterator &other) const
+ {
+ return realiter < other.realiter;
+ }
+
+ bool operator>(const const_iterator &other) const
+ {
+ return realiter > other.realiter;
+ }
+
+ bool operator<=(const const_iterator &other) const
+ {
+ return realiter <= other.realiter;
+ }
+
+ bool operator>=(const const_iterator &other) const
+ {
+ return realiter >= other.realiter;
+ }
+
+ const_iterator &operator++()
+ {
+ ++realiter;
+ return *this;
+ }
+
+ const_iterator &operator--()
+ {
+ --realiter;
+ return *this;
+ }
+
+ const_iterator operator+(size_type i) const
+ {
+ return realiter + i;
+ }
+
+ const_iterator operator+=(size_type i) const
+ {
+ return realiter + i;
+ }
+
+ const_iterator operator-(size_type i) const
+ {
+ return realiter - i;
+ }
+
+ const_iterator operator-=(size_type i) const
+ {
+ return realiter - i;
+ }
+ };
+
+ dense_mapset(size_type n, const IdFunc &extractid = IdFunc())
+ :S(n, key_id(extractid))
+ {
+ }
+
+ const_iterator begin() const
+ {
+ return S.begin();
+ }
+
+ const_iterator end() const
+ {
+ return const_iterator(S.end());
+ }
+
+ size_type size() const
+ {
+ return S.size();
+ }
+
+ void insert(const imm::map<Key, Val, Compare> &m)
+ {
+ S.insert(m.get_bindings());
+ }
+
+ const_iterator find_submap(const imm::map<Key, Val, Compare> &m) const
+ {
+ return const_iterator(S.find_subset(m.get_bindings()));
+ }
+
+ template<typename R>
+ const_iterator find_submap(const imm::map<Key, Val, Compare> &m,
+ const R &r) const
+ {
+ return const_iterator(S.find_subset(m.get_bindings(), r));
+ }
+};
+
+#endif // DENSE_SETSET_H
+
Modified: branches/aptitude-0.3/aptitude/src/generic/problemresolver/problemresolver.h
==============================================================================
--- branches/aptitude-0.3/aptitude/src/generic/problemresolver/problemresolver.h (original)
+++ branches/aptitude-0.3/aptitude/src/generic/problemresolver/problemresolver.h Wed Sep 7 19:03:15 2005
@@ -52,7 +52,7 @@
#include "exceptions.h"
#include "solution.h"
-#include "../setset.h"
+#include "../dense_setset.h"
/** A dummy iterator that's always an "end" iterator. */
template<class V>
@@ -178,7 +178,7 @@
private:
/** Hash function for packages: */
- struct PackageHash
+ struct ExtractPackageId
{
public:
size_t operator()(const package &p) const
@@ -187,1682 +187,1686 @@
}
};
- /** Compares solutions according to their "goodness". */
- struct solution_goodness_compare
- {
- bool operator()(const solution &s1, const solution &s2) const
- {
- return s1.get_score() < s2.get_score();
- }
- };
-
- /** Compares solutions according to their contents; used to
- * manage "closed".
- */
- struct solution_contents_compare
- {
- bool operator()(const solution &s1, const solution &s2) const
- {
- // Variation on lexicographical comparison.
- //
- // S1 < S2 if
- // - S1(p)=S2(p) for all p<p' and S1(p')<S2(p'), where
- // p,p' \in \dom(S1) \cup dom(S2)
-
-
- // NB: the correctness of this code depends implicitly on the
- // invariant that the score is a function of the contents of the
- // solution. Essentially we're using the score as a sort of
- // hash value on solutions!
- if(s1.get_score() < s2.get_score())
- return true;
- else if(s2.get_score() < s1.get_score())
- return false;
- else if(s1.get_action_score() < s2.get_action_score())
- return true;
- else if(s2.get_action_score() < s1.get_action_score())
- return false;
-
- const imm::map<package,action>
- &a1=s1.get_actions(), &a2=s2.get_actions();
- const imm::set<dep>
- &us1=s1.get_unresolved_soft_deps(), &us2=s2.get_unresolved_soft_deps();
-
-
- // Speed hack: order by size first to avoid traversing the whole
- // tree.
- if(a1.size() < a2.size())
- return true;
- else if(a2.size() < a1.size())
- return false;
- else if(us1.size() < us2.size())
- return true;
- else if(us2.size() < us1.size())
- return false;
- else
- return a1 < a2 || (a2 == a1 && us1 < us2);
- }
- };
-
- // Information regarding the weight given to various parameters;
- // packaged up in a struct so it can be easily used by the solution
- // constructors.
- solution_weights weights;
-
- /** Solutions whose score is smaller than this value will be
- * discarded rather than being enqueued.
- */
- int minimum_score;
-
- /** The "maximum" number of successors to generate for any given
- * node. Note, however, that a full set of successors will always
- * be generated for each broken dependency, so this is not exact.
- *
- * The theoretical justification is that in order to cover all
- * solutions, it's sufficient to generate all successors for a
- * single broken dependency. This avoids the problem that when a
- * large number of packages are broken, or when a single package
- * version with a large number of reverse dependencies is broken,
- * you can end up searching way too many successor nodes, even if
- * all the successors are discarded outright.
- *
- * Unfortunately, only generating one successor has its own
- * problem: solutions might be generated out-of-order (i.e., worse
- * solutions before better). This variable allows you to seek a
- * "happy medium" by generating a reasonable number of successors,
- * but not too many. If a single package breaks a large number of
- * other packages, then any attempt to fix those packages should
- * generate the correct successor (which will then pop to the top
- * of the queue and likely stay there), while if a large number of
- * unrelated packages are broken, it doesn't matter which successor
- * goes first.
- *
- * Note that in practice, turning this up is very likely to result
- * in dreadful performance; the option to do so may very well be
- * removed in the future.
- */
- unsigned int max_successors;
-
- /** The universe in which we are solving problems. */
- const PackageUniverse universe;
-
- /** If \b true, we have exhausted the list of solutions. */
- bool finished:1;
-
- /** If \b true, it is possible that some deferred solutions are
- * no longer "forbidden".
- */
- bool deferred_dirty:1;
-
- /** If \b true, debugging messages will be sent to stdout. */
- bool debug:1;
-
- /** If \b true, so-called "stupid" pairs of actions will be
- * eliminated from solutions. (see paper)
- */
- bool remove_stupid:1;
-
- /** The working queue: */
- std::priority_queue<solution, std::vector<solution>, solution_goodness_compare> open;
-
- /** Stores already-seen solutions: */
- std::set<solution, solution_contents_compare> closed;
-
- /** Stores solutions that were ignored because of user constraints
- * (but could be reinstated later). Disjoint with closed.
- */
- std::set<solution, solution_contents_compare> deferred;
-
- typedef mapset<package, action, PackageHash> conflictset;
-
- /** Stores conflicts: sets of installations that have been
- * determined to be mutually incompatible.
- */
- mapset<package, action, PackageHash> conflicts;
-
- /** The initial set of broken dependencies. Kept here for use in
- * the stupid-elimination algorithm.
- */
- imm::set<dep> initial_broken;
-
- /** Stores versions that have been rejected by the user; distinct
- * from the per-solution reject sets that track changes on a single
- * inference path.
- */
- std::set<version> user_rejected;
-
- /** Stores versions that have been mandated by the user; we should
- * always pick these versions if the chance arises.
- */
- std::set<version> user_mandated;
-
- /** Stores dependencies that have been "hardened" by the user (that
- * aren't allowed to default).
- */
- std::set<dep> user_hardened;
-
- /** Stores generated solutions: */
- std::vector<solution> generated_solutions;
-
- typedef std::set<std::pair<version, version> > stupid_table;
-
- std::ostream &dump_conflict(std::ostream &out, const imm::map<package, action> &conflict) const;
- std::ostream &dump_conflict(std::ostream &out, const action &act) const;
-
-
- /** \param conflictorpair a (package, action) pair contained in a conflict.
- * \param apair a (package, action) pair from a solution or a conflict.
- *
- * \return \b true if the given conflictor matches a. This relation
- * is transitive, so if c1 matches c2 and c2 matches a,
- * then c1 matches a.
- */
- static bool conflictor_matches(const std::pair<package, action> &conflictorpair,
- const std::pair<package, action> &apair)
- {
- const action &conflictor = conflictorpair.second;
- const action &a = apair.second;
-
- if(a.ver != conflictor.ver)
- return false;
-
- if(!conflictor.from_dep_source)
- return true;
- else
- return a.from_dep_source && a.d == conflictor.d;
- }
-
- /** \return \b true if each element of pc2 is matched by an element
- * in pc1.
- */
- static bool conflict_matches(const typename imm::map<package, action> &c,
- const typename imm::map<package, action> &acts)
- {
- typename imm::map<package, action>::const_iterator ci = c.begin();
- typename imm::map<package, action>::const_iterator ai = acts.begin();
-
- while(ci != c.end() &&
- ai != acts.end())
- {
- if(ai->first < ci->first)
- ++ai;
- else if(ci->first < ai->first)
- return false;
- else if(!(conflictor_matches(ci->second, ai->second)))
- return false;
- else
- {
- ++ci;
- ++ai;
- }
- }
-
- return (ci == c.end());
- }
-
- /** Test whether the given partial conflict subsumes an existing
- * conflict.
- *
- * \return a conflict matched by m, or conflicts.end() if no such
- * conflict exists.
- */
- typename conflictset::const_iterator
- find_matching_conflict(const imm::map<package, action> &m) const
- {
- return conflicts.find_submap(m, &conflictor_matches);
- }
-
- /** Test whether the given solution contains a conflict. */
- bool contains_conflict(const solution &s) const
- {
- typename conflictset::const_iterator
- found = find_matching_conflict(s.get_actions());
- bool rval = (found != conflicts.end());
-
- if(debug && rval)
- {
- std::cout << "The conflict ";
- dump_conflict(std::cout, *found);
- std::cout << " is triggered by the solution ";
- s.dump(std::cout);
- std::cout << std::endl;
- }
-
- return rval;
- }
-
- /** Add the given conflict to the set of conflicts. Tests if the
- * conflict is matched by an existing conflict (in which case it's
- * redundant and will be dropped), then tests whether any existing
- * conflicts will be made redundant by this conflict.
- */
- void add_conflict(const imm::map<package, action> &conflict)
- {
- typename conflictset::const_iterator
- found = find_matching_conflict(conflict);
-
- if(found != conflicts.end())
- {
- if(debug)
- {
- std::cout << "Dropping conflict ";
- dump_conflict(std::cout, conflict);
- std::cout << " because it is redundant with ";
- dump_conflict(std::cout, *found);
- std::cout << std::endl;
- }
- }
- else
- // TODO: drop conflicts of which this is a subset. Needs work
- // at the setset level.
- conflicts.insert(conflict);
- }
-
-#if 0
- /** Test whether the given solution contains a conflict when the
- * given action is taken.
- *
- * \return such a conflict if it exists; otherwise conflicts.end().
- */
- typename std::set<std::map<package, act_conflict> >::const_iterator
- will_conflict(const solution &s,
- const act_conflict &a) const
- {
- // For now I'm being lazy and actually generating a full mapping.
- // However, this could be done much more efficiently if optimizing
- // this code is important.
- std::map<package, act_conflict> m;
-
- populate_partial_conflict(s, m);
-
- package p = a.ver.get_package();
-
- m[p] = a;
-
- typename std::set<std::map<package, act_conflict> >::const_iterator
- result = subsumes_any_conflict(m);
-
- if(debug && result != conflicts.end())
- {
- std::cout << "Adding " << p.get_name() << " "
- << a.ver.get_name();
-
- if(a.from_dep_source)
- std::cout << " due to " << a.d;
-
- std::cout << " will trigger conflict ";
- dump(std::cout, *result);
- std::cout << std::endl;
- }
-
- return result;
- }
-
- /** Generate a table listing the "stupid" pairs of actions in a
- * solution. If (a,b) is in the table, then b solves the
- * dependency that triggered a's installation.
- */
- void initialize_stupid_pairs(const solution &s,
- stupid_table &table)
- {
- for(typename std::map<package, action>::const_iterator i = s.get_actions().begin();
- i != s.get_actions().end(); ++i)
- {
- const dep &d = i->second.d;
-
- for(typename dep::solver_iterator j = d.solvers_begin();
- !j.end(); ++j)
- {
- // Don't claim that (v,v) is a stupid pair.
- if(*j == i->second.ver)
- continue;
-
- typename std::map<package, action>::const_iterator found
- = s.get_actions().find((*j).get_package());
-
- // Check whether j will be installed; if so, insert it.
- if(found != s.get_actions().end() &&
- found->second.ver == *j)
- table.insert(std::pair<version, version>(i->second.ver, *j));
- }
- }
- }
-
- /** A solution wrapper suitable for use with broken_under() that
- * drops one mapping from the solution.
- */
- class drop_package
- {
- solution s;
- package drop;
- public:
- drop_package(const solution &_s, const package &_drop)
- :s(_s), drop(_drop)
- {
- }
-
- version version_of(const package &p) const
- {
- if(p == drop)
- return p.current_version();
- else
- return s.version_of(p);
- }
- };
-
- /** An action iterator wrapper that replaces the action on a given
- * package with the given action.
- */
- class swap_action
- {
- typename std::map<package, action>::const_iterator i;
- package replace;
- action with;
- public:
- swap_action(const typename std::map<package, action>::const_iterator &_i,
- const package &_replace, const action &_with)
- : i(_i), replace(_replace), with(_with)
- {
- }
-
- bool operator==(const swap_action &other) const
- {
- return i == other.i && replace == other.replace && with.ver == other.with.ver;
- }
-
- bool operator!=(const swap_action &other) const
- {
- return i != other.i || replace != other.replace || with.ver != other.with.ver;
- }
-
- swap_action &operator++()
- {
- ++i;
- return *this;
- }
-
- const action &operator*() const
- {
- if(i->first == replace)
- return with;
- else
- return i->second;
- }
-
- const action *operator->() const
- {
- if(i->first == replace)
- return &with;
- else
- return &i->second;
- }
- };
-
- /** Check whether dropping a version from a solution breaks
- * dependencies.
- *
- * \param s the "starting" solution; should not have broken deps
- * \param v the version to drop
- * \param d output parameter; if there is a broken dependency,
- * it will be placed in this location.
- *
- * \return \b true if a broken dependency exists.
- */
- bool broken_by_drop(const solution &s,
- const version &v,
- dep &d)
- {
- // Don't flag an error if a dep that's SUPPOSED to be unresolved
- // gets broken.
- const std::set<dep> &ignore_deps = s.get_unresolved_soft_deps();
-
- drop_package dropped(s, v.get_package());
-
- version curr = v.get_package().current_version();
- for(typename version::revdep_iterator i = curr.revdeps_begin();
- !i.end(); ++i)
- if(ignore_deps.find(*i) == ignore_deps.end() &&
- (*i).broken_under(dropped))
- {
- d = *i;
- return true;
- }
-
- for(typename version::revdep_iterator i = curr.revdeps_begin();
- !i.end(); ++i)
- if(ignore_deps.find(*i) == ignore_deps.end() &&
- (*i).broken_under(dropped))
- {
- d = *i;
- return true;
- }
-
- for(typename version::dep_iterator i = curr.deps_begin();
- !i.end(); ++i)
- if(ignore_deps.find(*i) == ignore_deps.end() &&
- (*i).broken_under(dropped))
- {
- d = *i;
- return true;
- }
-
- return false;
- }
-
- /** Represents an empty solution. */
- class null_solution
- {
- public:
- version version_of(const package &p) const
- {
- return p.current_version();
- }
- };
-
- /** Represents a partial solution based directly (by reference) on a
- * reference to a map.
- */
- class map_solution
- {
- const std::map<package, action> &actions;
- public:
- map_solution(const std::map<package, action> &_actions)
- : actions(_actions)
- {
- }
-
- version version_of(const package &p) const
- {
- typename std::map<package, action>::const_iterator found = actions.find(p);
-
- if(found == actions.end())
- return p.current_version();
- else
- return found->second.ver;
- }
- };
-
- /** Represents a possible successor to the given solution, created
- * by installing a single 'new' version.
- */
- template<typename SolutionType>
- class partial_solution
- {
- /** The solution this is based on. */
- const SolutionType &s;
- /** More stuff to install */
- std::map<package, version> installations;
- public:
- /** Initialize a new solution.
- */
- partial_solution (const SolutionType &_s)
- :s(_s)
- {
- }
-
- /** Takes a range of versions and installs them in order. */
- template<typename InputIterator>
- void install(const InputIterator &begin, const InputIterator &end)
- {
- for(InputIterator i = begin; i != end; ++i)
- install(i->get_package(), *i);
- }
-
- void install(const package &p, const version &v)
- {
- assert(installations.find(p) == installations.end());
-
- installations[p]=v;
- }
-
- /** \return the currently installed version of the given
- * package.
- */
- version version_of(const package &pkg) const
- {
- typename std::map<package, version>::const_iterator found=installations.find(pkg);
-
- if(found!=installations.end())
- return found->second;
- else
- return s.version_of(pkg);
- }
-
- /** Test whether the given version is installed in this solution.
- */
- bool ver_installed(const version &test_ver) const
- {
- return version_of(test_ver.get_package()) == test_ver;
- }
- };
-
- /** Wrap a solver list by generating pairs where the second value
- * is a bound constant value.
- */
- class forbid_iter_builder
- {
- typename dep::solver_iterator i;
- dep d;
- public:
- forbid_iter_builder(const typename dep::solver_iterator &_i,
- const dep &_d)
- :i(_i), d(_d)
- {
- }
-
- typename std::pair<version, dep> operator*() const
- {
- return std::pair<version, dep>(*i, d);
- }
-
- forbid_iter_builder &operator++()
- {
- ++i;
- return *this;
- }
-
- bool end() const
- {
- return i.end();
- }
- };
-
- /** Wrap an iterator over pairs to an iterator over the second
- * element of the pair.
- */
- template<typename Iter>
- class project_iter_2nd_impl
- {
- Iter i;
- public:
- project_iter_2nd_impl(const Iter &_i)
- :i(_i)
- {
- }
-
- typename Iter::value_type::second_type &operator*() const
- {
- return i->second;
- }
-
- typename Iter::value_type::second_type *operator->() const
- {
- return &i->second;
- }
-
- project_iter_2nd_impl &operator++()
- {
- ++i;
- return *this;
- }
-
- bool operator==(const project_iter_2nd_impl &other) const
- {
- return i == other.i;
- }
-
- bool operator!=(const project_iter_2nd_impl &other) const
- {
- return i != other.i;
- }
- };
-
- template<typename Iter>
- project_iter_2nd_impl<Iter> project_iter_2nd(const Iter &i)
- {
- return project_iter_2nd_impl<Iter>(i);
- }
-
- /** Wrap an action iterator to select its version element. */
- template<class Iter>
- class project_ver_impl
- {
- Iter i;
- public:
- project_ver_impl(const Iter &_i)
- :i(_i)
- {
- }
-
- bool operator==(const project_ver_impl &other)
- {
- return i == other.i;
- }
-
- bool operator!=(const project_ver_impl &other)
- {
- return i != other.i;
- }
-
- project_ver_impl &operator++()
- {
- ++i;
- return *this;
- }
-
- const version &operator*() const
- {
- return i->ver;
- }
-
- const version *operator->() const
- {
- return &i->ver;
- }
- };
-
- template<typename Iter>
- project_ver_impl<Iter> project_ver(const Iter &i)
- {
- return project_ver_impl<Iter>(i);
- }
-
-
- /** Convert a [begin,end) pair to a single APT-style iterator.
- */
- template<class iter>
- class apt_iter_wrapper_impl
- {
- iter curr, the_end;
- public:
- apt_iter_wrapper_impl(const iter &_begin, const iter &_end)
- :curr(_begin), the_end(_end)
- {
- }
-
- bool end() const
- {
- return curr==the_end;
- }
-
- apt_iter_wrapper_impl &operator++()
- {
- ++curr;
- return *this;
- }
-
- const typename std::iterator_traits<iter>::reference operator*() const
- {
- return *curr;
- }
-
- const typename std::iterator_traits<iter>::reference operator->() const
- {
- return &*curr;
- }
- };
-
- template<class Iter>
- apt_iter_wrapper_impl<Iter> apt_iter_wrapper(const Iter &begin,
- const Iter &end)
- {
- return apt_iter_wrapper_impl<Iter>(begin, end);
- }
-
-
- /** Given a list of actions, remove "obviously" unnecessary actions;
- * that is, actions which are not inspired by any broken
- * dependency. Corresponds to ResolveFrom, but is a bit more
- * extreme in its approach. As usual, no effort is made to avoid
- * stupid pairs.
- *
- * \param actions the set of actions to apply
- *
- * \param unresolved_soft_deps a set of broken dependencies which
- * are permitted (indeed, required?)
- * to be broken at the end of the process.
- *
- * \param forbidden_iter an iterator over the list of versions that
- * should be forbidden in the new solution.
- *
- * \return a solution containing only the actions which appear to
- * be necessary.
- */
- template<typename forbid_iter>
- solution filter_unnecessary_actions(const std::vector<action> &actions,
- const std::set<dep> &unresolved_soft_deps,
- const forbid_iter &forbidden_iter)
- {
- // Versions from which to choose.
- std::set<version> avail_versions(project_ver(actions.begin()),
- project_ver(actions.end()));
-
- // Currently broken deps.
- std::set<dep> broken_deps = initial_broken;
-
- // The solution being built.
- std::map<package, action> output_actions;
-
- // Score of this solution
- int action_score = 0;
-
- while(!broken_deps.empty())
- {
- assert(!avail_versions.empty());
-
- // Pick an "arbitrary" broken dependency.
- dep d = *broken_deps.begin();
- broken_deps.erase(broken_deps.begin());
-
- // If it's to be ignored, just drop it on the floor.
- if(unresolved_soft_deps.find(d) != unresolved_soft_deps.end())
- continue;
-
- // Now, pick an "arbitrary" resolver of this dep.
- //
- // (maybe pick the best-scored one that's available instead?)
- bool found_one = false;
- version solver;
- for(typename dep::solver_iterator si = d.solvers_begin();
- !si.end(); ++si)
- {
- typename std::set<version>::const_iterator found
- = avail_versions.find(*si);
-
- if(found != avail_versions.end())
- {
- found_one = true;
- solver = *si;
- break;
- }
- }
-
- // I have a proof that a solver should exist, but that doesn't
- // mean it does ;-)
- assert(found_one);
- assert(output_actions.find(solver.get_package()) == output_actions.end());
- assert(solver != solver.get_package().current_version());
-
- if(debug)
- std::cout << "Filter: resolving " << d << " with "
- << solver.get_package().get_name() << ":"
- << solver.get_name() << std::endl;
-
- action act(solver, d, output_actions.size());
-
- // Update the set of broken dependencies. FIXME: it should be
- // possible to do this in-place, maybe using immutable sets?
- std::set<dep> new_broken;
- update_broken(partial_solution<map_solution>(output_actions),
- project_ver(&act),
- project_ver((&act)+1),
- unresolved_soft_deps,
- broken_deps,
- new_broken);
-
- // Expensive copy.
- broken_deps = new_broken;
-
- // Finally, update the old solution with the new action.
- output_actions[solver.get_package()] = act;
- action_score += step_score;
- action_score += version_scores[solver.get_id()];
- action_score -= version_scores[solver.get_package().current_version().get_id()];
- }
-
- // By definition we have a solution now.
- int score = action_score + full_solution_score + unfixed_soft_score * unresolved_soft_deps.size();
-
- return solution(project_iter_2nd(output_actions.begin()),
- project_iter_2nd(output_actions.end()),
- unresolved_soft_deps,
- broken_deps,
- forbidden_iter,
- score,
- action_score);
- }
-
- /** Eliminate stupid pairs from the given solution.
- *
- * \param s the solution from which stupid pairs should be removed.
- *
- * \return the new solution
- */
- solution eliminate_stupid(const solution &s)
- {
- assert(s.get_broken().empty());
-
- stupid_table stupid_pairs;
-
- initialize_stupid_pairs(s, stupid_pairs);
-
- solution rval = s;
-
- bool contained_stupid = !stupid_pairs.empty();
-
- while(!stupid_pairs.empty())
- {
- if(debug)
- {
- std::cout << "Eliminating stupid pairs from ";
- rval.dump(std::cout);
- std::cout << std::endl;
- }
-
- // The pair to eliminate; picked (sorta) arbitrarily.
- const typename std::pair<version, version> &victim = *stupid_pairs.begin();
- stupid_pairs.erase(stupid_pairs.begin());
-
- // This is where we discard pairs that are no longer relevant
- // because one of the elements of the pair has been dropped.
- if(rval.get_actions().find(victim.first.get_package()) == rval.get_actions().end() ||
- rval.get_actions().find(victim.second.get_package()) == rval.get_actions().end())
- {
- if(debug)
- std::cout << "Dropping invalidated stupid pair("
- << victim.first.get_package().get_name()
- << ":" << victim.first.get_name() << ","
- << victim.second.get_package().get_name()
- << ":" << victim.second.get_name() << ")"
- << std::endl;
-
- continue;
- }
-
- if(debug)
- std::cout << "Examining stupid pair ("
- << victim.first.get_package().get_name()
- << ":" << victim.first.get_name() << ","
- << victim.second.get_package().get_name()
- << ":" << victim.second.get_name() << ")" << std::endl;
-
- // Suppose we drop the second element in favor of the first.
- // Will that produce a valid solution?
- dep first_broken;
-
- // If something was broken, then we need to use the fallback
- // position: just change the dependency given as the "reason"
- // for the first action.
- if(broken_by_drop(rval, victim.first, first_broken))
- {
- if(debug)
- std::cout << "Unable to drop "
- << victim.first.get_package().get_name()
- << ":" << victim.first.get_name()
- << ", changing justification to "
- << first_broken << std::endl;
-
- typename std::map<package, action>::const_iterator found = rval.get_actions().find(victim.first.get_package());
- assert(found != rval.get_actions().end());
-
- action repl_action(victim.first, first_broken,
- found->second.id);
-
- // Note that the score is perfectly correct, as it depends
- // only on the set of versions in rval (which doesn't
- // change). The set of forbidden versions is in some
- // sense "wrong", but if we have a final solution this
- // shouldn't be a problem.
-
- swap_action swbegin(rval.get_actions().begin(),
- victim.first.get_package(),
- repl_action);
- swap_action swend(rval.get_actions().end(),
- victim.first.get_package(),
- repl_action);
-
- rval = solution(swbegin, swend,
- rval.get_unresolved_soft_deps(),
- rval.get_broken(),
- apt_iter_wrapper(rval.get_forbidden_versions().begin(),
- rval.get_forbidden_versions().end()),
- rval.get_score(), rval.get_action_score());
- }
- else
- {
- if(debug)
- std::cout << "Dropping "
- << victim.first.get_package().get_name()
- << ":" << victim.first.get_name()
- << " and filtering unnecessary installations."
- << std::endl;
- // Ok, it's safe.
- //
- // Generate a node by dropping the second element and
- // changing the version of the first:
- std::vector<action> actions;
-
- for(typename std::map<package, action>::const_iterator i =
- s.get_actions().begin(); i != s.get_actions().end(); ++i)
- {
- if(i->second.ver == victim.first)
- actions.push_back(action(victim.second, i->second.d,
- i->second.id));
- else if(i->second.ver != victim.second)
- actions.push_back(i->second);
- }
-
- rval = filter_unnecessary_actions(actions,
- rval.get_unresolved_soft_deps(),
- apt_iter_wrapper(rval.get_forbidden_versions().begin(),
- rval.get_forbidden_versions().end()));
- }
- }
-
- if(debug)
- {
- if(contained_stupid)
- {
- std::cout << "Done eliminating stupid pairs, result is ";
- rval.dump(std::cout);
- std::cout << std::endl;
- }
- else
- {
- std::cout << "No stupid pairs in ";
- rval.dump(std::cout);
- std::cout << std::endl;
- }
- }
-
- assert(rval.get_broken().empty());
-
- return rval;
- }
-#endif
+ typedef ExtractPackageId PackageHash;
- solution eliminate_stupid(const solution &s) const
- {
- if(debug)
- std::cout << "Would eliminate stupid, but stupid elimination is disabled." << std::endl;
-
- return s;
- }
-
- /** Calculate whether the solution is rejected based on
- * user_rejected by testing whether the intersection of the
- * solution domain and the rejected set is nonempty.
- */
- bool contains_rejected(const solution &s) const
- {
- for(typename std::set<version>::const_iterator uri
- = user_rejected.begin(); uri != user_rejected.end(); ++uri)
- {
- typename imm::map<package, action>::node found = s.get_actions().lookup(uri->get_package());
-
- if(found.isValid() && found.getVal().second.ver == *uri)
- {
- if(debug)
- std::cout << "Rejected version " << found.getVal().first.get_name()
- << " " << found.getVal().second.ver.get_name()
- << " detected." << std::endl;
-
- return true;
- }
- }
-
- return false;
- }
-
- bool breaks_hardened(const solution &s) const
- {
- typename std::set<dep>::const_iterator uh_iter
- = user_hardened.begin();
-
- if(uh_iter == user_hardened.end())
- return false;
-
- typename imm::set<dep>::const_iterator su_iter
- = s.get_unresolved_soft_deps().begin();
-
- while(uh_iter != user_hardened.end() &&
- su_iter != s.get_unresolved_soft_deps().end())
- {
- if(*uh_iter == *su_iter)
- {
- if(debug)
- std::cout << "Broken hardened dependency " << *uh_iter
- << " detected." << std::endl;
-
- return true;
- }
- else if(*uh_iter < *su_iter)
- ++uh_iter;
- else
- ++su_iter;
- }
-
- return false;
- }
-
- /** \return \b true if the given solution passed up an opportunity
- * to include an 'mandated' version.
- */
- bool avoids_mandated(const solution &s) const
- {
- // NB: The current algorithm is not terribly efficient.
- for(typename std::set<version>::const_iterator ai = user_mandated.begin();
- ai != user_mandated.end(); ++ai)
- {
- version v = s.version_of(ai->get_package());
- // If it's already being installed, then we're fine.
- if(v == *ai)
- continue;
- // Don't allow another version of this package to be
- // installed.
- else if(v != ai->get_package().current_version())
- return true;
-
- // Check (very slowly) whether we made a decision where we had
- // the opportunity to use this version (and of course didn't).
- for(typename imm::map<package, action>::const_iterator si = s.get_actions().begin();
- si != s.get_actions().end(); ++si)
- if(si->second.d.solved_by(*ai))
- {
- if(debug)
- {
- std::cout << ai->get_package().get_name() << " version " << ai->get_name() << " is avoided (when resolving " << si->second.d << ") by the solution:" << std::endl;
- s.dump(std::cout);
- std::cout << std::endl;
- }
-
- return true;
- }
- }
-
- return false;
- }
-
- /** \return \b true if the resolution of the given dependency might
- * be affected by a user constraint.
- */
- bool impinges_user_constraint(const dep &d) const
- {
- if(user_hardened.find(d) != user_hardened.end())
- return true;
-
- for(typename dep::solver_iterator si = d.solvers_begin();
- !si.end(); ++si)
- {
- if(user_rejected.find(*si) != user_rejected.end())
- return true;
-
- for(typename std::set<version>::iterator mi = user_mandated.begin();
- mi != user_mandated.end(); ++mi)
- {
- if(mi->get_package() == (*si).get_package())
- return true;
- }
- }
-
- return false;
- }
-
-
- /** \return \b true if the given solution should be deferred. */
- bool should_defer(const solution &s) const
- {
- return contains_rejected(s) || breaks_hardened(s) ||
- avoids_mandated(s);
- }
-
- /** Place any solutions that were deferred and are no longer
- * rejected back on the open queue.
- */
- void reexamine_deferred()
- {
- // NB: the STL guarantees that erasing elements from a set does
- // not invalidate existing iterators. Hence this very careful
- // iteration:
-
- typename std::set<solution, solution_contents_compare>::const_iterator
- i = deferred.begin(), j = i;
-
- while(i != deferred.end())
- {
- ++j;
-
- if(!should_defer(*i))
- {
- open.push(*i);
- deferred.erase(i);
- }
-
- i = j;
- }
-
- // Note that we might have to rescind the "finished" state: the
- // actions above can actually cause new solutions to be available
- // for processing!
- if(finished && !open.empty())
- finished = false;
-
- deferred_dirty = false;
- }
-
- /** \return \b true if the given solution is "irrelevant": that is,
- * either it was already generated and placed in the closed queue,
- * or it includes an already-generated solution as a proper subset.
- */
- bool irrelevant(const solution &s)
- {
- if(closed.find(s) != closed.end())
- return true;
-
- if(contains_conflict(s))
- return true;
-
- // The efficiency of this step hinges on the *assumption* that the
- // number of solutions that will be generated is small relative to
- // the number of potential solutions.
- for(typename std::vector<solution>::const_iterator i=generated_solutions.begin();
- i!=generated_solutions.end(); ++i)
- if(includes(s.get_actions().begin(), s.get_actions().end(),
- i->get_actions().begin(), i->get_actions().end()))
- return true;
-
- if(s.get_score() < minimum_score)
- {
- if(debug)
- std::cout << "Not generating solution (infinite badness " << s.get_score() << "<" << minimum_score << ")" << std::endl;
- return true;
- }
-
- return false;
- }
-
- /** Tries to enqueue the given package.
- *
- * \return \b true if the solution was not irrelevant.
- */
- bool try_enqueue(const solution &s)
- {
- if(irrelevant(s))
- {
- if(debug)
- {
- std::cout << "Dropping irrelevant solution ";
- s.dump(std::cout);
- std::cout << std::endl;
- }
-
- return false;
- }
- else if(should_defer(s))
- {
- if(debug)
- {
- std::cout << "Deferring rejected solution ";
- s.dump(std::cout);
- std::cout << std::endl;
- }
-
- deferred.insert(s);
- return false;
- }
- else
- {
- if(debug)
- {
- std::cout << "Enqueuing ";
- s.dump(std::cout);
- std::cout << std::endl;
- }
-
- open.push(s);
-
- return true;
- }
- }
-
- /** Try to enqueue the given collection of packages. */
- void try_enqueue(const std::vector<solution> &sols)
- {
- for(typename std::vector<solution>::const_iterator
- succi = sols.begin(); succi != sols.end(); ++succi)
- try_enqueue(*succi);
- }
-
- /** Internal routine to check for the legality of a 'move' and
- * generate a conflictor if it's not legal.
- */
- bool is_legal(const solution &s,
- const version &v,
- action &out_act) const
- {
- package p = v.get_package();
- version cur = p.current_version();
- version inst = s.version_of(p);
-
- if(inst != cur)
- {
- if(debug)
- std::cout << "Discarding " << p.get_name() << " "
- << v.get_name() << ": monotonicity violation"
- << std::endl;
-
- out_act.ver = inst;
- out_act.from_dep_source = false;
- return false;
- }
- else
- {
- assert(v != cur);
-
- typename imm::map<version, dep>::node found
- = s.get_forbidden_versions().lookup(v);
-
- if(!found.isValid())
- return true;
- else
- {
- const dep &found_d = found.getVal().second;
-
- if(debug)
- std::cout << "Discarding " << p.get_name() << " "
- << v.get_name() << ": forbidden by the resolution of "
- << found_d << std::endl;
-
- out_act.ver = s.version_of(found_d.get_source().get_package());
- out_act.d = found_d;
- out_act.from_dep_source = true;
-
- return false;
- }
- }
- }
-
- /** Internal routine to insert a new conflictor into a conflict set.
- * Handles the case in which the conflictor subsumes an existing
- * element of the set.
- */
- void insert_conflictor(imm::map<package, action> &conflict,
- const action &act) const
- {
- package p = act.ver.get_package();
- typename imm::map<package, action>::node found
- = conflict.lookup(p);
-
- if(!found.isValid())
- conflict.put(p, act);
- else
- {
- const action &found_act = found.getVal().second;
-
- action a2 = act;
-
- assert(found_act.ver == act.ver);
- if(a2.from_dep_source)
- {
- if(found_act.from_dep_source)
- assert(a2.d == found_act.d);
-
- else
- a2.from_dep_source = false;
- }
- conflict.put(p, a2);
- }
- }
-
- /** Generate a solution and push it onto an encapsulated vector of
- * solutions.
- */
- class real_generator
- {
- std::vector<solution> ⌖
- public:
- real_generator(std::vector<solution> &_target)
- :target(_target)
- {
- }
-
- template<typename a_iter, class u_iter>
- void make_successor(const solution &s,
- const a_iter &abegin, const a_iter &aend,
- const u_iter &ubegin, const u_iter &uend,
- const PackageUniverse &universe,
- const solution_weights &weights) const
- {
- target.push_back(solution::successor(s,
- abegin, aend,
- ubegin, uend,
- universe, weights));
- }
- };
-
- /** Don't actually generate solutions; instead, just count how many
- * *would* be generated. Each time a solution would be generated,
- * the integer referenced by this object is incremented (it is
- * never set to 0; if you need it to be initialized to 0, do that
- * yourself).
- */
- class null_generator
- {
- int &count;
- public:
- null_generator(int &_count)
- :count(_count)
- {
- }
-
- template<typename a_iter, class u_iter>
- void make_successor(const solution &s,
- const a_iter &abegin, const a_iter &aend,
- const u_iter &ubegin, const u_iter &uend,
- const PackageUniverse &universe,
- const solution_weights &weights) const
- {
- ++count;
- }
-
- int get_count() const
- {
- return count;
- }
- };
-
- /** Convenience routine for the below: try to generate a successor
- * by installing a single package version. NB: assumes that the
- * solution's actions have dense identifiers (i.e., less than
- * s.get_actions().size()).
- *
- * \param s the solution for which a successor should be generated
- * \param v the version to install
- * \param d the dependency for which the successor is being generated.
- * \param from_dep_source if \b true, this successor is the result
- * of an action on the source of a dependency
- * \param conflict a map to which conflictors for this version, if
- * any, will be added.
- *
- * \param generator an object supporting the make_successor() routine,
- * as real_generator and null_generator above.
- */
- template<typename SolutionGenerator>
- void generate_single_successor(const solution &s,
- const dep &d,
- const version &v,
- bool from_dep_source,
- imm::map<package, action> &conflict,
- const SolutionGenerator &generator) const
- {
- action conflictor;
-
- if(debug)
- {
- std::cout << "Trying to resolve " << d << " by installing "
- << v.get_package().get_name() << " "
- << v.get_name();
- if(from_dep_source)
- std::cout << " from the dependency source";
-
- std::cout << std::endl;
- }
-
- const int newid = s.get_actions().size();
-
- if(!is_legal(s, v, conflictor))
- insert_conflictor(conflict, conflictor);
- else
- {
- action act(v, d, from_dep_source, newid);
-
- // Speculatively form the new set of actions; doing this
- // rather than forming the whole solution allows us to avoid
- // several rather expensive steps in the successor routine
- // (most notably the formation of the new broken packages
- // set).
- imm::map<package, action> new_acts = s.get_actions();
- new_acts.put(v.get_package(), act);
-
- typename conflictset::const_iterator
- found = find_matching_conflict(new_acts);
-
- if(found == conflicts.end())
- generator.make_successor(s, &act, &act+1,
- (dep *) 0, (dep *) 0,
- universe, weights);
- else
- {
- if(debug)
- {
- std::cout << "Discarding " << v.get_package().get_name()
- << " " << v.get_name() << " due to conflict ";
- dump_conflict(std::cout, *found);
- std::cout << std::endl;
- }
-
- imm::map<package, action> m = *found;
-
- for(typename imm::map<package, action>::const_iterator ci
- = m.begin(); ci != m.end(); ++ci)
- {
- // Discard the version that we were trying to install,
- // so that the caller can use this to form a more
- // general conflict if all its resolutions fail.
- if(ci->first != v.get_package())
- insert_conflictor(conflict, ci->second);
- else
- assert(ci->second.ver == v);
- }
- }
- }
- }
-
- /** Build the successors of a solution node for a particular
- * dependency.
- *
- * \param conflict a map which will be initialized with a conflict
- * explaining the non-appearance of some solvers (if there are no
- * solvers, this will be a full conflict explaining the lack of
- * solvers).
- *
- * \param out a vector onto which the successors should be pushed.
- */
- template<typename SolutionGenerator>
- void generate_successors(const solution &s,
- const dep &d,
- imm::map<package, action> &conflict,
- const SolutionGenerator &generator) const
- {
- version source = d.get_source();
- typename imm::map<package, action>::node
- source_found = s.get_actions().lookup(source.get_package());
-
- // Try moving the source, if it is legal to do so
- if(source_found.isValid())
- insert_conflictor(conflict, action(source, d, false, -1));
- else
- {
- assert(source == source.get_package().current_version());
-
- for(typename package::version_iterator vi = source.get_package().versions_begin();
- !vi.end(); ++vi)
- if(*vi != source)
- generate_single_successor(s, d, *vi, true, conflict, generator);
- }
-
- // Now try installing each target of the dependency.
- for(typename dep::solver_iterator si = d.solvers_begin();
- !si.end(); ++si)
- generate_single_successor(s, d, *si, false, conflict, generator);
-
- // Finally, maybe we can leave this dependency unresolved.
- if(d.is_soft())
- generator.make_successor(s, (action *) 0, (action *) 0,
- &d, &d+1, universe, weights);
- }
-
- /** Processes the given solution by enqueuing its successor nodes
- * (if any are available). Note that for clarity, we now generate
- * *all* successors before examining *any*.
- */
- void process_solution(const solution &s)
- {
- // Any forcings are immediately applied to 'curr', so that
- // forcings are performed ASAP.
- solution curr = s;
-
- assert(!s.get_broken().empty());
-
- // This loop attempts to generate all the successors of a
- // solution. However, if a "forced" dependency arises, it
- // re-verifies all the dependencies of the solution.
- bool done = false;
- while(!done)
- {
- if(debug)
- {
- std::cout << "Processing ";
- curr.dump(std::cout);
- std::cout << std::endl;
- }
-
- done = true;
-
- // Remember the solution whose broken dependencies we're
- // iterating over.
- solution starting_solution = curr;
-
- for(typename imm::set<dep>::const_iterator bi=starting_solution.get_broken().begin();
- bi!=starting_solution.get_broken().end(); ++bi)
-
- {
- // Check for the case where this dependency has been
- // fortuitously solved by forcing another broken
- // dependency.
- if(starting_solution != curr && !(*bi).broken_under(curr))
- continue;
-
- // Assert against impossible conditions (if this happens
- // something is broken elsewhere).
- if(starting_solution == curr && !(*bi).broken_under(curr))
- {
- std::cerr << "Unexpectedly non-broken dependency "
- << *bi << "!" << std::endl;
-
- version source = (*bi).get_source();
-
- if(curr.version_of(source.get_package()) != source)
- std::cerr << " (" << source.get_package().get_name()
- << " " << source.get_name()
- << " is not installed)" << std::endl;
-
- for(typename dep::solver_iterator si = (*bi).solvers_begin();
- !si.end(); ++si)
- if(curr.version_of((*si).get_package()) == *si)
- std::cerr << " (" << source.get_package().get_name()
- << " " << source.get_name()
- << " is not installed)" << std::endl;
-
- abort();
- }
-
- imm::map<package, action> conflict;
-
-
- int num_successors = 0;
- generate_successors(curr, *bi, conflict,
- null_generator(num_successors));
-
-
-
- if(num_successors == 0)
- {
- if(debug)
- {
- std::cout << "Discarding solution; unresolvable dependency "
- << *bi << " with conflict ";
-
- dump_conflict(std::cout, conflict);
-
- std::cout << std::endl;
- }
-
- add_conflict(conflict);
-
- return;
- }
- else if(num_successors == 1)
- {
- if(debug)
- std::cout << "Forced resolution of " << *bi << std::endl;
-
- std::vector<solution> v;
- real_generator g(v);
- // NB: this may do redundant work adding to 'conflict'.
- // Use a generator object to avoid that?
- generate_successors(curr, *bi, conflict,
- real_generator(v));
-
- assert(v.size() == 1);
-
- curr = v.back();
- done = false;
- }
- }
- }
-
- // In the course of forcing dependencies, we might have actually
- // arrived at a solution, in which case we should just enqueue it
- // and stop (a full solution has no successors to generate).
- if(curr.is_full_solution())
- {
- try_enqueue(curr);
- return;
- }
-
- unsigned int nsols = 0;
-
- // First try to enqueue stuff related to a dependency that the
- // user constrained; then just go for a free-for-all.
- for(typename imm::set<dep>::const_iterator bi=curr.get_broken().begin();
- bi!=curr.get_broken().end() && (nsols == 0 ||
- nsols < max_successors); ++bi)
-
- if(impinges_user_constraint(*bi))
- {
- // Is it possible to take this out somehow?
- imm::map<package, action> conflict;
-
- if(debug)
- std::cout << "Generating successors for " << *bi
- << std::endl;
-
- std::vector<solution> v;
- generate_successors(curr, *bi, conflict, real_generator(v));
- try_enqueue(v);
- nsols += v.size();
- }
-
- for(typename imm::set<dep>::const_iterator bi=curr.get_broken().begin();
- bi!=curr.get_broken().end() && (nsols == 0 ||
- nsols < max_successors); ++bi)
- {
- // Is it possible to take this out somehow?
- imm::map<package, action> conflict;
-
- if(debug)
- std::cout << "Generating successors for " << *bi
- << std::endl;
-
- std::vector<solution> v;
- generate_successors(curr, *bi, conflict, real_generator(v));
- try_enqueue(v);
- nsols += v.size();
- }
- }
-public:
-
- /** Construct a new generic_problem_resolver.
- *
- * \param _score_score the score per "step" of a (partial) solution. Typically negative.
- * \param _broken_score the score to add per broken dependency of a (partial) solution. Typically negative.
- * \param _unfixed_soft_score the score to add per soft dependency LEFT UNFIXED. Typically negative.
- * \param infinity a score value that will be considered to be "infinite". Solutions
- * with less than -infinity points will be immediately discarded.
- * \param _full_solution_score a bonus for goal nodes (things
- * that solve all dependencies)
- * \param _universe the universe in which we are working.
- */
- generic_problem_resolver(int _step_score, int _broken_score,
- int _unfixed_soft_score,
- int infinity, unsigned int _max_successors,
- int _full_solution_score,
- const PackageUniverse &_universe)
- :weights(_step_score, _broken_score, _unfixed_soft_score,
- _full_solution_score, _universe.get_version_count()),
- minimum_score(-infinity), max_successors(_max_successors),
- universe(_universe), finished(false), deferred_dirty(false), debug(false), remove_stupid(true)
+ /** Compares solutions according to their "goodness". */
+ struct solution_goodness_compare
+ {
+ bool operator()(const solution &s1, const solution &s2) const
+ {
+ return s1.get_score() < s2.get_score();
+ }
+ };
+
+ /** Compares solutions according to their contents; used to
+ * manage "closed".
+ */
+ struct solution_contents_compare
+ {
+ bool operator()(const solution &s1, const solution &s2) const
+ {
+ // Variation on lexicographical comparison.
+ //
+ // S1 < S2 if
+ // - S1(p)=S2(p) for all p<p' and S1(p')<S2(p'), where
+ // p,p' \in \dom(S1) \cup dom(S2)
+
+
+ // NB: the correctness of this code depends implicitly on the
+ // invariant that the score is a function of the contents of the
+ // solution. Essentially we're using the score as a sort of
+ // hash value on solutions!
+ if(s1.get_score() < s2.get_score())
+ return true;
+ else if(s2.get_score() < s1.get_score())
+ return false;
+ else if(s1.get_action_score() < s2.get_action_score())
+ return true;
+ else if(s2.get_action_score() < s1.get_action_score())
+ return false;
+
+ const imm::map<package,action>
+ &a1=s1.get_actions(), &a2=s2.get_actions();
+ const imm::set<dep>
+ &us1=s1.get_unresolved_soft_deps(), &us2=s2.get_unresolved_soft_deps();
+
+
+ // Speed hack: order by size first to avoid traversing the whole
+ // tree.
+ if(a1.size() < a2.size())
+ return true;
+ else if(a2.size() < a1.size())
+ return false;
+ else if(us1.size() < us2.size())
+ return true;
+ else if(us2.size() < us1.size())
+ return false;
+ else
+ return a1 < a2 || (a2 == a1 && us1 < us2);
+ }
+ };
+
+ // Information regarding the weight given to various parameters;
+ // packaged up in a struct so it can be easily used by the solution
+ // constructors.
+ solution_weights weights;
+
+ /** Solutions whose score is smaller than this value will be
+ * discarded rather than being enqueued.
+ */
+ int minimum_score;
+
+ /** The "maximum" number of successors to generate for any given
+ * node. Note, however, that a full set of successors will always
+ * be generated for each broken dependency, so this is not exact.
+ *
+ * The theoretical justification is that in order to cover all
+ * solutions, it's sufficient to generate all successors for a
+ * single broken dependency. This avoids the problem that when a
+ * large number of packages are broken, or when a single package
+ * version with a large number of reverse dependencies is broken,
+ * you can end up searching way too many successor nodes, even if
+ * all the successors are discarded outright.
+ *
+ * Unfortunately, only generating one successor has its own
+ * problem: solutions might be generated out-of-order (i.e., worse
+ * solutions before better). This variable allows you to seek a
+ * "happy medium" by generating a reasonable number of successors,
+ * but not too many. If a single package breaks a large number of
+ * other packages, then any attempt to fix those packages should
+ * generate the correct successor (which will then pop to the top
+ * of the queue and likely stay there), while if a large number of
+ * unrelated packages are broken, it doesn't matter which successor
+ * goes first.
+ *
+ * Note that in practice, turning this up is very likely to result
+ * in dreadful performance; the option to do so may very well be
+ * removed in the future.
+ */
+ unsigned int max_successors;
+
+ /** The universe in which we are solving problems. */
+ const PackageUniverse universe;
+
+ /** If \b true, we have exhausted the list of solutions. */
+ bool finished:1;
+
+ /** If \b true, it is possible that some deferred solutions are
+ * no longer "forbidden".
+ */
+ bool deferred_dirty:1;
+
+ /** If \b true, debugging messages will be sent to stdout. */
+ bool debug:1;
+
+ /** If \b true, so-called "stupid" pairs of actions will be
+ * eliminated from solutions. (see paper)
+ */
+ bool remove_stupid:1;
+
+ /** The working queue: */
+ std::priority_queue<solution, std::vector<solution>, solution_goodness_compare> open;
+
+ /** Stores already-seen solutions: */
+ std::set<solution, solution_contents_compare> closed;
+
+ /** Stores solutions that were ignored because of user constraints
+ * (but could be reinstated later). Disjoint with closed.
+ */
+ std::set<solution, solution_contents_compare> deferred;
+
+ typedef dense_mapset<package, action, ExtractPackageId> conflictset;
+
+ /** Stores conflicts: sets of installations that have been
+ * determined to be mutually incompatible.
+ */
+ conflictset conflicts;
+
+ /** The initial set of broken dependencies. Kept here for use in
+ * the stupid-elimination algorithm.
+ */
+ imm::set<dep> initial_broken;
+
+ /** Stores versions that have been rejected by the user; distinct
+ * from the per-solution reject sets that track changes on a single
+ * inference path.
+ */
+ std::set<version> user_rejected;
+
+ /** Stores versions that have been mandated by the user; we should
+ * always pick these versions if the chance arises.
+ */
+ std::set<version> user_mandated;
+
+ /** Stores dependencies that have been "hardened" by the user (that
+ * aren't allowed to default).
+ */
+ std::set<dep> user_hardened;
+
+ /** Stores generated solutions: */
+ std::vector<solution> generated_solutions;
+
+ typedef std::set<std::pair<version, version> > stupid_table;
+
+ std::ostream &dump_conflict(std::ostream &out, const imm::map<package, action> &conflict) const;
+ std::ostream &dump_conflict(std::ostream &out, const action &act) const;
+
+
+ /** \param conflictorpair a (package, action) pair contained in a conflict.
+ * \param apair a (package, action) pair from a solution or a conflict.
+ *
+ * \return \b true if the given conflictor matches a. This relation
+ * is transitive, so if c1 matches c2 and c2 matches a,
+ * then c1 matches a.
+ */
+ static bool conflictor_matches(const std::pair<package, action> &conflictorpair,
+ const std::pair<package, action> &apair)
+ {
+ const action &conflictor = conflictorpair.second;
+ const action &a = apair.second;
+
+ if(a.ver != conflictor.ver)
+ return false;
+
+ if(!conflictor.from_dep_source)
+ return true;
+ else
+ return a.from_dep_source && a.d == conflictor.d;
+ }
+
+ /** \return \b true if each element of pc2 is matched by an element
+ * in pc1.
+ */
+ static bool conflict_matches(const typename imm::map<package, action> &c,
+ const typename imm::map<package, action> &acts)
+ {
+ typename imm::map<package, action>::const_iterator ci = c.begin();
+ typename imm::map<package, action>::const_iterator ai = acts.begin();
+
+ while(ci != c.end() &&
+ ai != acts.end())
+ {
+ if(ai->first < ci->first)
+ ++ai;
+ else if(ci->first < ai->first)
+ return false;
+ else if(!(conflictor_matches(ci->second, ai->second)))
+ return false;
+ else
+ {
+ ++ci;
+ ++ai;
+ }
+ }
+
+ return (ci == c.end());
+ }
+
+ /** Test whether the given partial conflict subsumes an existing
+ * conflict.
+ *
+ * \return a conflict matched by m, or conflicts.end() if no such
+ * conflict exists.
+ */
+ typename conflictset::const_iterator
+ find_matching_conflict(const imm::map<package, action> &m) const
+ {
+ return conflicts.find_submap(m, &conflictor_matches);
+ }
+
+ /** Test whether the given solution contains a conflict. */
+ bool contains_conflict(const solution &s) const
+ {
+ typename conflictset::const_iterator
+ found = find_matching_conflict(s.get_actions());
+ bool rval = (found != conflicts.end());
+
+ if(debug && rval)
+ {
+ std::cout << "The conflict ";
+ dump_conflict(std::cout, *found);
+ std::cout << " is triggered by the solution ";
+ s.dump(std::cout);
+ std::cout << std::endl;
+ }
+
+ return rval;
+ }
+
+ /** Add the given conflict to the set of conflicts. Tests if the
+ * conflict is matched by an existing conflict (in which case it's
+ * redundant and will be dropped), then tests whether any existing
+ * conflicts will be made redundant by this conflict.
+ */
+ void add_conflict(const imm::map<package, action> &conflict)
+ {
+ typename conflictset::const_iterator
+ found = find_matching_conflict(conflict);
+
+ if(found != conflicts.end())
+ {
+ if(debug)
+ {
+ std::cout << "Dropping conflict ";
+ dump_conflict(std::cout, conflict);
+ std::cout << " because it is redundant with ";
+ dump_conflict(std::cout, *found);
+ std::cout << std::endl;
+ }
+ }
+ else
+ // TODO: drop conflicts of which this is a subset. Needs work
+ // at the setset level.
+ conflicts.insert(conflict);
+ }
+
+ #if 0
+ /** Test whether the given solution contains a conflict when the
+ * given action is taken.
+ *
+ * \return such a conflict if it exists; otherwise conflicts.end().
+ */
+ typename std::set<std::map<package, act_conflict> >::const_iterator
+ will_conflict(const solution &s,
+ const act_conflict &a) const
+ {
+ // For now I'm being lazy and actually generating a full mapping.
+ // However, this could be done much more efficiently if optimizing
+ // this code is important.
+ std::map<package, act_conflict> m;
+
+ populate_partial_conflict(s, m);
+
+ package p = a.ver.get_package();
+
+ m[p] = a;
+
+ typename std::set<std::map<package, act_conflict> >::const_iterator
+ result = subsumes_any_conflict(m);
+
+ if(debug && result != conflicts.end())
+ {
+ std::cout << "Adding " << p.get_name() << " "
+ << a.ver.get_name();
+
+ if(a.from_dep_source)
+ std::cout << " due to " << a.d;
+
+ std::cout << " will trigger conflict ";
+ dump(std::cout, *result);
+ std::cout << std::endl;
+ }
+
+ return result;
+ }
+
+ /** Generate a table listing the "stupid" pairs of actions in a
+ * solution. If (a,b) is in the table, then b solves the
+ * dependency that triggered a's installation.
+ */
+ void initialize_stupid_pairs(const solution &s,
+ stupid_table &table)
+ {
+ for(typename std::map<package, action>::const_iterator i = s.get_actions().begin();
+ i != s.get_actions().end(); ++i)
+ {
+ const dep &d = i->second.d;
+
+ for(typename dep::solver_iterator j = d.solvers_begin();
+ !j.end(); ++j)
+ {
+ // Don't claim that (v,v) is a stupid pair.
+ if(*j == i->second.ver)
+ continue;
+
+ typename std::map<package, action>::const_iterator found
+ = s.get_actions().find((*j).get_package());
+
+ // Check whether j will be installed; if so, insert it.
+ if(found != s.get_actions().end() &&
+ found->second.ver == *j)
+ table.insert(std::pair<version, version>(i->second.ver, *j));
+ }
+ }
+ }
+
+ /** A solution wrapper suitable for use with broken_under() that
+ * drops one mapping from the solution.
+ */
+ class drop_package
+ {
+ solution s;
+ package drop;
+ public:
+ drop_package(const solution &_s, const package &_drop)
+ :s(_s), drop(_drop)
+ {
+ }
+
+ version version_of(const package &p) const
+ {
+ if(p == drop)
+ return p.current_version();
+ else
+ return s.version_of(p);
+ }
+ };
+
+ /** An action iterator wrapper that replaces the action on a given
+ * package with the given action.
+ */
+ class swap_action
+ {
+ typename std::map<package, action>::const_iterator i;
+ package replace;
+ action with;
+ public:
+ swap_action(const typename std::map<package, action>::const_iterator &_i,
+ const package &_replace, const action &_with)
+ : i(_i), replace(_replace), with(_with)
+ {
+ }
+
+ bool operator==(const swap_action &other) const
+ {
+ return i == other.i && replace == other.replace && with.ver == other.with.ver;
+ }
+
+ bool operator!=(const swap_action &other) const
+ {
+ return i != other.i || replace != other.replace || with.ver != other.with.ver;
+ }
+
+ swap_action &operator++()
+ {
+ ++i;
+ return *this;
+ }
+
+ const action &operator*() const
+ {
+ if(i->first == replace)
+ return with;
+ else
+ return i->second;
+ }
+
+ const action *operator->() const
+ {
+ if(i->first == replace)
+ return &with;
+ else
+ return &i->second;
+ }
+ };
+
+ /** Check whether dropping a version from a solution breaks
+ * dependencies.
+ *
+ * \param s the "starting" solution; should not have broken deps
+ * \param v the version to drop
+ * \param d output parameter; if there is a broken dependency,
+ * it will be placed in this location.
+ *
+ * \return \b true if a broken dependency exists.
+ */
+ bool broken_by_drop(const solution &s,
+ const version &v,
+ dep &d)
+ {
+ // Don't flag an error if a dep that's SUPPOSED to be unresolved
+ // gets broken.
+ const std::set<dep> &ignore_deps = s.get_unresolved_soft_deps();
+
+ drop_package dropped(s, v.get_package());
+
+ version curr = v.get_package().current_version();
+ for(typename version::revdep_iterator i = curr.revdeps_begin();
+ !i.end(); ++i)
+ if(ignore_deps.find(*i) == ignore_deps.end() &&
+ (*i).broken_under(dropped))
+ {
+ d = *i;
+ return true;
+ }
+
+ for(typename version::revdep_iterator i = curr.revdeps_begin();
+ !i.end(); ++i)
+ if(ignore_deps.find(*i) == ignore_deps.end() &&
+ (*i).broken_under(dropped))
+ {
+ d = *i;
+ return true;
+ }
+
+ for(typename version::dep_iterator i = curr.deps_begin();
+ !i.end(); ++i)
+ if(ignore_deps.find(*i) == ignore_deps.end() &&
+ (*i).broken_under(dropped))
+ {
+ d = *i;
+ return true;
+ }
+
+ return false;
+ }
+
+ /** Represents an empty solution. */
+ class null_solution
+ {
+ public:
+ version version_of(const package &p) const
+ {
+ return p.current_version();
+ }
+ };
+
+ /** Represents a partial solution based directly (by reference) on a
+ * reference to a map.
+ */
+ class map_solution
+ {
+ const std::map<package, action> &actions;
+ public:
+ map_solution(const std::map<package, action> &_actions)
+ : actions(_actions)
+ {
+ }
+
+ version version_of(const package &p) const
+ {
+ typename std::map<package, action>::const_iterator found = actions.find(p);
+
+ if(found == actions.end())
+ return p.current_version();
+ else
+ return found->second.ver;
+ }
+ };
+
+ /** Represents a possible successor to the given solution, created
+ * by installing a single 'new' version.
+ */
+ template<typename SolutionType>
+ class partial_solution
+ {
+ /** The solution this is based on. */
+ const SolutionType &s;
+ /** More stuff to install */
+ std::map<package, version> installations;
+ public:
+ /** Initialize a new solution.
+ */
+ partial_solution (const SolutionType &_s)
+ :s(_s)
+ {
+ }
+
+ /** Takes a range of versions and installs them in order. */
+ template<typename InputIterator>
+ void install(const InputIterator &begin, const InputIterator &end)
+ {
+ for(InputIterator i = begin; i != end; ++i)
+ install(i->get_package(), *i);
+ }
+
+ void install(const package &p, const version &v)
+ {
+ assert(installations.find(p) == installations.end());
+
+ installations[p]=v;
+ }
+
+ /** \return the currently installed version of the given
+ * package.
+ */
+ version version_of(const package &pkg) const
+ {
+ typename std::map<package, version>::const_iterator found=installations.find(pkg);
+
+ if(found!=installations.end())
+ return found->second;
+ else
+ return s.version_of(pkg);
+ }
+
+ /** Test whether the given version is installed in this solution.
+ */
+ bool ver_installed(const version &test_ver) const
+ {
+ return version_of(test_ver.get_package()) == test_ver;
+ }
+ };
+
+ /** Wrap a solver list by generating pairs where the second value
+ * is a bound constant value.
+ */
+ class forbid_iter_builder
+ {
+ typename dep::solver_iterator i;
+ dep d;
+ public:
+ forbid_iter_builder(const typename dep::solver_iterator &_i,
+ const dep &_d)
+ :i(_i), d(_d)
+ {
+ }
+
+ typename std::pair<version, dep> operator*() const
+ {
+ return std::pair<version, dep>(*i, d);
+ }
+
+ forbid_iter_builder &operator++()
+ {
+ ++i;
+ return *this;
+ }
+
+ bool end() const
+ {
+ return i.end();
+ }
+ };
+
+ /** Wrap an iterator over pairs to an iterator over the second
+ * element of the pair.
+ */
+ template<typename Iter>
+ class project_iter_2nd_impl
+ {
+ Iter i;
+ public:
+ project_iter_2nd_impl(const Iter &_i)
+ :i(_i)
+ {
+ }
+
+ typename Iter::value_type::second_type &operator*() const
+ {
+ return i->second;
+ }
+
+ typename Iter::value_type::second_type *operator->() const
+ {
+ return &i->second;
+ }
+
+ project_iter_2nd_impl &operator++()
+ {
+ ++i;
+ return *this;
+ }
+
+ bool operator==(const project_iter_2nd_impl &other) const
+ {
+ return i == other.i;
+ }
+
+ bool operator!=(const project_iter_2nd_impl &other) const
+ {
+ return i != other.i;
+ }
+ };
+
+ template<typename Iter>
+ project_iter_2nd_impl<Iter> project_iter_2nd(const Iter &i)
+ {
+ return project_iter_2nd_impl<Iter>(i);
+ }
+
+ /** Wrap an action iterator to select its version element. */
+ template<class Iter>
+ class project_ver_impl
+ {
+ Iter i;
+ public:
+ project_ver_impl(const Iter &_i)
+ :i(_i)
+ {
+ }
+
+ bool operator==(const project_ver_impl &other)
+ {
+ return i == other.i;
+ }
+
+ bool operator!=(const project_ver_impl &other)
+ {
+ return i != other.i;
+ }
+
+ project_ver_impl &operator++()
+ {
+ ++i;
+ return *this;
+ }
+
+ const version &operator*() const
+ {
+ return i->ver;
+ }
+
+ const version *operator->() const
+ {
+ return &i->ver;
+ }
+ };
+
+ template<typename Iter>
+ project_ver_impl<Iter> project_ver(const Iter &i)
+ {
+ return project_ver_impl<Iter>(i);
+ }
+
+
+ /** Convert a [begin,end) pair to a single APT-style iterator.
+ */
+ template<class iter>
+ class apt_iter_wrapper_impl
+ {
+ iter curr, the_end;
+ public:
+ apt_iter_wrapper_impl(const iter &_begin, const iter &_end)
+ :curr(_begin), the_end(_end)
+ {
+ }
+
+ bool end() const
+ {
+ return curr==the_end;
+ }
+
+ apt_iter_wrapper_impl &operator++()
+ {
+ ++curr;
+ return *this;
+ }
+
+ const typename std::iterator_traits<iter>::reference operator*() const
+ {
+ return *curr;
+ }
+
+ const typename std::iterator_traits<iter>::reference operator->() const
+ {
+ return &*curr;
+ }
+ };
+
+ template<class Iter>
+ apt_iter_wrapper_impl<Iter> apt_iter_wrapper(const Iter &begin,
+ const Iter &end)
+ {
+ return apt_iter_wrapper_impl<Iter>(begin, end);
+ }
+
+
+ /** Given a list of actions, remove "obviously" unnecessary actions;
+ * that is, actions which are not inspired by any broken
+ * dependency. Corresponds to ResolveFrom, but is a bit more
+ * extreme in its approach. As usual, no effort is made to avoid
+ * stupid pairs.
+ *
+ * \param actions the set of actions to apply
+ *
+ * \param unresolved_soft_deps a set of broken dependencies which
+ * are permitted (indeed, required?)
+ * to be broken at the end of the process.
+ *
+ * \param forbidden_iter an iterator over the list of versions that
+ * should be forbidden in the new solution.
+ *
+ * \return a solution containing only the actions which appear to
+ * be necessary.
+ */
+ template<typename forbid_iter>
+ solution filter_unnecessary_actions(const std::vector<action> &actions,
+ const std::set<dep> &unresolved_soft_deps,
+ const forbid_iter &forbidden_iter)
+ {
+ // Versions from which to choose.
+ std::set<version> avail_versions(project_ver(actions.begin()),
+ project_ver(actions.end()));
+
+ // Currently broken deps.
+ std::set<dep> broken_deps = initial_broken;
+
+ // The solution being built.
+ std::map<package, action> output_actions;
+
+ // Score of this solution
+ int action_score = 0;
+
+ while(!broken_deps.empty())
+ {
+ assert(!avail_versions.empty());
+
+ // Pick an "arbitrary" broken dependency.
+ dep d = *broken_deps.begin();
+ broken_deps.erase(broken_deps.begin());
+
+ // If it's to be ignored, just drop it on the floor.
+ if(unresolved_soft_deps.find(d) != unresolved_soft_deps.end())
+ continue;
+
+ // Now, pick an "arbitrary" resolver of this dep.
+ //
+ // (maybe pick the best-scored one that's available instead?)
+ bool found_one = false;
+ version solver;
+ for(typename dep::solver_iterator si = d.solvers_begin();
+ !si.end(); ++si)
+ {
+ typename std::set<version>::const_iterator found
+ = avail_versions.find(*si);
+
+ if(found != avail_versions.end())
+ {
+ found_one = true;
+ solver = *si;
+ break;
+ }
+ }
+
+ // I have a proof that a solver should exist, but that doesn't
+ // mean it does ;-)
+ assert(found_one);
+ assert(output_actions.find(solver.get_package()) == output_actions.end());
+ assert(solver != solver.get_package().current_version());
+
+ if(debug)
+ std::cout << "Filter: resolving " << d << " with "
+ << solver.get_package().get_name() << ":"
+ << solver.get_name() << std::endl;
+
+ action act(solver, d, output_actions.size());
+
+ // Update the set of broken dependencies. FIXME: it should be
+ // possible to do this in-place, maybe using immutable sets?
+ std::set<dep> new_broken;
+ update_broken(partial_solution<map_solution>(output_actions),
+ project_ver(&act),
+ project_ver((&act)+1),
+ unresolved_soft_deps,
+ broken_deps,
+ new_broken);
+
+ // Expensive copy.
+ broken_deps = new_broken;
+
+ // Finally, update the old solution with the new action.
+ output_actions[solver.get_package()] = act;
+ action_score += step_score;
+ action_score += version_scores[solver.get_id()];
+ action_score -= version_scores[solver.get_package().current_version().get_id()];
+ }
+
+ // By definition we have a solution now.
+ int score = action_score + full_solution_score + unfixed_soft_score * unresolved_soft_deps.size();
+
+ return solution(project_iter_2nd(output_actions.begin()),
+ project_iter_2nd(output_actions.end()),
+ unresolved_soft_deps,
+ broken_deps,
+ forbidden_iter,
+ score,
+ action_score);
+ }
+
+ /** Eliminate stupid pairs from the given solution.
+ *
+ * \param s the solution from which stupid pairs should be removed.
+ *
+ * \return the new solution
+ */
+ solution eliminate_stupid(const solution &s)
+ {
+ assert(s.get_broken().empty());
+
+ stupid_table stupid_pairs;
+
+ initialize_stupid_pairs(s, stupid_pairs);
+
+ solution rval = s;
+
+ bool contained_stupid = !stupid_pairs.empty();
+
+ while(!stupid_pairs.empty())
+ {
+ if(debug)
+ {
+ std::cout << "Eliminating stupid pairs from ";
+ rval.dump(std::cout);
+ std::cout << std::endl;
+ }
+
+ // The pair to eliminate; picked (sorta) arbitrarily.
+ const typename std::pair<version, version> &victim = *stupid_pairs.begin();
+ stupid_pairs.erase(stupid_pairs.begin());
+
+ // This is where we discard pairs that are no longer relevant
+ // because one of the elements of the pair has been dropped.
+ if(rval.get_actions().find(victim.first.get_package()) == rval.get_actions().end() ||
+ rval.get_actions().find(victim.second.get_package()) == rval.get_actions().end())
+ {
+ if(debug)
+ std::cout << "Dropping invalidated stupid pair("
+ << victim.first.get_package().get_name()
+ << ":" << victim.first.get_name() << ","
+ << victim.second.get_package().get_name()
+ << ":" << victim.second.get_name() << ")"
+ << std::endl;
+
+ continue;
+ }
+
+ if(debug)
+ std::cout << "Examining stupid pair ("
+ << victim.first.get_package().get_name()
+ << ":" << victim.first.get_name() << ","
+ << victim.second.get_package().get_name()
+ << ":" << victim.second.get_name() << ")" << std::endl;
+
+ // Suppose we drop the second element in favor of the first.
+ // Will that produce a valid solution?
+ dep first_broken;
+
+ // If something was broken, then we need to use the fallback
+ // position: just change the dependency given as the "reason"
+ // for the first action.
+ if(broken_by_drop(rval, victim.first, first_broken))
+ {
+ if(debug)
+ std::cout << "Unable to drop "
+ << victim.first.get_package().get_name()
+ << ":" << victim.first.get_name()
+ << ", changing justification to "
+ << first_broken << std::endl;
+
+ typename std::map<package, action>::const_iterator found = rval.get_actions().find(victim.first.get_package());
+ assert(found != rval.get_actions().end());
+
+ action repl_action(victim.first, first_broken,
+ found->second.id);
+
+ // Note that the score is perfectly correct, as it depends
+ // only on the set of versions in rval (which doesn't
+ // change). The set of forbidden versions is in some
+ // sense "wrong", but if we have a final solution this
+ // shouldn't be a problem.
+
+ swap_action swbegin(rval.get_actions().begin(),
+ victim.first.get_package(),
+ repl_action);
+ swap_action swend(rval.get_actions().end(),
+ victim.first.get_package(),
+ repl_action);
+
+ rval = solution(swbegin, swend,
+ rval.get_unresolved_soft_deps(),
+ rval.get_broken(),
+ apt_iter_wrapper(rval.get_forbidden_versions().begin(),
+ rval.get_forbidden_versions().end()),
+ rval.get_score(), rval.get_action_score());
+ }
+ else
+ {
+ if(debug)
+ std::cout << "Dropping "
+ << victim.first.get_package().get_name()
+ << ":" << victim.first.get_name()
+ << " and filtering unnecessary installations."
+ << std::endl;
+ // Ok, it's safe.
+ //
+ // Generate a node by dropping the second element and
+ // changing the version of the first:
+ std::vector<action> actions;
+
+ for(typename std::map<package, action>::const_iterator i =
+ s.get_actions().begin(); i != s.get_actions().end(); ++i)
+ {
+ if(i->second.ver == victim.first)
+ actions.push_back(action(victim.second, i->second.d,
+ i->second.id));
+ else if(i->second.ver != victim.second)
+ actions.push_back(i->second);
+ }
+
+ rval = filter_unnecessary_actions(actions,
+ rval.get_unresolved_soft_deps(),
+ apt_iter_wrapper(rval.get_forbidden_versions().begin(),
+ rval.get_forbidden_versions().end()));
+ }
+ }
+
+ if(debug)
+ {
+ if(contained_stupid)
+ {
+ std::cout << "Done eliminating stupid pairs, result is ";
+ rval.dump(std::cout);
+ std::cout << std::endl;
+ }
+ else
+ {
+ std::cout << "No stupid pairs in ";
+ rval.dump(std::cout);
+ std::cout << std::endl;
+ }
+ }
+
+ assert(rval.get_broken().empty());
+
+ return rval;
+ }
+ #endif
+
+ solution eliminate_stupid(const solution &s) const
+ {
+ if(debug)
+ std::cout << "Would eliminate stupid, but stupid elimination is disabled." << std::endl;
+
+ return s;
+ }
+
+ /** Calculate whether the solution is rejected based on
+ * user_rejected by testing whether the intersection of the
+ * solution domain and the rejected set is nonempty.
+ */
+ bool contains_rejected(const solution &s) const
+ {
+ for(typename std::set<version>::const_iterator uri
+ = user_rejected.begin(); uri != user_rejected.end(); ++uri)
+ {
+ typename imm::map<package, action>::node found = s.get_actions().lookup(uri->get_package());
+
+ if(found.isValid() && found.getVal().second.ver == *uri)
+ {
+ if(debug)
+ std::cout << "Rejected version " << found.getVal().first.get_name()
+ << " " << found.getVal().second.ver.get_name()
+ << " detected." << std::endl;
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ bool breaks_hardened(const solution &s) const
+ {
+ typename std::set<dep>::const_iterator uh_iter
+ = user_hardened.begin();
+
+ if(uh_iter == user_hardened.end())
+ return false;
+
+ typename imm::set<dep>::const_iterator su_iter
+ = s.get_unresolved_soft_deps().begin();
+
+ while(uh_iter != user_hardened.end() &&
+ su_iter != s.get_unresolved_soft_deps().end())
+ {
+ if(*uh_iter == *su_iter)
+ {
+ if(debug)
+ std::cout << "Broken hardened dependency " << *uh_iter
+ << " detected." << std::endl;
+
+ return true;
+ }
+ else if(*uh_iter < *su_iter)
+ ++uh_iter;
+ else
+ ++su_iter;
+ }
+
+ return false;
+ }
+
+ /** \return \b true if the given solution passed up an opportunity
+ * to include an 'mandated' version.
+ */
+ bool avoids_mandated(const solution &s) const
+ {
+ // NB: The current algorithm is not terribly efficient.
+ for(typename std::set<version>::const_iterator ai = user_mandated.begin();
+ ai != user_mandated.end(); ++ai)
+ {
+ version v = s.version_of(ai->get_package());
+ // If it's already being installed, then we're fine.
+ if(v == *ai)
+ continue;
+ // Don't allow another version of this package to be
+ // installed.
+ else if(v != ai->get_package().current_version())
+ return true;
+
+ // Check (very slowly) whether we made a decision where we had
+ // the opportunity to use this version (and of course didn't).
+ for(typename imm::map<package, action>::const_iterator si = s.get_actions().begin();
+ si != s.get_actions().end(); ++si)
+ if(si->second.d.solved_by(*ai))
+ {
+ if(debug)
+ {
+ std::cout << ai->get_package().get_name() << " version " << ai->get_name() << " is avoided (when resolving " << si->second.d << ") by the solution:" << std::endl;
+ s.dump(std::cout);
+ std::cout << std::endl;
+ }
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /** \return \b true if the resolution of the given dependency might
+ * be affected by a user constraint.
+ */
+ bool impinges_user_constraint(const dep &d) const
+ {
+ if(user_hardened.find(d) != user_hardened.end())
+ return true;
+
+ for(typename dep::solver_iterator si = d.solvers_begin();
+ !si.end(); ++si)
+ {
+ if(user_rejected.find(*si) != user_rejected.end())
+ return true;
+
+ for(typename std::set<version>::iterator mi = user_mandated.begin();
+ mi != user_mandated.end(); ++mi)
+ {
+ if(mi->get_package() == (*si).get_package())
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+
+ /** \return \b true if the given solution should be deferred. */
+ bool should_defer(const solution &s) const
+ {
+ return contains_rejected(s) || breaks_hardened(s) ||
+ avoids_mandated(s);
+ }
+
+ /** Place any solutions that were deferred and are no longer
+ * rejected back on the open queue.
+ */
+ void reexamine_deferred()
+ {
+ // NB: the STL guarantees that erasing elements from a set does
+ // not invalidate existing iterators. Hence this very careful
+ // iteration:
+
+ typename std::set<solution, solution_contents_compare>::const_iterator
+ i = deferred.begin(), j = i;
+
+ while(i != deferred.end())
+ {
+ ++j;
+
+ if(!should_defer(*i))
+ {
+ open.push(*i);
+ deferred.erase(i);
+ }
+
+ i = j;
+ }
+
+ // Note that we might have to rescind the "finished" state: the
+ // actions above can actually cause new solutions to be available
+ // for processing!
+ if(finished && !open.empty())
+ finished = false;
+
+ deferred_dirty = false;
+ }
+
+ /** \return \b true if the given solution is "irrelevant": that is,
+ * either it was already generated and placed in the closed queue,
+ * or it includes an already-generated solution as a proper subset.
+ */
+ bool irrelevant(const solution &s)
+ {
+ if(closed.find(s) != closed.end())
+ return true;
+
+ if(contains_conflict(s))
+ return true;
+
+ // The efficiency of this step hinges on the *assumption* that the
+ // number of solutions that will be generated is small relative to
+ // the number of potential solutions.
+ for(typename std::vector<solution>::const_iterator i=generated_solutions.begin();
+ i!=generated_solutions.end(); ++i)
+ if(includes(s.get_actions().begin(), s.get_actions().end(),
+ i->get_actions().begin(), i->get_actions().end()))
+ return true;
+
+ if(s.get_score() < minimum_score)
+ {
+ if(debug)
+ std::cout << "Not generating solution (infinite badness " << s.get_score() << "<" << minimum_score << ")" << std::endl;
+ return true;
+ }
+
+ return false;
+ }
+
+ /** Tries to enqueue the given package.
+ *
+ * \return \b true if the solution was not irrelevant.
+ */
+ bool try_enqueue(const solution &s)
+ {
+ if(irrelevant(s))
+ {
+ if(debug)
+ {
+ std::cout << "Dropping irrelevant solution ";
+ s.dump(std::cout);
+ std::cout << std::endl;
+ }
+
+ return false;
+ }
+ else if(should_defer(s))
+ {
+ if(debug)
+ {
+ std::cout << "Deferring rejected solution ";
+ s.dump(std::cout);
+ std::cout << std::endl;
+ }
+
+ deferred.insert(s);
+ return false;
+ }
+ else
+ {
+ if(debug)
+ {
+ std::cout << "Enqueuing ";
+ s.dump(std::cout);
+ std::cout << std::endl;
+ }
+
+ open.push(s);
+
+ return true;
+ }
+ }
+
+ /** Try to enqueue the given collection of packages. */
+ void try_enqueue(const std::vector<solution> &sols)
+ {
+ for(typename std::vector<solution>::const_iterator
+ succi = sols.begin(); succi != sols.end(); ++succi)
+ try_enqueue(*succi);
+ }
+
+ /** Internal routine to check for the legality of a 'move' and
+ * generate a conflictor if it's not legal.
+ */
+ bool is_legal(const solution &s,
+ const version &v,
+ action &out_act) const
+ {
+ package p = v.get_package();
+ version cur = p.current_version();
+ version inst = s.version_of(p);
+
+ if(inst != cur)
+ {
+ if(debug)
+ std::cout << "Discarding " << p.get_name() << " "
+ << v.get_name() << ": monotonicity violation"
+ << std::endl;
+
+ out_act.ver = inst;
+ out_act.from_dep_source = false;
+ return false;
+ }
+ else
+ {
+ assert(v != cur);
+
+ typename imm::map<version, dep>::node found
+ = s.get_forbidden_versions().lookup(v);
+
+ if(!found.isValid())
+ return true;
+ else
+ {
+ const dep &found_d = found.getVal().second;
+
+ if(debug)
+ std::cout << "Discarding " << p.get_name() << " "
+ << v.get_name() << ": forbidden by the resolution of "
+ << found_d << std::endl;
+
+ out_act.ver = s.version_of(found_d.get_source().get_package());
+ out_act.d = found_d;
+ out_act.from_dep_source = true;
+
+ return false;
+ }
+ }
+ }
+
+ /** Internal routine to insert a new conflictor into a conflict set.
+ * Handles the case in which the conflictor subsumes an existing
+ * element of the set.
+ */
+ void insert_conflictor(imm::map<package, action> &conflict,
+ const action &act) const
+ {
+ package p = act.ver.get_package();
+ typename imm::map<package, action>::node found
+ = conflict.lookup(p);
+
+ if(!found.isValid())
+ conflict.put(p, act);
+ else
+ {
+ const action &found_act = found.getVal().second;
+
+ action a2 = act;
+
+ assert(found_act.ver == act.ver);
+ if(a2.from_dep_source)
+ {
+ if(found_act.from_dep_source)
+ assert(a2.d == found_act.d);
+
+ else
+ a2.from_dep_source = false;
+ }
+ conflict.put(p, a2);
+ }
+ }
+
+ /** Generate a solution and push it onto an encapsulated vector of
+ * solutions.
+ */
+ class real_generator
+ {
+ std::vector<solution> ⌖
+ public:
+ real_generator(std::vector<solution> &_target)
+ :target(_target)
+ {
+ }
+
+ template<typename a_iter, class u_iter>
+ void make_successor(const solution &s,
+ const a_iter &abegin, const a_iter &aend,
+ const u_iter &ubegin, const u_iter &uend,
+ const PackageUniverse &universe,
+ const solution_weights &weights) const
+ {
+ target.push_back(solution::successor(s,
+ abegin, aend,
+ ubegin, uend,
+ universe, weights));
+ }
+ };
+
+ /** Don't actually generate solutions; instead, just count how many
+ * *would* be generated. Each time a solution would be generated,
+ * the integer referenced by this object is incremented (it is
+ * never set to 0; if you need it to be initialized to 0, do that
+ * yourself).
+ */
+ class null_generator
+ {
+ int &count;
+ public:
+ null_generator(int &_count)
+ :count(_count)
+ {
+ }
+
+ template<typename a_iter, class u_iter>
+ void make_successor(const solution &s,
+ const a_iter &abegin, const a_iter &aend,
+ const u_iter &ubegin, const u_iter &uend,
+ const PackageUniverse &universe,
+ const solution_weights &weights) const
+ {
+ ++count;
+ }
+
+ int get_count() const
+ {
+ return count;
+ }
+ };
+
+ /** Convenience routine for the below: try to generate a successor
+ * by installing a single package version. NB: assumes that the
+ * solution's actions have dense identifiers (i.e., less than
+ * s.get_actions().size()).
+ *
+ * \param s the solution for which a successor should be generated
+ * \param v the version to install
+ * \param d the dependency for which the successor is being generated.
+ * \param from_dep_source if \b true, this successor is the result
+ * of an action on the source of a dependency
+ * \param conflict a map to which conflictors for this version, if
+ * any, will be added.
+ *
+ * \param generator an object supporting the make_successor() routine,
+ * as real_generator and null_generator above.
+ */
+ template<typename SolutionGenerator>
+ void generate_single_successor(const solution &s,
+ const dep &d,
+ const version &v,
+ bool from_dep_source,
+ imm::map<package, action> &conflict,
+ const SolutionGenerator &generator) const
+ {
+ action conflictor;
+
+ if(debug)
+ {
+ std::cout << "Trying to resolve " << d << " by installing "
+ << v.get_package().get_name() << " "
+ << v.get_name();
+ if(from_dep_source)
+ std::cout << " from the dependency source";
+
+ std::cout << std::endl;
+ }
+
+ const int newid = s.get_actions().size();
+
+ if(!is_legal(s, v, conflictor))
+ insert_conflictor(conflict, conflictor);
+ else
+ {
+ action act(v, d, from_dep_source, newid);
+
+ // Speculatively form the new set of actions; doing this
+ // rather than forming the whole solution allows us to avoid
+ // several rather expensive steps in the successor routine
+ // (most notably the formation of the new broken packages
+ // set).
+ imm::map<package, action> new_acts = s.get_actions();
+ new_acts.put(v.get_package(), act);
+
+ typename conflictset::const_iterator
+ found = find_matching_conflict(new_acts);
+
+ if(found == conflicts.end())
+ generator.make_successor(s, &act, &act+1,
+ (dep *) 0, (dep *) 0,
+ universe, weights);
+ else
+ {
+ if(debug)
+ {
+ std::cout << "Discarding " << v.get_package().get_name()
+ << " " << v.get_name() << " due to conflict ";
+ dump_conflict(std::cout, *found);
+ std::cout << std::endl;
+ }
+
+ imm::map<package, action> m = *found;
+
+ for(typename imm::map<package, action>::const_iterator ci
+ = m.begin(); ci != m.end(); ++ci)
+ {
+ // Discard the version that we were trying to install,
+ // so that the caller can use this to form a more
+ // general conflict if all its resolutions fail.
+ if(ci->first != v.get_package())
+ insert_conflictor(conflict, ci->second);
+ else
+ assert(ci->second.ver == v);
+ }
+ }
+ }
+ }
+
+ /** Build the successors of a solution node for a particular
+ * dependency.
+ *
+ * \param conflict a map which will be initialized with a conflict
+ * explaining the non-appearance of some solvers (if there are no
+ * solvers, this will be a full conflict explaining the lack of
+ * solvers).
+ *
+ * \param out a vector onto which the successors should be pushed.
+ */
+ template<typename SolutionGenerator>
+ void generate_successors(const solution &s,
+ const dep &d,
+ imm::map<package, action> &conflict,
+ const SolutionGenerator &generator) const
+ {
+ version source = d.get_source();
+ typename imm::map<package, action>::node
+ source_found = s.get_actions().lookup(source.get_package());
+
+ // Try moving the source, if it is legal to do so
+ if(source_found.isValid())
+ insert_conflictor(conflict, action(source, d, false, -1));
+ else
+ {
+ assert(source == source.get_package().current_version());
+
+ for(typename package::version_iterator vi = source.get_package().versions_begin();
+ !vi.end(); ++vi)
+ if(*vi != source)
+ generate_single_successor(s, d, *vi, true, conflict, generator);
+ }
+
+ // Now try installing each target of the dependency.
+ for(typename dep::solver_iterator si = d.solvers_begin();
+ !si.end(); ++si)
+ generate_single_successor(s, d, *si, false, conflict, generator);
+
+ // Finally, maybe we can leave this dependency unresolved.
+ if(d.is_soft())
+ generator.make_successor(s, (action *) 0, (action *) 0,
+ &d, &d+1, universe, weights);
+ }
+
+ /** Processes the given solution by enqueuing its successor nodes
+ * (if any are available). Note that for clarity, we now generate
+ * *all* successors before examining *any*.
+ */
+ void process_solution(const solution &s)
+ {
+ // Any forcings are immediately applied to 'curr', so that
+ // forcings are performed ASAP.
+ solution curr = s;
+
+ assert(!s.get_broken().empty());
+
+ // This loop attempts to generate all the successors of a
+ // solution. However, if a "forced" dependency arises, it
+ // re-verifies all the dependencies of the solution.
+ bool done = false;
+ while(!done)
+ {
+ if(debug)
+ {
+ std::cout << "Processing ";
+ curr.dump(std::cout);
+ std::cout << std::endl;
+ }
+
+ done = true;
+
+ // Remember the solution whose broken dependencies we're
+ // iterating over.
+ solution starting_solution = curr;
+
+ for(typename imm::set<dep>::const_iterator bi=starting_solution.get_broken().begin();
+ bi!=starting_solution.get_broken().end(); ++bi)
+
+ {
+ // Check for the case where this dependency has been
+ // fortuitously solved by forcing another broken
+ // dependency.
+ if(starting_solution != curr && !(*bi).broken_under(curr))
+ continue;
+
+ // Assert against impossible conditions (if this happens
+ // something is broken elsewhere).
+ if(starting_solution == curr && !(*bi).broken_under(curr))
+ {
+ std::cerr << "Unexpectedly non-broken dependency "
+ << *bi << "!" << std::endl;
+
+ version source = (*bi).get_source();
+
+ if(curr.version_of(source.get_package()) != source)
+ std::cerr << " (" << source.get_package().get_name()
+ << " " << source.get_name()
+ << " is not installed)" << std::endl;
+
+ for(typename dep::solver_iterator si = (*bi).solvers_begin();
+ !si.end(); ++si)
+ if(curr.version_of((*si).get_package()) == *si)
+ std::cerr << " (" << source.get_package().get_name()
+ << " " << source.get_name()
+ << " is not installed)" << std::endl;
+
+ abort();
+ }
+
+ imm::map<package, action> conflict;
+
+
+ int num_successors = 0;
+ generate_successors(curr, *bi, conflict,
+ null_generator(num_successors));
+
+
+
+ if(num_successors == 0)
+ {
+ if(debug)
+ {
+ std::cout << "Discarding solution; unresolvable dependency "
+ << *bi << " with conflict ";
+
+ dump_conflict(std::cout, conflict);
+
+ std::cout << std::endl;
+ }
+
+ add_conflict(conflict);
+
+ return;
+ }
+ else if(num_successors == 1)
+ {
+ if(debug)
+ std::cout << "Forced resolution of " << *bi << std::endl;
+
+ std::vector<solution> v;
+ real_generator g(v);
+ // NB: this may do redundant work adding to 'conflict'.
+ // Use a generator object to avoid that?
+ generate_successors(curr, *bi, conflict,
+ real_generator(v));
+
+ assert(v.size() == 1);
+
+ curr = v.back();
+ done = false;
+ }
+ }
+ }
+
+ // In the course of forcing dependencies, we might have actually
+ // arrived at a solution, in which case we should just enqueue it
+ // and stop (a full solution has no successors to generate).
+ if(curr.is_full_solution())
+ {
+ try_enqueue(curr);
+ return;
+ }
+
+ unsigned int nsols = 0;
+
+ // First try to enqueue stuff related to a dependency that the
+ // user constrained; then just go for a free-for-all.
+ for(typename imm::set<dep>::const_iterator bi=curr.get_broken().begin();
+ bi!=curr.get_broken().end() && (nsols == 0 ||
+ nsols < max_successors); ++bi)
+
+ if(impinges_user_constraint(*bi))
+ {
+ // Is it possible to take this out somehow?
+ imm::map<package, action> conflict;
+
+ if(debug)
+ std::cout << "Generating successors for " << *bi
+ << std::endl;
+
+ std::vector<solution> v;
+ generate_successors(curr, *bi, conflict, real_generator(v));
+ try_enqueue(v);
+ nsols += v.size();
+ }
+
+ for(typename imm::set<dep>::const_iterator bi=curr.get_broken().begin();
+ bi!=curr.get_broken().end() && (nsols == 0 ||
+ nsols < max_successors); ++bi)
+ {
+ // Is it possible to take this out somehow?
+ imm::map<package, action> conflict;
+
+ if(debug)
+ std::cout << "Generating successors for " << *bi
+ << std::endl;
+
+ std::vector<solution> v;
+ generate_successors(curr, *bi, conflict, real_generator(v));
+ try_enqueue(v);
+ nsols += v.size();
+ }
+ }
+ public:
+
+ /** Construct a new generic_problem_resolver.
+ *
+ * \param _score_score the score per "step" of a (partial) solution. Typically negative.
+ * \param _broken_score the score to add per broken dependency of a (partial) solution. Typically negative.
+ * \param _unfixed_soft_score the score to add per soft dependency LEFT UNFIXED. Typically negative.
+ * \param infinity a score value that will be considered to be "infinite". Solutions
+ * with less than -infinity points will be immediately discarded.
+ * \param _full_solution_score a bonus for goal nodes (things
+ * that solve all dependencies)
+ * \param _universe the universe in which we are working.
+ */
+ generic_problem_resolver(int _step_score, int _broken_score,
+ int _unfixed_soft_score,
+ int infinity, unsigned int _max_successors,
+ int _full_solution_score,
+ const PackageUniverse &_universe)
+ :weights(_step_score, _broken_score, _unfixed_soft_score,
+ _full_solution_score, _universe.get_version_count()),
+ minimum_score(-infinity), max_successors(_max_successors),
+ universe(_universe), finished(false), deferred_dirty(false),
+ debug(false), remove_stupid(true),
+ conflicts(_universe.get_package_count())
{
// Find all the broken deps.
for(typename PackageUniverse::broken_dep_iterator bi=universe.broken_begin();
More information about the Aptitude-svn-commit
mailing list