Work in progress
This page of the documentation is not yet finished and only contains a draft of the content.
Technical specification: solver state
Note
This document is a technical specification, which might not be the best way to learn about how the solver works. For that, refer to conda install and Solvers.
The Solver API will pass a collection of MatchSpec objects (from now on, we will refer to
them as specs) to the underlying SAT solver. How this list is built from the prefix state
and context options is not a straightforward process, but an elaborate logic. This is better
understood if we examine the ingredients that participate in the construction of specs. We
will label them like this:
These groups below will not change during the solver attempts:
requested:MatchSpecobjects the user is explicitly asking for.installed: Installed packages, expressed asPrefixRecordobjects. Empty if the environment did not exist.history: Specs asked in the past: theHistory. Empty if the environment did not exist.aggressive_updates: Packages included in the aggressive updates list. These packages are always included in any requests to make sure they stay up-to-date under all circumstances.pinned: Packages pinned to a specific version, either viapinned_packagesin your.condarcor defined in a$PREFIX/conda-meta/pinnedfile.virtual: System properties exposed as virtual packages (e.g.__glibc=2.17). They can’t really be installed or uninstalled, but they do participate in the solver by adding runtime constraints.do_not_remove: A fixed list of packages that receive special treatment by the solver due to poor metadata in the early days of conda packaging. A legacy leftover.
This one group does change during the solver lifetime:
conflicting: Specs that are suspected to be a conflict for the solver.
Also, two more sources that are not obvious at first. These are not labeled as a source, but they
do participate in the specs collection:
In new environments, packages included in the
contex.create_default_packageslist. TheseMatchSpecobjects are injected in eachconda createcommand, so the solver will see them as explicitly requested by the user (requested).Specs added by command line modifiers. The specs here present aren’t new (they are already in other categories), but they might end up in the
specslist only when a flag is added. For example,update --allwill add all the installed packages to thespecslist, with no version constraint. Without this flag, the installed packages will still end up in thespecslist, but with full constraints (--freeze-installeddefaults for the first attempt) unless:Frozen attempt failed.
--update-specs(or any otherUpdateModifier) was passed, overriding--freeze-installed.
See? It gets involved. We will also use this vocabulary to help narrow down the type of change being done:
Types of spec objects:
specs: map of package name to its currently correspondingMatchSpecinstance.spec: specific instance of aMatchSpecobject.Exact or frozen
spec: aspecwhere both theversionandbuildfields are constrained with==operators (exact match).Fully constrained or tight
spec: aspecwhere bothversionandbuildare populated, but not necessarily with equality operators. It can also be inequalities (>,<, etc.) and fuzzy matches (*something*).Version-only
spec: aspecwhere only theversionfield is populated. Thebuildis not.Name-only, bare, or unconstrained
spec: aspecwith noversionorbuildfields. Just the name of the package.Targeted
spec: aspecwith thetargetfield populated. Extracted from the comments in the solver logic:targetis a reference to the package currently existing in the environment. Settingtargetinstructs the solver to not disturb that package if it’s not necessary. If the spec.name is being modified by inclusion inspecs_to_add, we don’t settarget, since we want the solver to modify/update that package.TL;DR: when working with
MatchSpecobjects,to minimize the version change, set
MatchSpec(name=name, target=prec.dist_str())to freeze the package, set all the components of
MatchSpecindividually
if the
specobject does not have an adjective, it should be assumed it’s being added to thespecsmap unmodified, as it came from its origin.
Pools (collections of PackageRecord objects):
Installed pool: The installed packages, grouped by name. Each group should only contain one record!
Explicit pool: The full index, but reduced for the specs in
requested.
The following sections will get dry and to the point. They will state what output to expect from
a given set of initial conditions. At least we’ll try. Take into account that the specs list
is kept around across attempts! In other words, the specs list is only really empty in the first
attempt; if this fails, the subsequent attempts will only overwrite (update) the existing one. In
practice, this should only affect how constrained packages are. The names should be the same.
It will also depend on whether we are adding (conda install|create|update) or removing
(conda remove) packages. There’s a common initialization part for both, but after that the
logic is separate.
Common initialization
Note: This happens in
Solver._collect_all_metadata()
This happens regardless of the type of command we are using (install, update, create or
remove).
Add specs from
history, if any.Add specs from
do_not_remove, but only if:There’s no spec for that name in
specsalready, andA package with that name is not installed.
Add
virtualpackages as unconstrainedspecs.Add all those installed packages, as unconstrained
specs, that satisfy any of these conditions:The history is empty (in that case, all installed packages are added)
The package name is part of
aggresive_updatesThe package was not installed by
conda, but by pip or other PyPI tools instead.
Preparing the index
At this point, the populated specs and the requested specs are merged together. This temporary
collection is used to determine how to reduce the index.
Processing specs for conda install
Preparation
Generate the explicit pool for the requested specs (via
Resolve._get_package_pool()).Detect potential conflicts (via (
Resolve.get_conflicting_specs()).
Refine specs that match installed records
Check that each of
specsmatch a single installed package or none! If there are two or more matches, it means that the environment is in bad shape and is basically broken. If thespecmatches one installed package (let’s call it installed match), we will modify the originalspec.We will turn the
specinto an exact (frozen) spec if:The installed match is unmanageable (installed by pip, virtual, etc.)
There’s no history, we are not in
--freeze-installedmode, and:The spec is not a potential conflict, and
The package name cannot be found in the explicit pool index or, if it is, the installed match can be found in that explicit pool (to guarantee it will be found instead of creating one more conflict just because).
We relax the
specto a name-onlyspecif it’s part of the aggressive updates list.We turn it into a targeted
specif:The spec is in
history. In that case, we take its historic spec counterpart and set the target to the installed match version and build.None of the above conditions were met. In other words, we’ll try our best to match the installed package if none of the above applies, but if we fail we’ll stick to whatever was already present in the
specs.